Merge branch 'dev'

This commit is contained in:
lyswhut
2022-01-01 15:28:19 +08:00
294 changed files with 17923 additions and 12649 deletions
+6 -2
View File
@@ -1,5 +1,8 @@
{
"extends": "standard",
"extends": [
// "plugin:vue/vue3-recommended",
"standard"
],
"plugins": [
"html"
],
@@ -18,7 +21,8 @@
"standard/no-callback-literal": "off",
"prefer-const": "off",
"no-labels": "off",
"node/no-callback-literal": "off"
"node/no-callback-literal": "off",
"vue/multi-word-component-names": "off"
},
"settings": {
"html/html-extensions": [".html", ".vue"]
+3 -3
View File
@@ -16,7 +16,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '14'
- name: Cache file
uses: actions/cache@v2
@@ -102,7 +102,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '14'
- name: Cache file
uses: actions/cache@v2
@@ -156,7 +156,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '14'
- name: Cache file
uses: actions/cache@v2
+3 -3
View File
@@ -16,7 +16,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '14'
- name: Cache file
uses: actions/cache@v2
@@ -59,7 +59,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '14'
- name: Cache file
uses: actions/cache@v2
@@ -102,7 +102,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
node-version: '14'
- name: Cache file
uses: actions/cache@v2
+4 -3
View File
@@ -1,11 +1,12 @@
module.exports = {
upgrade: true,
// upgrade: true,
// target: 'newest',
reject: [
'vue-loader',
'webpack-dev-server',
'eslint',
'electron',
'electron-builder'
'electron-builder',
'chalk',
// 'eslint-config-standard'
]
}
+7 -12
View File
@@ -1,19 +1,14 @@
{
"path-intellisense.mappings": {
"@main/*": "${workspaceFolder}/src/main/*",
"@renderer/*": "${workspaceFolder}/src/renderer/*",
"@lyric/*": "${workspaceFolder}/src/renderer-lyric/*",
"@static/*": "${workspaceFolder}/src/static/*",
"@common/*": "${workspaceFolder}/src/common/*",
},
"i18n-ally.localesPaths": [
"src/renderer/lang"
"src/lang"
],
// "i18n-ally.fullReloadOnChanged": true,
"i18n-ally.keystyle": "nested",
"i18n-ally.displayLanguage": "zh-cn",
"i18n-ally.sourceLanguage": "zh-cn",
"i18n-ally.namespace": true,
"i18n-ally.translate.engines": ["google-cn", "google"],
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.keystyle": "flat",
"i18n-ally.translate.engines": [
"google-cn",
"google"
],
"i18n-ally.sortKeys": true
}
+43 -6
View File
@@ -6,6 +6,49 @@ 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.16.0](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.3...v1.16.0) - 2022-01-01
这算是一个大版本,对主窗口部分的代码逻辑做了较大改动,但由于界面的改动不大,所以没有更新大版本号。
虽然经过一个月的测试与问题修复,但可能仍然存在未发现的问题,若你发现某些界面异常、某些行为与旧版本存在差异等问题,欢迎反馈!
另外祝大家元旦快乐~!
### 新增
- 播放详情页新增音量控制条
- 播放详情页新增桌面歌词切换按钮
- 新增将我的列表保存为TXT、CSV格式,可以去设置-备份与恢复中使用(注意:此类格式的备份目前不支持恢复到LX Music中)
- 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用
- 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启
- 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表
- 播放详情页新增音频可视化功能(实验性)
- 我的列表新增拖动调整位置功能,按住Ctrl键(Mac上对应Command键)的时候将进入“拖动模式”,此时可以拖动列表的位置来调整顺序
### 优化
- 优化列表性能,软件整体性能
- 调整Mac平台下的图标大小
- 同步功能添加对列表顺序调整的控制,确保手动调整位置后的列表与不同的电脑同步时,列表位置不会被还原
- 优化歌单详情、排行榜名右键的播放按钮的播放机制,现在不用等待整个列表(多页时)加载完成才能播放了
- 为播放详情页、桌面歌词添加延迟滚动,播放详情页略微减小已激活歌词的缩放大小及桌面歌词翻译大小
- 修改右边控制按钮为windows风格
- 更新了新年皮肤的背景与配色,欢迎体验~
### 修复
- 修复kw源某些歌曲的歌词提取异常的问题
### 变更
- 现在使用繁体中文语言时将不再自动转换歌词,转换行为将由上面新增的转换开关控制
### 移除
- 移除我的列表右键菜单的“上移、下移列表”功能,调整改用新增的拖动功能去调整位置
### 其他
- 升级vue到 3.x
## [1.15.3](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.2...v1.15.3) - 2021-11-21
### 修复
@@ -21,12 +64,6 @@ Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
- 降级electron到v13.4.0(这修复了windows 7下播放歌曲时软件会崩溃的问题)
## [1.15.3](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.1...v1.15.3) - 2021-11-09
### 其他
- 降级electron到v13.4.0(这修复了windows 7下播放歌曲时软件会崩溃的问题)
## [1.15.1](https://github.com/lyswhut/lx-music-desktop/compare/v1.15.0...v1.15.1) - 2021-11-09
### 优化
+16
View File
@@ -123,6 +123,12 @@
- 检查防火墙是否拦截了PC端的服务端口
- 路由器若开启了AP隔离,则此功能无法使用
#### 连接同步服务失败的检查
1. 确保PC端的同步服务已启用成功(若连接码、同步服务地址没有内容,则证明服务启动失败,此时看启用同步功能复选框后面的错误信息自行解决,另外若你不知道端口号是什么意思就不要乱改,或不要改得太大与太小)
2. 在手机浏览器地址栏输入`http://x.x.x.x:23332/hello` **(注:将`x.x.x.x`换成PC端显示的同步服务地址,`23332`为PC端的端口号)** 后回车,若此地址可以打开并显示 `Hello~::^-^::`则证明移动端与PC端网络已互通,
3. 若移动端无法打开第2步的地址,则在PC端的浏览器地址栏输入并打开该地址,若可以打开,则要么是被LX Music PC端被电脑防火墙拦截,要么PC端与移动端不在同一个网络下,或者路由器开启了AP隔离(一般在公共网络下会出现这种情况)
## 界面异常(界面显示不完整)
### Windows 7 下界面异常
@@ -244,6 +250,16 @@ Windows 7 未开启 Aero 效果时桌面歌词会有问题,详情看上面的
- 以管理员权限打开`cmd`,输入`sfc /scannow`回车等待检查完成重启电脑
- 若上面的方法**修复、重启**电脑后仍然不行,就自行百度弹出的**错误信息**看下别人是怎么解决的
## MAC OS无法启动软件,提示 lx-music-desktop 已损坏
这是因为软件没有签名,被系统阻止运行,<br>
在终端里输入 `sudo xattr -rd com.apple.quarantine /Applications/lx-music-desktop.app`,然后输入你的电脑密码即可
还可以参考:
- <http://www.pc6.com/edu/168719.html>
- <https://blog.csdn.net/for641/article/details/104811538>
## 杀毒软件提示有病毒或恶意行为
本人只能保证我写的代码不包含任何**恶意代码**、**收集用户信息**的行为,并且软件代码已开源,请自行查阅,软件安装包也是由CI拉取源代码构建,构建日志:[GitHub Actions](https://github.com/lyswhut/lx-music-desktop/actions)<br>
+2 -2
View File
@@ -36,8 +36,8 @@
所用技术栈:
- Electron 15
- Vue 2
- Electron 13
- Vue 3
已支持的平台:
-13
View File
@@ -1,7 +1,6 @@
const path = require('path')
const { merge } = require('webpack-merge')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const baseConfig = require('./webpack.config.base')
@@ -19,17 +18,5 @@ module.exports = merge(baseConfig, {
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
__userApi: `"${path.join(__dirname, '../../src/main/modules/userApi').replace(/\\/g, '\\\\')}"`,
}),
new FriendlyErrorsPlugin({
onErrors(severity, errors) { // Silent warning from electron-debug
if (severity != 'warning') return
for (let i = errors.length - 1; i > -1; i--) {
const error = errors[i]
if (error.file == './node_modules/electron-debug/index.js') errors.splice(i, 1)
}
// You can listen to errors transformed and prioritized by the plugin
// severity can be 'error' or 'warning'
},
}),
],
})
@@ -1,8 +1,7 @@
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const { VueLoaderPlugin } = require('vue-loader')
const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanCSSPlugin = require('less-plugin-clean-css')
const ESLintPlugin = require('eslint-webpack-plugin')
const vueLoaderConfig = require('../vue-loader.config')
@@ -23,6 +22,7 @@ module.exports = {
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
@@ -57,11 +57,6 @@ module.exports = {
loader: 'less-loader',
options: {
sourceMap: true,
lessOptions: {
plugins: [
new CleanCSSPlugin({ advanced: true }),
],
},
},
}),
},
@@ -81,6 +76,7 @@ module.exports = {
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: path.join(__dirname, '../../src/renderer/assets/svgs'),
type: 'asset',
parser: {
dataUrlCondition: {
@@ -91,6 +87,20 @@ module.exports = {
filename: 'imgs/[name]-[contenthash:8][ext]',
},
},
{
test: /\.svg$/,
include: path.join(__dirname, '../../src/renderer/assets/svgs'),
use: [
{
loader: 'svg-sprite-loader',
options: {
symbolId: 'icon-[name]',
},
},
'svg-transform-loader',
'svgo-loader',
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset',
@@ -126,7 +136,6 @@ module.exports = {
template: path.join(__dirname, '../../src/renderer-lyric/index.pug'),
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
scriptLoading: 'blocking',
__dirname,
}),
new VueLoaderPlugin(),
@@ -1,6 +1,5 @@
const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { merge } = require('webpack-merge')
@@ -11,12 +10,13 @@ module.exports = merge(baseConfig, {
devtool: 'eval-source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
@@ -9,7 +9,8 @@ const baseConfig = require('./webpack.config.base')
const { dependencies } = require('../../package.json')
let whiteListedModules = ['vue']
// let whiteListedModules = ['vue']
let whiteListedModules = ['vue', 'vue-router', 'vuex', 'vue-i18n']
module.exports = merge(baseConfig, {
@@ -31,6 +32,8 @@ module.exports = merge(baseConfig, {
'process.env': {
NODE_ENV: '"production"',
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
}),
],
optimization: {
+18 -9
View File
@@ -1,8 +1,7 @@
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const { VueLoaderPlugin } = require('vue-loader')
const HTMLPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanCSSPlugin = require('less-plugin-clean-css')
const ESLintPlugin = require('eslint-webpack-plugin')
const vueLoaderConfig = require('../vue-loader.config')
@@ -23,6 +22,7 @@ module.exports = {
},
resolve: {
alias: {
'@': path.join(__dirname, '../../src'),
'@main': path.join(__dirname, '../../src/main'),
'@renderer': path.join(__dirname, '../../src/renderer'),
'@lyric': path.join(__dirname, '../../src/renderer-lyric'),
@@ -57,11 +57,6 @@ module.exports = {
loader: 'less-loader',
options: {
sourceMap: true,
lessOptions: {
plugins: [
new CleanCSSPlugin({ advanced: true }),
],
},
},
}),
},
@@ -81,6 +76,7 @@ module.exports = {
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: path.join(__dirname, '../../src/renderer/assets/svgs'),
type: 'asset',
parser: {
dataUrlCondition: {
@@ -91,6 +87,20 @@ module.exports = {
filename: 'imgs/[name]-[contenthash:8][ext]',
},
},
{
test: /\.svg$/,
include: path.join(__dirname, '../../src/renderer/assets/svgs'),
use: [
{
loader: 'svg-sprite-loader',
options: {
symbolId: 'icon-[name]',
},
},
'svg-transform-loader',
'svgo-loader',
],
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset',
@@ -123,10 +133,9 @@ module.exports = {
plugins: [
new HTMLPlugin({
filename: 'index.html',
template: path.join(__dirname, '../../src/renderer/index.pug'),
template: path.join(__dirname, '../../src/renderer/index.html'),
isProd: process.env.NODE_ENV == 'production',
browser: process.browser,
scriptLoading: 'blocking',
__dirname,
}),
new VueLoaderPlugin(),
+2 -2
View File
@@ -1,6 +1,5 @@
const path = require('path')
const webpack = require('webpack')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const { merge } = require('webpack-merge')
@@ -11,12 +10,13 @@ module.exports = merge(baseConfig, {
devtool: 'eval-source-map',
plugins: [
new webpack.HotModuleReplacementPlugin(),
new FriendlyErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"development"',
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
__static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
+4 -1
View File
@@ -9,7 +9,7 @@ const baseConfig = require('./webpack.config.base')
const { dependencies } = require('../../package.json')
let whiteListedModules = ['vue']
let whiteListedModules = ['vue', 'vue-router', 'vuex', 'vue-i18n']
module.exports = merge(baseConfig, {
@@ -31,9 +31,12 @@ module.exports = merge(baseConfig, {
'process.env': {
NODE_ENV: '"production"',
},
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false',
}),
],
optimization: {
minimize: false,
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin(),
+2851 -1547
View File
File diff suppressed because it is too large Load Diff
+33 -29
View File
@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "1.15.3",
"version": "1.16.0",
"description": "一个免费的音乐查找助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",
@@ -70,7 +70,7 @@
"up": "cross-env ELECTRON_GET_USE_PROXY=true GLOBAL_AGENT_HTTPS_PROXY=http://localhost:1081 npm i"
},
"browserslist": [
"Electron 15.2.0"
"Electron 13.4.0"
],
"engines": {
"node": ">= 14"
@@ -167,51 +167,50 @@
},
"homepage": "https://github.com/lyswhut/lx-music-desktop#readme",
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/core": "^7.16.7",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-modules-umd": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.4",
"@babel/plugin-transform-modules-umd": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.16.7",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.16.4",
"@babel/preset-env": "^7.16.7",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.3",
"babel-preset-minify": "^0.5.1",
"browserslist": "^4.18.1",
"browserslist": "^4.19.1",
"cfonts": "^2.10.0",
"chalk": "^4.1.2",
"changelog-parser": "^2.8.0",
"copy-webpack-plugin": "^10.0.0",
"core-js": "^3.19.1",
"copy-webpack-plugin": "^10.2.0",
"core-js": "^3.20.1",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.1.4",
"css-minimizer-webpack-plugin": "^3.3.1",
"del": "^6.0.0",
"electron": "^13.4.0",
"electron-builder": "^22.11.7",
"electron-debug": "^3.2.0",
"electron-devtools-installer": "^3.2.0",
"electron-to-chromium": "^1.3.904",
"electron-to-chromium": "^1.4.31",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-formatter-friendly": "^7.0.0",
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^8.2.0",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"less-plugin-clean-css": "^1.5.1",
"markdown-it": "^12.2.0",
"markdown-it": "^12.3.0",
"mini-css-extract-plugin": "^2.4.5",
"node-loader": "^2.0.0",
"postcss": "^8.3.11",
"postcss-loader": "^6.2.0",
"postcss": "^8.4.5",
"postcss-loader": "^6.2.1",
"postcss-pxtorem": "^6.0.0",
"pug": "^3.0.2",
"pug-loader": "^2.4.0",
@@ -219,11 +218,14 @@
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"spinnies": "^0.5.1",
"terser-webpack-plugin": "^5.2.5",
"svg-sprite-loader": "^6.0.11",
"svg-transform-loader": "^2.0.13",
"svgo-loader": "^3.0.0",
"terser-webpack-plugin": "^5.3.0",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.8",
"vue-loader": "^17.0.0",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.64.2",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^3.11.2",
"webpack-hot-middleware": "^2.25.1",
@@ -232,23 +234,25 @@
"dependencies": {
"bufferutil": "^4.0.5",
"crypto-js": "^4.1.1",
"electron-log": "^4.4.1",
"electron-log": "^4.4.4",
"electron-store": "^8.0.1",
"electron-updater": "^4.6.1",
"font-list": "git+https://github.com/lyswhut/node-font-list.git#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
"http-terminator": "^3.0.3",
"font-list": "github:lyswhut/node-font-list#4edbb1933b49a9bac1eedd63a31da16b487fe57d",
"http-terminator": "^3.0.4",
"iconv-lite": "^0.6.3",
"image-size": "^1.0.0",
"koa": "^2.13.4",
"long": "^5.1.0",
"long": "^5.2.0",
"mitt": "^3.0.0",
"needle": "^3.0.0",
"node-id3": "^0.2.3",
"request": "^2.88.2",
"socket.io": "^4.4.0",
"sortablejs": "^1.14.0",
"utf-8-validate": "^5.0.7",
"vue": "^2.6.14",
"vue-i18n": "^8.26.7",
"vue-router": "^3.5.3",
"vuex": "^3.6.2"
"vue": "^3.2.26",
"vue-i18n": "^9.2.0-beta.26",
"vue-router": "^4.0.12",
"vuex": "^4.0.2"
}
}
+38 -4
View File
@@ -1,6 +1,40 @@
这算是一个大版本,对主窗口部分的代码逻辑做了较大改动,但由于界面的改动不大,所以没有更新大版本号。
虽然经过一个月的测试与问题修复,但可能仍然存在未发现的问题,若你发现某些界面异常、某些行为与旧版本存在差异等问题,欢迎反馈!
另外祝大家元旦快乐~!
### 新增
- 播放详情页新增音量控制条
- 播放详情页新增桌面歌词切换按钮
- 新增将我的列表保存为TXT、CSV格式,可以去设置-备份与恢复中使用(注意:此类格式的备份目前不支持恢复到LX Music中)
- 新增根据歌曲名、歌手名等字段对列表自动排序的功能,可以在我的列表右击列表名弹出的菜单中使用
- 新增将播放与下载的歌词转换为繁体中文选项,默认关闭,可在设置-播放设置中开启
- 现在已允许进入临时播放列表,即:使用歌单详情页、排行榜名称右键菜单的“播放”按钮播放歌曲时,可右击播放封面进入此临时列表
- 播放详情页新增音频可视化功能(实验性)
- 我的列表新增拖动调整位置功能,按住Ctrl键(Mac上对应Command键)的时候将进入“拖动模式”,此时可以拖动列表的位置来调整顺序
### 优化
- 优化列表性能,软件整体性能
- 调整Mac平台下的图标大小
- 同步功能添加对列表顺序调整的控制,确保手动调整位置后的列表与不同的电脑同步时,列表位置不会被还原
- 优化歌单详情、排行榜名右键的播放按钮的播放机制,现在不用等待整个列表(多页时)加载完成才能播放了
- 为播放详情页、桌面歌词添加延迟滚动,播放详情页略微减小已激活歌词的缩放大小及桌面歌词翻译大小
- 修改右边控制按钮为windows风格
- 更新了新年皮肤的背景与配色,欢迎体验~
### 修复
- 修复设置-控制按钮位置选项与下载歌词编码格式选项命名冲突导致选项显示异常的问题
- 修复播放下载列表时存在失效的歌曲会导致切歌不准确的问题
- 修复潜在的音乐加载超时不会切歌的问题
- 修复因kw源歌词接口停用导致该源歌词获取失败的问题
- 修复kw源某些歌曲的歌词提取异常的问题
### 变更
- 现在使用繁体中文语言时将不再自动转换歌词,转换行为将由上面新增的转换开关控制
### 移除
- 移除我的列表右键菜单的“上移、下移列表”功能,调整改用新增的拖动功能去调整位置
### 其他
- 升级vue到 3.x
+1 -1
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

+72
View File
@@ -53,4 +53,76 @@ module.exports = {
navigationUrlWhiteList: [
],
themes: [
{
id: 0,
name: '绿意盎然',
className: 'green',
},
{
id: 1,
name: '蓝田生玉',
className: 'blue',
},
{
id: 2,
name: '信口雌黄',
className: 'yellow',
},
{
id: 3,
name: '橙黄橘绿',
className: 'orange',
},
{
id: 4,
name: '热情似火',
className: 'red',
},
{
id: 10,
name: '粉装玉琢',
className: 'pink',
},
{
id: 5,
name: '重斤球紫',
className: 'purple',
},
{
id: 6,
name: '灰常美丽',
className: 'grey',
},
{
id: 11,
name: '青出于黑',
className: 'ming',
},
{
id: 12,
name: '青出于黑',
className: 'blue2',
},
{
id: 13,
name: '黑纸白字',
className: 'black',
},
{
id: 7,
name: '月里嫦娥',
className: 'mid_autumn',
},
{
id: 8,
name: '木叶之村',
className: 'naruto',
},
{
id: 9,
name: '新年快乐',
className: 'happy_new_year',
},
],
}
+7 -2
View File
@@ -2,7 +2,7 @@ const path = require('path')
const os = require('os')
const defaultSetting = {
version: '1.0.45',
version: '1.0.47',
player: {
togglePlayMethod: 'listLoop',
highQuality: false,
@@ -12,8 +12,10 @@ const defaultSetting = {
mediaDeviceId: 'default',
isMediaDeviceRemovedStopPlay: false,
isShowLyricTranslation: false,
isS2t: false, // 是否将歌词从简体转换为繁体
isPlayLxlrc: true,
isSavePlayTime: false,
audioVisualization: false,
},
desktopLyric: {
enable: false,
@@ -108,7 +110,10 @@ const overwriteSetting = {
}
// 使用新年皮肤
if (new Date().getMonth() < 2) defaultSetting.themeId = 9
if (new Date().getMonth() < 2) {
defaultSetting.themeId = 9
defaultSetting.desktopLyric.theme = 3
}
exports.defaultSetting = defaultSetting
+3 -1
View File
@@ -8,6 +8,8 @@ const names = {
clear_cache: 'clear_cache',
get_cache_size: 'get_cache_size',
get_env_params: 'get_env_params',
wait: 'wait',
wait_cancel: 'wait_cancel',
set_music_meta: 'set_music_meta',
progress: 'progress',
@@ -35,7 +37,7 @@ const names = {
lang_s2t: 'lang_s2t',
// handle_kw_decode_lyric: 'handle_kw_decode_lyric',
handle_kw_decode_lyric: 'handle_kw_decode_lyric',
get_lyric_info: 'get_lyric_info',
set_lyric_info: 'set_lyric_info',
set_config: 'set_config',
+407
View File
@@ -0,0 +1,407 @@
{
"action": "Manage",
"agree": "Accept",
"audio_visualization": "Audio visualization (experimental)",
"back": "Back",
"cancel_button_text": "Cancel",
"close": "Close",
"comment__hot_load_error": "Hot comments failed to load, click to try to reload",
"comment__hot_loading": "Hot comments are loading",
"comment__hot_title": "Hot Comment",
"comment__new_load_error": "The latest comment failed to load, click to try to reload",
"comment__new_loading": "Latest comments are loading",
"comment__new_title": "Latest comment",
"comment__no_content": "No comments yet",
"comment__refresh": "Refresh comments",
"comment__show": "Song comments",
"comment__title": "{name} comment",
"confirm_button_text": "Yes",
"copy_tip": " (Click to copy)",
"date_format_hour": "{num} hours ago",
"date_format_minute": "{num} minutes ago",
"date_format_second": "{num} seconds ago",
"default": "Default",
"default_list": "Recently Played",
"desktop_lyric__back": "Back",
"desktop_lyric__close": "Close",
"desktop_lyric__font_decrease": "Reduce font size (Right click to fine-tune)",
"desktop_lyric__font_increase": "Increase font size (Right click to fine-tune)",
"desktop_lyric__lock": "Lock Lyrics",
"desktop_lyric__lrc_active_zoom_off": "Unzoom the currently playing lyrics",
"desktop_lyric__lrc_active_zoom_on": "Zoom the currently playing lyrics",
"desktop_lyric__opactiy_decrease": "Increase Transparency (Right click to fine-tune)",
"desktop_lyric__opactiy_increase": "Decrease Transparency (Right click to fine-tune)",
"desktop_lyric__theme": "Theme Color",
"desktop_lyric__unlock": "Unlock Lyrics",
"desktop_lyric__win_top_off": "Cancel the top lyrics interface",
"desktop_lyric__win_top_on": "Top lyrics interface",
"download": "Downloads",
"download__all": "All Tasks",
"download__error": "Error",
"download__finished": "Download complete",
"download__high_quality": "High Quality",
"download__lossless": "Lossless",
"download__multiple_tip": "{len} song selected",
"download__multiple_tip2": "Select preferred download quality",
"download__normal": "Normal",
"download__not_available_tip": "The audio quality is not available",
"download__paused": "Paused",
"download__progress": "Progress",
"download__quality": "Quality",
"download__runing": "Downloading",
"download__status": "Status",
"export": "Export",
"history_clear": "Clear History",
"history_remove": "Right click to remove this entry",
"history_search": "History Searches",
"import": "Import",
"leaderboard": "Charts",
"list__add_to": "Add to ...",
"list__collect": "Collect",
"list__copy_name": "Copy name",
"list__download": "Download",
"list__export_part_desc": "Choose where to save the list file",
"list__file": "Locate the file",
"list__import_part_button_cancel": "Don't",
"list__import_part_button_confirm": "Overwrite",
"list__import_part_confirm": "The imported list ({importName}) has the same ID as the local list ({localName}). Do you want to overwrite the local list?",
"list__import_part_desc": "Select List File",
"list__load_failed": "Ah, the loading failed 😭",
"list__loading": "List loading...⏳",
"list__move_to": "Move to ...",
"list__movedown": "Movedown",
"list__moveup": "Move up",
"list__new_list_btn": "New List",
"list__new_list_input": "New list...",
"list__pause": "Pause Task",
"list__play": "Play",
"list__play_later": "Play later",
"list__remove": "Remove",
"list__remove_tip": "Do you really want to remove {name}?",
"list__remove_tip_button": "Yes, that's right",
"list__rename": "Rename",
"list__search": "Search",
"list__sort": "Adjust position",
"list__source_detail": "Song Page",
"list__start": "Start Task",
"list__sync": "Update",
"list_add__btn_title": "Add the song(s) to {name}",
"list_add__multiple_btn_title": "Add these song(s) to {name}",
"list_add__multiple_title_add": "Add the selected {num} song(s) to ...",
"list_add__multiple_title_move": "Move the selected {num} song(s) to ...",
"list_add__title_first_add": "Add",
"list_add__title_first_move": "Move",
"list_add__title_last": "to...",
"list_sort_modal_by_album": "Album name",
"list_sort_modal_by_down": "Descending",
"list_sort_modal_by_field": "Sort field",
"list_sort_modal_by_name": "Song name",
"list_sort_modal_by_singer": "Singer name",
"list_sort_modal_by_source": "Song source",
"list_sort_modal_by_time": "Duration",
"list_sort_modal_by_type": "Sort categories",
"list_sort_modal_by_up": "Ascending",
"list_sort_modal_tip_confirm": "Are you sure you want to do this?",
"lists__duplicate": "Duplicate song",
"lists__export": "Export",
"lists__export_part_desc": "Choose where to save the list file",
"lists__import": "Import",
"lists__import_part_button_cancel": "No",
"lists__import_part_button_confirm": "Overwrite",
"lists__import_part_confirm": "The imported list ({importName}) has the same ID as the local list ({localName}). Do you overwrite the local list?",
"lists__import_part_desc": "Select list file",
"lists__new_list_btn": "Create list",
"lists__new_list_input": "New list...",
"lists__remove": "Remove",
"lists__remove_tip": "Do you really want to remove {name}?",
"lists__remove_tip_button": "Yes, that's right",
"lists__rename": "Rename",
"lists__sort_list": "Sort songs",
"lists__sync": "Update",
"load_list_file_error_detail": "We have helped you back up the old list file to {path}\nIt is stored in JSON format, you can try to repair and restore it manually\n\nError details: {detail}",
"load_list_file_error_title": "Error loading playlist data",
"loding_list": "Loading...",
"love_list": "Favorites",
"lyric__load_error": "Failed to get lyrics",
"lyric__select": "Lyric text selection",
"min": "Minimize",
"music_album": "Album",
"music_duplicate": "Duplicate song",
"music_name": "Name",
"music_singer": "Artist",
"music_sort__btn_cancel": "Cancel",
"music_sort__btn_confirm": "Confirm",
"music_sort__input_tip": "Please input which position you want to adjust to",
"music_sort__title": "Adjust the position of {name} to:",
"music_sort__title_multiple": "Adjust the position of the selected {num} songs to:",
"music_time": "Length",
"my_list": "Your Library",
"no_item": "Nothing's here...",
"not_agree": "Not accept",
"pagination__next": "Next page",
"pagination__page": "Page {num}",
"pagination__prev": "Previous page",
"player__add_music_to": "Add the current song to...",
"player__buffering": "Buffering...",
"player__desktop_lyric_lock": "Right click to lock lyrics",
"player__desktop_lyric_off": "Close Desktop Lyrics",
"player__desktop_lyric_on": "Open Desktop Lyrics",
"player__desktop_lyric_unlock": "Right click to unlock lyrics",
"player__end": "Stopped",
"player__error": "Error loading music. Switch to next song after 5 seconds",
"player__geting_url": "Getting music link...",
"player__hide_detail_tip": "Hide detail page (Right-click in the view to quickly hide the details page)",
"player__loading": "Music loading...",
"player__music_album": "Album: ",
"player__music_name": "Name: ",
"player__music_singer": "Artist: ",
"player__next": "Next",
"player__pause": "Pause",
"player__pic_tip": "Right click to locate the currently playing song in \"My List\"",
"player__play": "Play",
"player__play_toggle_mode_list": "Play in order",
"player__play_toggle_mode_list_loop": "List Loop",
"player__play_toggle_mode_off": "Disable",
"player__play_toggle_mode_random": "List Random",
"player__play_toggle_mode_single_loop": "Single Loop",
"player__playing": "Now playing...",
"player__prev": "Prev",
"player__refresh_url": "Music URL expired, refreshing...",
"player__stop": "Paused",
"player__volume": "Volume: ",
"search": "Search",
"search__hot_search": "Top Searches",
"search__welcome": "Search what I want to 😉",
"setting": "Settings",
"setting__about": "About lx-music-desktop",
"setting__backup": "Backup and restore",
"setting__backup_all": "All data (list data and setting data)",
"setting__backup_all_export": "Export",
"setting__backup_all_export_desc": "Select the backup to...",
"setting__backup_all_import": "Import",
"setting__backup_all_import_desc": "Select a backup file",
"setting__backup_other": "Other backup formats (recovery of such backup files is not currently supported)",
"setting__backup_other_export_dir": "Choose where to save the file",
"setting__backup_other_export_list_csv": "Export list in CSV format",
"setting__backup_other_export_list_text": "Export list in TXT format",
"setting__backup_other_export_list_text_confirm": "Do you want to merge all the lists into one file?",
"setting__backup_part": "Partial data (list data includes audition list, favorite list, user-defined list, setting data does not include shortcut key settings)",
"setting__backup_part_export_list": "Export lists",
"setting__backup_part_export_list_desc": "Save the list to...",
"setting__backup_part_export_setting": "Export settings",
"setting__backup_part_export_setting_desc": "Save the list to...",
"setting__backup_part_import_list": "Import lists",
"setting__backup_part_import_list_desc": "Select a list backup",
"setting__backup_part_import_setting": "Import settings",
"setting__backup_part_import_setting_desc": "Select the Settings file",
"setting__basic": "General",
"setting__basic_animation": "Random pop-up animation",
"setting__basic_control_btn_position": "Control Button Position",
"setting__basic_control_btn_position_left": "Left",
"setting__basic_control_btn_position_right": "Right",
"setting__basic_lang": "Language",
"setting__basic_lang_title": "The language displayed in the software",
"setting__basic_show_animation": "Show switching animation",
"setting__basic_source": "Music source",
"setting__basic_source_status_failed": "Initialization failed",
"setting__basic_source_status_initing": "Initializing",
"setting__basic_source_status_success": "Initialization successful",
"setting__basic_source_temp": "Temporary API (some features not available; workaround if Test API unavailable)",
"setting__basic_source_test": "Test API (Available for most software features)",
"setting__basic_source_title": "Choose a music source",
"setting__basic_source_user_api_btn": "Custom Source Management",
"setting__basic_sourcename": "Source name",
"setting__basic_sourcename_alias": "Aliases",
"setting__basic_sourcename_real": "Original",
"setting__basic_sourcename_title": "Select the name of music source",
"setting__basic_theme": "Theme",
"setting__basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
"setting__basic_window_size": "Window size",
"setting__basic_window_size_big": "Large",
"setting__basic_window_size_huge": "Huge",
"setting__basic_window_size_larger": "Larger",
"setting__basic_window_size_medium": "Medium",
"setting__basic_window_size_oversized": "Oversized",
"setting__basic_window_size_small": "Small",
"setting__basic_window_size_smaller": "Smaller",
"setting__basic_window_size_title": "Set the window size",
"setting__click_copy": "Click to copy",
"setting__click_open": "Click to open",
"setting__desktop_lyric": "Desktop Lyric Settings",
"setting__desktop_lyric_always_on_top": "Make the lyrics always above other windows",
"setting__desktop_lyric_enable": "Display lyrics",
"setting__desktop_lyric_font": "Lyric font",
"setting__desktop_lyric_font_default": "Default",
"setting__desktop_lyric_lock": "Lock lyrics",
"setting__desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen",
"setting__download": "Download",
"setting__download_data_embed": "Whether to embed the following content in the audio file",
"setting__download_embed_lyric": "Embedding lyric",
"setting__download_embed_pic": "Embedding cover",
"setting__download_enable": "Whether to enable download function",
"setting__download_lyric": "Lyrics download",
"setting__download_lyric_format": "Downloaded lyrics file encoding format",
"setting__download_lyric_format_gbk": "GBK (Try to select this format when Chinese garbled characters appear on some devices)",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "Select whether to download the lyrics file",
"setting__download_name": "Music file naming",
"setting__download_name1": "Title - Artist",
"setting__download_name2": "Artist - Title",
"setting__download_name3": "Title only",
"setting__download_name_title": "Select the music naming method for downloading",
"setting__download_path": "Download path",
"setting__download_path_change_btn": "Change",
"setting__download_path_label": "Current: ",
"setting__download_path_open_label": "Click to open this path",
"setting__download_path_title": "Define the path to downloading",
"setting__download_select_save_path": "Select the save path",
"setting__download_use_other_source": "Auto source download",
"setting__download_use_other_source_tip": "When the song cannot be downloaded from the original source, try to switch to another source to download. Note: This function does not 100% guarantee that the version of the song after changing the source is consistent with the original version",
"setting__hot_key": "Shortcut Key Settings",
"setting__hot_key_common_focus_search_input": "Focus Search Box",
"setting__hot_key_common_min": "Minimize the program",
"setting__hot_key_common_toggle_close": "Quit Program",
"setting__hot_key_common_toggle_hide": "Show/Hide Program",
"setting__hot_key_common_toggle_min": "Minimize/Restore Program",
"setting__hot_key_desktop_lyric_toggle_always_top": "Top Desktop Lyrics Switch",
"setting__hot_key_desktop_lyric_toggle_lock": "Desktop Lyric Lock Switch",
"setting__hot_key_desktop_lyric_toggle_visible": "Turn on/off desktop lyrics",
"setting__hot_key_global_title": "Global Shortcut Key",
"setting__hot_key_local_title": "Shortcut Keys in Software",
"setting__hot_key_player_next": "Next Song",
"setting__hot_key_player_prev": "Previous Song",
"setting__hot_key_player_toggle_play": "Play/Pause Control",
"setting__hot_key_player_volume_down": "Reduce Volume",
"setting__hot_key_player_volume_mute": "Mute Switch",
"setting__hot_key_player_volume_up": "Increase Volume",
"setting__hot_key_tip_input": "Please enter a new key",
"setting__hot_key_unset_input": "Not Set",
"setting__is_enable": "Enabled",
"setting__is_show": "Showed",
"setting__list": "List",
"setting__list_add_music_location_type": "Position when adding a song to the list",
"setting__list_add_music_location_type_bottom": "Bottom",
"setting__list_add_music_location_type_top": "Top",
"setting__list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
"setting__list_source": "Show song source (only valid for my music category)",
"setting__network": "Network",
"setting__network_proxy_host": "Host",
"setting__network_proxy_password": "Password",
"setting__network_proxy_port": "Port",
"setting__network_proxy_title": "HTTP Proxy (False setting would block Internet connections)",
"setting__network_proxy_username": "Username",
"setting__odc": "Auto clear",
"setting__odc_clear_search_input": "Clear the search box when you are not searching",
"setting__odc_clear_search_list": "Clear the search list when you are not searching",
"setting__other": "Extras",
"setting__other_play_list_cache": "List cache management",
"setting__other_play_list_cache_clear_btn": "Clear list cache information",
"setting__other_play_list_cache_tip": "The cached song links and playback sources in my list need to be re-acquired when playing or downloading songs after cleaning. Do not clean up if there are no issues related to song playback",
"setting__other_play_list_cache_tip_confirm": "In my list, the cached song links and play alternative sources need to be re-obtained when playing and downloading songs after cleaning. Do not clean up if there are no issues related to song playback. Are you sure to clean up?",
"setting__other_resource_cache": "Resource cache management",
"setting__other_resource_cache_clear_btn": "Clear resource cache",
"setting__other_resource_cache_confirm": "I want to clear",
"setting__other_resource_cache_label": "The software has used cache size: ",
"setting__other_resource_cache_tip": "Picture, audio, and other caches. After cleaning, resources such as pictures will need to be downloaded again. It is not recommended to clean up. The software will dynamically manage the cache size according to the disk space.",
"setting__other_resource_cache_tip_confirm": "Involving the cache of pictures, audios, etc., the pictures and other resources will need to be downloaded again after cleaning. It is not recommended to clean up. The software will dynamically manage the cache size according to the disk space. Do you still need to clean up?",
"setting__other_tray_theme": "Tray Icon Style",
"setting__other_tray_theme_black": "Black Color",
"setting__other_tray_theme_native": "White",
"setting__other_tray_theme_origin": "Primary Color",
"setting__play": "Play",
"setting__play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
"setting__play_lyric_s2t": "Convert the playing and downloading lyrics to Traditional Chinese",
"setting__play_lyric_transition": "Show lyrics translation",
"setting__play_mediaDevice": "Audio output",
"setting__play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
"setting__play_mediaDevice_title": "Select a media device for audio output",
"setting__play_quality": "Play 320K quality songs first (if supported)",
"setting__play_save_play_time": "Remember playback progress",
"setting__play_task_bar": "Show playing progress on the taskbar",
"setting__search": "Search",
"setting__search_focus_search_box": "Automatically focus the search box on startup",
"setting__search_history": "Search history",
"setting__search_hot": "Top Searches",
"setting__sync": "Data synchronization",
"setting__sync_address": "Synchronization service address: {address}",
"setting__sync_auth_code": "Connection code: {code}",
"setting__sync_device": "Connected devices: {devices}",
"setting__sync_enable": "Enable the synchronization function (because the data is transmitted in clear text, please use it under a trusted network)",
"setting__sync_port": "Sync port settings",
"setting__sync_port_tip": "Please enter the synchronization service port number",
"setting__sync_refresh_code": "Refresh the connection code",
"setting__sync_tip": "For how to use it, please see the \"Sync function\" section of the FAQ",
"setting__update": "Update",
"setting__update_checking": "Checking for updates...",
"setting__update_current_label": "Current version: ",
"setting__update_downloading": "Update is found and being downloaded...⏳",
"setting__update_init": "Processing update...",
"setting__update_latest": "The software is up-to-date, enjoy yourself!🥂",
"setting__update_latest_label": "Latest version: ",
"setting__update_open_version_modal_btn": "Open the update window🚀",
"setting__update_progress": "Download progress: ",
"setting__update_unknown": "Unknown",
"song_list": "Playlists",
"songlist__import_input_tip": "Enter songlist link or songlist ID",
"songlist__import_input_tip_1": "Cross-source playlists are not supported, please confirm whether the playlist to be opened corresponds to the current playlist source",
"songlist__import_input_tip_2": "If you encounter a link to a playlist that cannot be opened, welcome feedback",
"songlist__import_input_tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening",
"songlist__open_list": "open {name} playlist",
"source_alias_all": "Aggregated",
"source_alias_bd": "BD Music",
"source_alias_kg": "KG Music",
"source_alias_kw": "KW Music",
"source_alias_mg": "MG Music",
"source_alias_tx": "TX Music",
"source_alias_wy": "WY Music",
"source_alias_xm": "XM Music",
"source_all": "Aggregated",
"source_bd": "Baidu",
"source_kg": "Kugou",
"source_kw": "Kuwo",
"source_mg": "Migu",
"source_tx": "Tencent",
"source_wy": "Netease",
"source_xm": "Xiami",
"sync__merge_btn_local_remote": "Local list merge remote list",
"sync__merge_btn_remote_local": "Remote list merge local list",
"sync__merge_label": "Merge",
"sync__merge_tip": "Merge:",
"sync__merge_tip_desc": "Merge the two lists together, the same song will be removed (the song of the merged person is removed), and different songs will be added.",
"sync__other_label": "Other",
"sync__other_tip": "Other: ",
"sync__other_tip_desc": "\"Only use real-time synchronization function\" will not modify the lists of both parties, only real-time synchronization operations; \"Cancel synchronization\" will directly disconnect the two parties.",
"sync__overwrite": "Full coverage",
"sync__overwrite_btn_cancel": "Cancel sync",
"sync__overwrite_btn_local_remote": "Local list Overwrite remote list",
"sync__overwrite_btn_none": "Only use real-time synchronization",
"sync__overwrite_btn_remote_local": "Remote list Overwrite local list",
"sync__overwrite_label": "Cover",
"sync__overwrite_tip": "Cover: ",
"sync__overwrite_tip_desc": "The list with the same ID of the covered person and the covered list will be deleted and replaced with the list of the covered person (lists with different list IDs will be merged together). If you check Complete coverage, all lists of the covered person will be moved. \nDivide, and then replace with a list of overriders.",
"sync__title": "Choose how to synchronize the list with {name}",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
"theme_black": "Black",
"theme_blue": "Blue",
"theme_blue2": "Purple Blue",
"theme_green": "Green",
"theme_grey": "Grey",
"theme_happy_new_year": "New Year",
"theme_mid_autumn": "Mid-Autumn",
"theme_ming": "Ming",
"theme_naruto": "Naruto",
"theme_orange": "Orange",
"theme_pink": "Pink",
"theme_purple": "Purple",
"theme_red": "Red",
"theme_yellow": "Yellow",
"user_api__btn_export": "Export",
"user_api__btn_import": "Import",
"user_api__btn_remove": "Remove",
"user_api__import_file": "Select music API script file",
"user_api__noitem": "There is nothing here...😲",
"user_api__note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.",
"user_api__readme": "Source writing instructions: ",
"user_api__title": "Custom Source Management"
}
+42
View File
@@ -0,0 +1,42 @@
import zh_cn from './zh-cn.json'
import zh_tw from './zh-tw.json'
import en_us from './en-us.json'
const langs = [
{
name: '简体中文',
locale: 'zh-cn',
alternate: 'zh-hans',
country: 'cn',
fallback: true,
message: zh_cn,
},
{
name: '繁体中文',
locale: 'zh-tw',
alternate: 'zh-hk',
country: 'cn',
message: zh_tw,
},
{
name: 'English',
locale: 'en-us',
country: 'us',
message: en_us,
},
]
const langList = []
const messages = {}
langs.forEach(item => {
langList.push({
name: item.name,
locale: item.locale,
})
messages[item.locale] = item.message
})
export {
langList,
messages,
}
+407
View File
@@ -0,0 +1,407 @@
{
"action": "操作",
"agree": "接受",
"audio_visualization": "音频可视化(实验性)",
"back": "返回",
"cancel_button_text": "我不",
"close": "关闭",
"comment__hot_load_error": "热门评论加载失败,点击尝试重新加载",
"comment__hot_loading": "热门评论加载中",
"comment__hot_title": "热门评论",
"comment__new_load_error": "最新评论加载失败,点击尝试重新加载",
"comment__new_loading": "最新评论加载中",
"comment__new_title": "最新评论",
"comment__no_content": "暂无评论",
"comment__refresh": "刷新评论",
"comment__show": "歌曲评论",
"comment__title": "{name} 的评论",
"confirm_button_text": "是的",
"copy_tip": "(点击复制)",
"date_format_hour": "{num}小时前",
"date_format_minute": "{num}分钟前",
"date_format_second": "{num}秒前",
"default": "默认",
"default_list": "试听列表",
"desktop_lyric__back": "返回",
"desktop_lyric__close": "关闭",
"desktop_lyric__font_decrease": "减小字体大小(右击可微调)",
"desktop_lyric__font_increase": "增加字体大小(右击可微调)",
"desktop_lyric__lock": "锁定歌词",
"desktop_lyric__lrc_active_zoom_off": "取消缩放当前播放的歌词",
"desktop_lyric__lrc_active_zoom_on": "缩放当前播放的歌词",
"desktop_lyric__opactiy_decrease": "增加透明度(右击可微调)",
"desktop_lyric__opactiy_increase": "减小透明度(右击可微调)",
"desktop_lyric__theme": "主题配色",
"desktop_lyric__unlock": "解锁歌词",
"desktop_lyric__win_top_off": "取消置顶歌词界面",
"desktop_lyric__win_top_on": "置顶歌词界面",
"download": "下载",
"download__all": "全部任务",
"download__error": "出错",
"download__finished": "下载完成",
"download__high_quality": "高品音质",
"download__lossless": "无损音质",
"download__multiple_tip": "已选择 {len} 首歌曲",
"download__multiple_tip2": "请选择要优先下载的音质",
"download__normal": "普通音质",
"download__not_available_tip": "该音质不可用",
"download__paused": "已暂停",
"download__progress": "进度",
"download__quality": "品质",
"download__runing": "正在下载",
"download__status": "状态",
"export": "导出",
"history_clear": "清空搜索历史",
"history_remove": "右击移除该历史",
"history_search": "历史搜索",
"import": "导入",
"leaderboard": "排行榜",
"list__add_to": "添加到...",
"list__collect": "收藏",
"list__copy_name": "复制歌曲名",
"list__download": "下载",
"list__export_part_desc": "选择列表文件保存位置",
"list__file": "定位文件",
"list__import_part_button_cancel": "不要啊",
"list__import_part_button_confirm": "覆盖掉",
"list__import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
"list__import_part_desc": "选择列表文件",
"list__load_failed": "啊,加载失败了😭",
"list__loading": "列表加载中...⏳",
"list__move_to": "移动到...",
"list__movedown": "下移",
"list__moveup": "上移",
"list__new_list_btn": "新建列表",
"list__new_list_input": "新列表...",
"list__pause": "暂停任务",
"list__play": "播放",
"list__play_later": "稍后播放",
"list__remove": "删除",
"list__remove_tip": "你真的想要移除 {name} 吗?",
"list__remove_tip_button": "是的 没错",
"list__rename": "重命名",
"list__search": "搜索",
"list__sort": "调整位置",
"list__source_detail": "歌曲详情页",
"list__start": "开始任务",
"list__sync": "更新",
"list_add__btn_title": "把该歌曲添加到 {name}",
"list_add__multiple_btn_title": "把这些歌曲添加到 {name}",
"list_add__multiple_title_add": "添加已选的 {num} 首歌曲到...",
"list_add__multiple_title_move": "移动已选的 {num} 首歌曲到...",
"list_add__title_first_add": "添加",
"list_add__title_first_move": "移动",
"list_add__title_last": "到...",
"list_sort_modal_by_album": "专辑名",
"list_sort_modal_by_down": "降序",
"list_sort_modal_by_field": "排序字段",
"list_sort_modal_by_name": "歌曲名",
"list_sort_modal_by_singer": "歌手名",
"list_sort_modal_by_source": "歌曲源",
"list_sort_modal_by_time": "时长",
"list_sort_modal_by_type": "排序类别",
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你确定要这么做吗?",
"lists__duplicate": "重复歌曲",
"lists__export": "导出",
"lists__export_part_desc": "选择列表文件保存位置",
"lists__import": "导入",
"lists__import_part_button_cancel": "不要啊",
"lists__import_part_button_confirm": "覆盖掉",
"lists__import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
"lists__import_part_desc": "选择列表文件",
"lists__new_list_btn": "新建列表",
"lists__new_list_input": "新列表...",
"lists__remove": "删除",
"lists__remove_tip": "你真的想要移除 {name} 吗?",
"lists__remove_tip_button": "是的 没错",
"lists__rename": "重命名",
"lists__sort_list": "排序歌曲",
"lists__sync": "更新",
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情:{detail}",
"load_list_file_error_title": "播放列表数据加载错误(建议到GitHub或加群反馈)",
"loding": "加载中...",
"love_list": "收藏",
"lyric__load_error": "歌词获取失败",
"lyric__select": "歌词文本选择",
"min": "最小化",
"music_album": "专辑名",
"music_duplicate": "重复歌曲",
"music_name": "歌曲名",
"music_singer": "艺术家",
"music_sort__btn_cancel": "取消",
"music_sort__btn_confirm": "确定",
"music_sort__input_tip": "请输入要调整到第几个位置",
"music_sort__title": "将 {name} 的位置调整到:",
"music_sort__title_multiple": "将已选的 {num} 首歌曲的位置调整到:",
"music_time": "时长",
"my_list": "我的列表",
"no_item": "列表竟然是空的...",
"not_agree": "不接受",
"pagination__next": "下一页",
"pagination__page": "第 {num} 页",
"pagination__prev": "上一页",
"player__add_music_to": "添加当前歌曲到...",
"player__buffering": "缓冲中...",
"player__desktop_lyric_lock": "右击锁定歌词",
"player__desktop_lyric_off": "关闭桌面歌词",
"player__desktop_lyric_on": "开启桌面歌词",
"player__desktop_lyric_unlock": "右击解锁歌词",
"player__end": "播放完毕",
"player__error": "音频加载出错,5 秒后切换下一首",
"player__geting_url": "歌曲链接获取中...",
"player__hide_detail_tip": "隐藏详情页(界面内右键双击可快速隐藏详情页)",
"player__loading": "音乐加载中...",
"player__music_album": "专辑名:",
"player__music_name": "歌曲名:",
"player__music_singer": "艺术家:",
"player__next": "下一首",
"player__pause": "暂停",
"player__pic_tip": "右击在“我的列表”定位当前播放的歌曲",
"player__play": "播放",
"player__play_toggle_mode_list": "顺序播放",
"player__play_toggle_mode_list_loop": "列表循环",
"player__play_toggle_mode_off": "禁用",
"player__play_toggle_mode_random": "列表随机",
"player__play_toggle_mode_single_loop": "单曲循环",
"player__playing": "播放中...",
"player__prev": "上一首",
"player__refresh_url": "URL过期,正在刷新URL...",
"player__stop": "暂停播放",
"player__volume": "当前音量:",
"search": "搜索",
"search__hot_search": "热门搜索",
"search__welcome": "搜我所想~~😉",
"setting": "设置",
"setting__about": "关于洛雪音乐",
"setting__backup": "备份与恢复",
"setting__backup_all": "所有数据(列表数据与设置数据)",
"setting__backup_all_export": "导出",
"setting__backup_all_export_desc": "选择备份保存位置",
"setting__backup_all_import": "导入",
"setting__backup_all_import_desc": "选择备份文件",
"setting__backup_other": "其他备份格式(目前不支持恢复此类备份文件)",
"setting__backup_other_export_dir": "选择文件保存位置",
"setting__backup_other_export_list_csv": "导出 CSV 格式的列表",
"setting__backup_other_export_list_text": "导出 TXT 格式的列表",
"setting__backup_other_export_list_text_confirm": "是否将所有列表合并为一个文件?",
"setting__backup_part": "部分数据(列表数据包括试听列表、收藏列表、用户自定义列表,设置数据不包括快捷键设置)",
"setting__backup_part_export_list": "导出列表",
"setting__backup_part_export_list_desc": "选择歌单保存位置",
"setting__backup_part_export_setting": "导出设置",
"setting__backup_part_export_setting_desc": "选择设置保存位置",
"setting__backup_part_import_list": "导入列表",
"setting__backup_part_import_list_desc": "选择列表文件",
"setting__backup_part_import_setting": "导入设置",
"setting__backup_part_import_setting_desc": "选择配置文件",
"setting__basic": "基本设置",
"setting__basic_animation": "弹出层随机动画",
"setting__basic_control_btn_position": "控制按钮位置",
"setting__basic_control_btn_position_left": "左边",
"setting__basic_control_btn_position_right": "右边",
"setting__basic_lang": "语言",
"setting__basic_lang_title": "软件显示的语言",
"setting__basic_show_animation": "显示切换动画",
"setting__basic_source": "音乐来源",
"setting__basic_source_status_failed": "初始化失败",
"setting__basic_source_status_initing": "初始化中",
"setting__basic_source_status_success": "初始化成功",
"setting__basic_source_temp": "临时接口(软件的某些功能不可用,建议测试接口不可用再使用本接口)",
"setting__basic_source_test": "测试接口(几乎软件的所有功能都可用)",
"setting__basic_source_title": "选择音乐来源",
"setting__basic_source_user_api_btn": "自定义源管理",
"setting__basic_sourcename": "音源名字",
"setting__basic_sourcename_alias": "别名",
"setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "选择音源名字类型",
"setting__basic_theme": "主题颜色",
"setting__basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
"setting__basic_window_size": "窗口尺寸",
"setting__basic_window_size_big": "大",
"setting__basic_window_size_huge": "巨大",
"setting__basic_window_size_larger": "较大",
"setting__basic_window_size_medium": "中",
"setting__basic_window_size_oversized": "超大",
"setting__basic_window_size_small": "小",
"setting__basic_window_size_smaller": "较小",
"setting__basic_window_size_title": "设置软件窗口尺寸",
"setting__click_copy": "点击复制",
"setting__click_open": "点击打开",
"setting__desktop_lyric": "桌面歌词设置",
"setting__desktop_lyric_always_on_top": "使歌词总是在其他窗口之上",
"setting__desktop_lyric_enable": "显示歌词",
"setting__desktop_lyric_font": "歌词字体",
"setting__desktop_lyric_font_default": "默认",
"setting__desktop_lyric_lock": "锁定歌词",
"setting__desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外",
"setting__download": "下载设置",
"setting__download_data_embed": "是否将以下内容嵌入到音频文件中",
"setting__download_embed_lyric": "歌词嵌入",
"setting__download_embed_pic": "封面嵌入",
"setting__download_enable": "是否启用下载功能",
"setting__download_lyric": "歌词下载",
"setting__download_lyric_format": "下载的歌词文件编码格式",
"setting__download_lyric_format_gbk": "GBK(在某些设备上出现中文乱码时可尝试选择此格式)",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "是否同时下载歌词文件",
"setting__download_name": "文件命名方式",
"setting__download_name1": "歌名 - 歌手",
"setting__download_name2": "歌手 - 歌名",
"setting__download_name3": "歌名",
"setting__download_name_title": "下载歌曲时的命名方式",
"setting__download_path": "下载路径",
"setting__download_path_change_btn": "更改",
"setting__download_path_label": "当前下载路径:",
"setting__download_path_open_label": "点击打开当前路径",
"setting__download_path_title": "下载歌曲保存的路径",
"setting__download_select_save_path": "选择歌曲保存路径",
"setting__download_use_other_source": "自动换源下载",
"setting__download_use_other_source_tip": "当无法从歌曲的原始源下载时,尝试切换到其他源下载,注:此功能不100%保证换源后的歌曲版本与原版一致",
"setting__hot_key": "快捷键设置",
"setting__hot_key_common_focus_search_input": "聚焦搜索框",
"setting__hot_key_common_min": "最小化程序",
"setting__hot_key_common_toggle_close": "退出程序",
"setting__hot_key_common_toggle_hide": "显示/隐藏程序",
"setting__hot_key_common_toggle_min": "最小化/还原程序",
"setting__hot_key_desktop_lyric_toggle_always_top": "桌面歌词置顶切换",
"setting__hot_key_desktop_lyric_toggle_lock": "桌面歌词锁定切换",
"setting__hot_key_desktop_lyric_toggle_visible": "开/关桌面歌词",
"setting__hot_key_global_title": "全局快捷键",
"setting__hot_key_local_title": "软件内快捷键",
"setting__hot_key_player_next": "下一首歌曲",
"setting__hot_key_player_prev": "上一首歌曲",
"setting__hot_key_player_toggle_play": "播放/暂停控制",
"setting__hot_key_player_volume_down": "减少音量",
"setting__hot_key_player_volume_mute": "静音切换",
"setting__hot_key_player_volume_up": "增加音量",
"setting__hot_key_tip_input": "请输入新的按键",
"setting__hot_key_unset_input": "未设置",
"setting__is_enable": "是否启用",
"setting__is_show": "是否显示",
"setting__list": "列表设置",
"setting__list_add_music_location_type": "添加歌曲到列表时的位置",
"setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "顶部",
"setting__list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
"setting__list_source": "显示歌曲源(仅对我的音乐分类有效)",
"setting__network": "网络设置",
"setting__network_proxy_host": "主机",
"setting__network_proxy_password": "密码",
"setting__network_proxy_port": "端口",
"setting__network_proxy_title": "HTTP代理设置(乱设置软件将无法联网)",
"setting__network_proxy_username": "用户名",
"setting__odc": "强迫症设置",
"setting__odc_clear_search_input": "离开搜索界面时清空搜索框",
"setting__odc_clear_search_list": "离开搜索界面时清空搜索列表",
"setting__other": "其他",
"setting__other_play_list_cache": "列表缓存管理",
"setting__other_play_list_cache_clear_btn": "清理列表缓存信息",
"setting__other_play_list_cache_tip": "我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理",
"setting__other_play_list_cache_tip_confirm": "我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理,是否确认清理?",
"setting__other_resource_cache": "资源缓存管理",
"setting__other_resource_cache_clear_btn": "清理资源缓存",
"setting__other_resource_cache_confirm": "我要清掉",
"setting__other_resource_cache_label": "软件已使用缓存大小:",
"setting__other_resource_cache_tip": "图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小",
"setting__other_resource_cache_tip_confirm": "涉及图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小,是否仍要清理?",
"setting__other_tray_theme": "托盘图标样式",
"setting__other_tray_theme_black": "黑色",
"setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色",
"setting__play": "播放设置",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
"setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文",
"setting__play_lyric_transition": "显示歌词翻译",
"setting__play_mediaDevice": "音频输出",
"setting__play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
"setting__play_mediaDevice_title": "选择声音输出的媒体设备",
"setting__play_quality": "优先播放320K品质的歌曲(如果支持)",
"setting__play_save_play_time": "记住播放进度",
"setting__play_task_bar": "在任务栏上显示当前歌曲播放进度",
"setting__search": "搜索设置",
"setting__search_focus_search_box": "启动时自动聚焦搜索框",
"setting__search_history": "显示历史搜索记录",
"setting__search_hot": "显示热门搜索",
"setting__sync": "数据同步",
"setting__sync_address": "同步服务地址:{address}",
"setting__sync_auth_code": "连接码:{code}",
"setting__sync_device": "已连接的设备:{devices}",
"setting__sync_enable": "启用同步功能(由于数据是明文传输,请在受信任的网络下使用)",
"setting__sync_port": "同步端口设置",
"setting__sync_port_tip": "请输入同步服务端口号",
"setting__sync_refresh_code": "刷新连接码",
"setting__sync_tip": "使用方式请看常见问题“同步功能”部分",
"setting__update": "软件更新",
"setting__update_checking": "检查更新中...",
"setting__update_current_label": "当前版本:",
"setting__update_downloading": "发现新版本并在努力下载中,请稍后...⏳",
"setting__update_init": "处理更新中...",
"setting__update_latest": "软件已是最新,尽情地体验吧~🥂",
"setting__update_latest_label": "最新版本:",
"setting__update_open_version_modal_btn": "打开更新窗口 🚀",
"setting__update_progress": "下载进度:",
"setting__update_unknown": "未知",
"song_list": "歌单",
"songlist__import_input_tip": "输入歌单链接或歌单ID",
"songlist__import_input_tip_1": "不支持跨源打开歌单,请确认要打开的歌单与当前歌单源是否对应",
"songlist__import_input_tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
"songlist__import_input_tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
"songlist__open_list": "打开{name}歌单",
"source_alias_all": "聚合大会",
"source_alias_bd": "小杜音乐",
"source_alias_kg": "小枸音乐",
"source_alias_kw": "小蜗音乐",
"source_alias_mg": "小蜜音乐",
"source_alias_tx": "小秋音乐",
"source_alias_wy": "小芸音乐",
"source_alias_xm": "小霞音乐",
"source_all": "聚合搜索",
"source_bd": "百度音乐",
"source_kg": "酷狗音乐",
"source_kw": "酷我音乐",
"source_mg": "咪咕音乐",
"source_tx": "企鹅音乐",
"source_wy": "网易音乐",
"source_xm": "虾米音乐",
"sync__merge_btn_local_remote": "本机列表 合并 远程列表",
"sync__merge_btn_remote_local": "远程列表 合并 本机列表",
"sync__merge_label": "合并",
"sync__merge_tip": "合并:",
"sync__merge_tip_desc": "将两边的列表合并到一起,相同的歌曲将被去掉(去掉的是被合并者的歌曲),不同的歌曲将被添加。",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip_desc": "“仅使用实时同步功能”将不修改双方的列表,仅实时同步操作;“取消同步”将直接断开双方的连接。",
"sync__overwrite": "完全覆盖",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本机列表 覆盖 远程列表",
"sync__overwrite_btn_none": "仅使用实时同步功能",
"sync__overwrite_btn_remote_local": "远程列表 覆盖 本机列表",
"sync__overwrite_label": "覆盖",
"sync__overwrite_tip": "覆盖:",
"sync__overwrite_tip_desc": "被覆盖者与覆盖者列表ID相同的列表将被删除后替换成覆盖者的列表(列表ID不同的列表将被合并到一起),若勾选完全覆盖,则被覆盖者的所有列表将被移除,然后替换成覆盖者的列表。",
"sync__title": "选择与 {name} 的列表同步方式",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
"theme_black": "黑灯瞎火",
"theme_blue": "蓝田生玉",
"theme_blue2": "清热版蓝",
"theme_green": "绿意盎然",
"theme_grey": "灰常美丽",
"theme_happy_new_year": "新年快乐",
"theme_mid_autumn": "月里嫦娥",
"theme_ming": "青出于黑",
"theme_naruto": "木叶之村",
"theme_orange": "橙黄橘绿",
"theme_pink": "粉装玉琢",
"theme_purple": "重斤球紫",
"theme_red": "热情似火",
"theme_yellow": "信口雌黄",
"user_api__btn_export": "导出",
"user_api__btn_import": "导入",
"user_api__btn_remove": "移除",
"user_api__import_file": "选择音乐API脚本文件",
"user_api__noitem": "这里竟然是空的 😲",
"user_api__note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
"user_api__readme": "源编写说明:",
"user_api__title": "自定义源管理"
}
+407
View File
@@ -0,0 +1,407 @@
{
"action": "操作",
"agree": "接受",
"audio_visualization": "音頻可視化(實驗性)",
"back": "返回",
"cancel_button_text": "取消",
"close": "關閉",
"comment__hot_load_error": "熱門評論加載失敗,點擊嘗試重新加載",
"comment__hot_loading": "熱門評論加載中",
"comment__hot_title": "熱門評論",
"comment__new_load_error": "最新評論加載失敗,點擊嘗試重新加載",
"comment__new_loading": "最新評論加載中",
"comment__new_title": "最新評論",
"comment__no_content": "暫無評論",
"comment__refresh": "刷新評論",
"comment__show": "歌曲評論",
"comment__title": "{name} 的評論",
"confirm_button_text": "是的",
"copy_tip": "(點擊複製)",
"date_format_hour": "{num}小時前",
"date_format_minute": "{num}分鐘前",
"date_format_second": "{num}秒前",
"default": "默認",
"default_list": "試聽列表",
"desktop_lyric__back": "返回",
"desktop_lyric__close": "關閉",
"desktop_lyric__font_decrease": "減小字體大小(右擊可微調)",
"desktop_lyric__font_increase": "增加字體大小(右擊可微調)",
"desktop_lyric__lock": "鎖定歌詞",
"desktop_lyric__lrc_active_zoom_off": "取消縮放當前播放的歌詞",
"desktop_lyric__lrc_active_zoom_on": "縮放當前播放的歌詞",
"desktop_lyric__opactiy_decrease": "增加透明度(右擊可微調)",
"desktop_lyric__opactiy_increase": "減小透明度(右擊可微調)",
"desktop_lyric__theme": "主題配色",
"desktop_lyric__unlock": "解鎖歌詞",
"desktop_lyric__win_top_off": "取消置頂歌詞界面",
"desktop_lyric__win_top_on": "置頂歌詞界面",
"download": "下載管理",
"download__all": "全部任務",
"download__error": "出錯",
"download__finished": "下載完成",
"download__high_quality": "高品音質",
"download__lossless": "無損音質",
"download__multiple_tip": "已選擇 {len} 首歌曲",
"download__multiple_tip2": "請選擇要優先下載的音質",
"download__normal": "普通音質",
"download__not_available_tip": "該音質不可用",
"download__paused": "已暫停",
"download__progress": "進度",
"download__quality": "品質",
"download__runing": "正在下載",
"download__status": "狀態",
"export": "導出",
"history_clear": "清空搜索歷史",
"history_remove": "右擊移除該歷史",
"history_search": "歷史搜索",
"import": "導入",
"leaderboard": "排行榜",
"list__add_to": "添加到...",
"list__collect": "收藏",
"list__copy_name": "複製歌曲名",
"list__download": "下載",
"list__export_part_desc": "選擇列表文件保存位置",
"list__file": "定位文件",
"list__import_part_button_cancel": "不要啊",
"list__import_part_button_confirm": "覆蓋掉",
"list__import_part_confirm": "導入的列表({importName})與本地列表({localName})的ID相同,是否覆蓋本地列表?",
"list__import_part_desc": "選擇列表文件",
"list__load_failed": "啊,加載失敗了😭",
"list__loading": "列表加載中...⏳",
"list__move_to": "移動到...",
"list__movedown": "下移",
"list__moveup": "上移",
"list__new_list_btn": "新建列表",
"list__new_list_input": "新列表...",
"list__pause": "暫停任務",
"list__play": "播放",
"list__play_later": "稍後播放",
"list__remove": "刪除",
"list__remove_tip": "你真的想要移除 {name} 嗎?",
"list__remove_tip_button": "是的 沒錯",
"list__rename": "重命名",
"list__search": "搜索",
"list__sort": "調整位置",
"list__source_detail": "歌曲詳情頁",
"list__start": "開始任務",
"list__sync": "更新",
"list_add__btn_title": "把該歌曲添加到 {name}",
"list_add__multiple_btn_title": "把這些歌曲添加到 {name}",
"list_add__multiple_title_add": "添加已選的 {num} 首歌曲到...",
"list_add__multiple_title_move": "添加移動已選的 {num} 首歌曲到...",
"list_add__title_first_add": "添加",
"list_add__title_first_move": "移動",
"list_add__title_last": "到...",
"list_sort_modal_by_album": "專輯名",
"list_sort_modal_by_down": "降序",
"list_sort_modal_by_field": "排序字段",
"list_sort_modal_by_name": "歌曲名",
"list_sort_modal_by_singer": "歌手名",
"list_sort_modal_by_source": "歌曲源",
"list_sort_modal_by_time": "時長",
"list_sort_modal_by_type": "排序類別",
"list_sort_modal_by_up": "升序",
"list_sort_modal_tip_confirm": "你確定要這麼做嗎?",
"lists__duplicate": "重複歌曲",
"lists__export": "導出",
"lists__export_part_desc": "選擇列表文件保存位置",
"lists__import": "導入",
"lists__import_part_button_cancel": "不要啊",
"lists__import_part_button_confirm": "覆蓋掉",
"lists__import_part_confirm": "導入的列表({importName})與本地列表({localName})的ID相同,是否覆蓋本地列表?",
"lists__import_part_desc": "選擇列表文件",
"lists__new_list_btn": "新建列表",
"lists__new_list_input": "新列表...",
"lists__remove": "刪除",
"lists__remove_tip": "你真的想要移除 {name} 嗎?",
"lists__remove_tip_button": "是的 沒錯",
"lists__rename": "重命名",
"lists__sort_list": "排序歌曲",
"lists__sync": "更新",
"load_list_file_error_detail": "我們已經幫你把舊的列表文件備份到{path}\n它以 JSON 格式存儲,你可以嘗試手動修復並恢復它\n\n錯誤詳情:{detail}",
"load_list_file_error_title": "播放列表數據加載錯誤",
"loding": "加載中...",
"love_list": "收藏列表",
"lyric__load_error": "歌詞獲取失敗",
"lyric__select": "歌詞文本選擇",
"min": "最小化",
"music_album": "專輯名",
"music_duplicate": "重複歌曲",
"music_name": "歌曲名",
"music_singer": "藝術家",
"music_sort__btn_cancel": "取消",
"music_sort__btn_confirm": "確定",
"music_sort__input_tip": "請輸入要調整到第幾個位置",
"music_sort__title": "將 {name} 的位置調整到:",
"music_sort__title_multiple": "將已選的 {num} 首歌曲的位置調整到:",
"music_time": "時長",
"my_list": "我的列表",
"no_item": "列表竟然是空的...",
"not_agree": "不接受",
"pagination__next": "下一頁",
"pagination__page": "第 {num} 頁",
"pagination__prev": "上一頁",
"player__add_music_to": "添加當前歌曲到...",
"player__album": "專輯名:",
"player__buffering": "緩衝中...",
"player__desktop_lyric_lock": "右擊鎖定歌詞",
"player__desktop_lyric_off": "關閉桌面歌詞",
"player__desktop_lyric_on": "開啟桌面歌詞",
"player__desktop_lyric_unlock": "右擊解鎖歌詞",
"player__end": "播放完畢",
"player__error": "音頻加載出錯,5 秒後切換下一首",
"player__geting_url": "歌曲鏈接獲取中...",
"player__hide_detail_tip": "隱藏詳情頁(界面內右鍵雙擊可快速隱藏詳情頁)",
"player__loading": "音樂加載中...",
"player__music_name": "歌曲名:",
"player__music_singer": "藝術家:",
"player__next": "下一首",
"player__pause": "暫停",
"player__pic_tip": "右擊在“我的列表”定位當前播放的歌曲",
"player__play": "播放",
"player__play_toggle_mode_list": "順序播放",
"player__play_toggle_mode_list_loop": "列表循環",
"player__play_toggle_mode_off": "禁用",
"player__play_toggle_mode_random": "列表隨機",
"player__play_toggle_mode_single_loop": "單曲循環",
"player__playing": "播放中...",
"player__prev": "上一首",
"player__refresh_url": "URL過期,正在刷新URL...",
"player__stop": "暫停播放",
"player__volume": "當前音量:",
"search": "搜索",
"search__hot_search": "熱門搜索",
"search__welcome": "搜我所想~~😉",
"setting": "設置",
"setting__about": "關於洛雪音樂",
"setting__backup": "備份與恢復",
"setting__backup_all": "所有數據(列表數據與設置數據)",
"setting__backup_all_export": "導出",
"setting__backup_all_export_desc": "選擇備份保存位置",
"setting__backup_all_import": "導入",
"setting__backup_all_import_desc": "選擇備份文件",
"setting__backup_other": "其他備份格式(目前不支持恢復此類備份文件)",
"setting__backup_other_export_dir": "選擇文件保存位置",
"setting__backup_other_export_list_csv": "導出 CSV 格式的列表",
"setting__backup_other_export_list_text": "導出 TXT 格式的列表",
"setting__backup_other_export_list_text_confirm": "是否將所有列表合併為一個文件?",
"setting__backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自定義列表,設置數據不包括快捷鍵設置)",
"setting__backup_part_export_list": "導出列表",
"setting__backup_part_export_list_desc": "選擇歌單保存位置",
"setting__backup_part_export_setting": "導出設置",
"setting__backup_part_export_setting_desc": "選擇設置保存位置",
"setting__backup_part_import_list": "導入列表",
"setting__backup_part_import_list_desc": "選擇列表文件",
"setting__backup_part_import_setting": "導入設置",
"setting__backup_part_import_setting_desc": "選擇配置文件",
"setting__basic": "基本設置",
"setting__basic_animation": "彈出層隨機動畫",
"setting__basic_control_btn_position": "控制按鈕位置",
"setting__basic_control_btn_position_left": "左邊",
"setting__basic_control_btn_position_right": "右邊",
"setting__basic_lang": "語言",
"setting__basic_lang_title": "軟件顯示的語言",
"setting__basic_show_animation": "顯示切換動畫",
"setting__basic_source": "音樂來源",
"setting__basic_source_status_failed": "初始化失敗",
"setting__basic_source_status_initing": "初始化中",
"setting__basic_source_status_success": "初始化成功",
"setting__basic_source_temp": "臨時接口(軟件的某些功能不可用,建議測試接口不可用再使用本接口)",
"setting__basic_source_test": "測試接口(幾乎軟件的所有功能都可用)",
"setting__basic_source_title": "選擇音樂來源",
"setting__basic_source_user_api_btn": "自定義源管理",
"setting__basic_sourcename": "音源名字",
"setting__basic_sourcename_alias": "別名",
"setting__basic_sourcename_real": "原名",
"setting__basic_sourcename_title": "選擇音源名字類型",
"setting__basic_theme": "主題顏色",
"setting__basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤",
"setting__basic_window_size": "窗口尺寸",
"setting__basic_window_size_big": "大",
"setting__basic_window_size_huge": "巨大",
"setting__basic_window_size_larger": "較大",
"setting__basic_window_size_medium": "中",
"setting__basic_window_size_oversized": "超大",
"setting__basic_window_size_small": "小",
"setting__basic_window_size_smaller": "較小",
"setting__basic_window_size_title": "設置軟件窗口尺寸",
"setting__click_copy": "點擊複製",
"setting__click_open": "點擊打開",
"setting__desktop_lyric": "桌面歌詞設置",
"setting__desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
"setting__desktop_lyric_enable": "顯示歌詞",
"setting__desktop_lyric_font": "歌詞字體",
"setting__desktop_lyric_font_default": "默認",
"setting__desktop_lyric_lock": "鎖定歌詞",
"setting__desktop_lyric_lock_screen": "不允許歌詞窗口拖出主屏幕之外",
"setting__download": "下載設置",
"setting__download_data_embed": "是否將以下內容嵌入到音頻文件中",
"setting__download_embed_lyric": "歌詞嵌入",
"setting__download_embed_pic": "封面嵌入",
"setting__download_enable": "是否啟用下載功能",
"setting__download_lyric": "歌詞下載",
"setting__download_lyric_format": "下載的歌詞文件編碼格式",
"setting__download_lyric_format_gbk": "GBK(在某些設備上出現中文亂碼時可嘗試選擇此格式)",
"setting__download_lyric_format_utf8": "UTF-8",
"setting__download_lyric_title": "是否同時下載歌詞文件",
"setting__download_name": "文件命名方式",
"setting__download_name1": "歌名 - 歌手",
"setting__download_name2": "歌手 - 歌名",
"setting__download_name3": "歌名",
"setting__download_name_title": "下載歌曲時的命名方式",
"setting__download_path": "下載路徑",
"setting__download_path_change_btn": "更改",
"setting__download_path_label": "當前下載路徑:",
"setting__download_path_open_label": "點擊打開當前路徑",
"setting__download_path_title": "下載歌曲保存的路徑",
"setting__download_select_save_path": "選擇歌曲保存路徑",
"setting__download_use_other_source": "自動換源下載",
"setting__download_use_other_source_tip": "當無法從歌曲的原始源下載時,嘗試切換到其他源下載,注:此功能不100%保證換源後的歌曲版本與原版一致",
"setting__hot_key": "快捷鍵設置",
"setting__hot_key_common_focus_search_input": "聚焦搜索框",
"setting__hot_key_common_min": "最小化程序",
"setting__hot_key_common_toggle_close": "退出程序",
"setting__hot_key_common_toggle_hide": "顯示/隱藏程序",
"setting__hot_key_common_toggle_min": "最小化/還原程序",
"setting__hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
"setting__hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
"setting__hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
"setting__hot_key_global_title": "全局快捷鍵",
"setting__hot_key_local_title": "軟件內快捷鍵",
"setting__hot_key_player_next": "下一首歌曲",
"setting__hot_key_player_prev": "上一首歌曲",
"setting__hot_key_player_toggle_play": "播放/暫停控制​​",
"setting__hot_key_player_volume_down": "減少音量",
"setting__hot_key_player_volume_mute": "靜音切換",
"setting__hot_key_player_volume_up": "增加音量",
"setting__hot_key_tip_input": "請輸入新的按鍵",
"setting__hot_key_unset_input": "未設置",
"setting__is_enable": "是否啟用",
"setting__is_show": "是否顯示",
"setting__list": "列表設置",
"setting__list_add_music_location_type": "添加歌曲到列表時的位置",
"setting__list_add_music_location_type_bottom": "底部",
"setting__list_add_music_location_type_top": "頂部",
"setting__list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
"setting__list_source": "顯示歌曲源(僅對我的音樂分類有效)",
"setting__network": "網絡設置",
"setting__network_proxy_host": "主機",
"setting__network_proxy_password": "密碼",
"setting__network_proxy_port": "端口",
"setting__network_proxy_title": "HTTP代理設置(亂設置軟件將無法聯網)",
"setting__network_proxy_username": "用戶名",
"setting__odc": "強迫症設置",
"setting__odc_clear_search_input": "離開搜索界面時清空搜索框",
"setting__odc_clear_search_list": "離開搜索界面時清空搜索列表",
"setting__other": "其他",
"setting__other_play_list_cache": "列表緩存管理",
"setting__other_play_list_cache_clear_btn": "清理列表緩存信息",
"setting__other_play_list_cache_tip": "我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理",
"setting__other_play_list_cache_tip_confirm": "我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理,是否確認清理?",
"setting__other_resource_cache": "資源緩存管理",
"setting__other_resource_cache_clear_btn": "清理資源緩存",
"setting__other_resource_cache_confirm": "我要清掉",
"setting__other_resource_cache_label": "軟件已使用緩存大小:",
"setting__other_resource_cache_tip": "圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小",
"setting__other_resource_cache_tip_confirm": "涉及圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小,是否仍要清理?",
"setting__other_tray_theme": "托盤圖標樣式",
"setting__other_tray_theme_black": "黑色",
"setting__other_tray_theme_native": "白色",
"setting__other_tray_theme_origin": "原色",
"setting__play": "播放設置",
"setting__play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
"setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文",
"setting__play_lyric_transition": "顯示歌詞翻譯",
"setting__play_mediaDevice": "音頻輸出",
"setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
"setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備",
"setting__play_quality": "優先播放320K品質的歌曲(如果支持)",
"setting__play_save_play_time": "記住播放進度",
"setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度",
"setting__search": "搜索設置",
"setting__search_focus_search_box": "啟動時自動聚焦搜索框",
"setting__search_history": "顯示歷史搜索記錄",
"setting__search_hot": "顯示熱門搜索",
"setting__sync": "數據同步",
"setting__sync_address": "同步服務地址:{address}",
"setting__sync_auth_code": "連接碼:{code}",
"setting__sync_device": "已連接的設備:{devices}",
"setting__sync_enable": "啟用同步功能(由於數據是明文傳輸,請在受信任的網絡下使用)",
"setting__sync_port": "同步端口設置",
"setting__sync_port_tip": "請輸入同步服務端口號",
"setting__sync_refresh_code": "刷新連接碼",
"setting__sync_tip": "使用方式請看常見問題“同步功能”部分",
"setting__update": "軟件更新",
"setting__update_checking": "檢查更新中...",
"setting__update_current_label": "當前版本:",
"setting__update_downloading": "發現新版本並在努力下載中,請稍後...⏳",
"setting__update_init": "處理更新中...",
"setting__update_latest": "軟件已是最新,盡情地體驗吧~🥂",
"setting__update_latest_label": "最新版本:",
"setting__update_open_version_modal_btn": "打開更新窗口 🚀",
"setting__update_progress": "下載進度:",
"setting__update_unknown": "未知",
"song_list": "歌單",
"songlist__import_input_tip": "輸入歌單鏈接或歌單ID",
"songlist__import_input_tip_1": "不支持跨源打開歌單,請確認要打開的歌單與當前歌單源是否對應",
"songlist__import_input_tip_2": "若遇到無法打開的歌單鏈接,歡迎反饋",
"songlist__import_input_tip_3": "酷狗源不支持用歌單ID打開,但支持酷狗碼打開",
"songlist__open_list": "打開{name}歌單",
"source_alias_all": "聚合大會",
"source_alias_bd": "小杜音樂",
"source_alias_kg": "小枸音樂",
"source_alias_kw": "小蝸音樂",
"source_alias_mg": "小蜜音樂",
"source_alias_tx": "小秋音樂",
"source_alias_wy": "小芸音樂",
"source_alias_xm": "小霞音樂",
"source_all": "聚合搜索",
"source_bd": "百度音樂",
"source_kg": "酷狗音樂",
"source_kw": "酷我音樂",
"source_mg": "咪咕音樂",
"source_tx": "企鵝音樂",
"source_wy": "網易音樂",
"source_xm": "蝦米音樂",
"sync__merge_btn_local_remote": "本機列表 合併 遠程列表",
"sync__merge_btn_remote_local": "遠程列表 合併 本機列表",
"sync__merge_label": "合併",
"sync__merge_tip": "合併:",
"sync__merge_tip_desc": "將兩邊的列表合併到一起,相同的歌曲將被去掉(去掉的是被合併者的歌曲),不同的歌曲將被添加。",
"sync__other_label": "其他",
"sync__other_tip": "其他:",
"sync__other_tip_desc": "“僅使用實時同步功能”將不修改雙方的列表,僅實時同步操作;“取消同步”將直接斷開雙方的連接。",
"sync__overwrite": "完全覆蓋",
"sync__overwrite_btn_cancel": "取消同步",
"sync__overwrite_btn_local_remote": "本機列表 覆蓋 遠程列表",
"sync__overwrite_btn_none": "僅使用實時同步功能",
"sync__overwrite_btn_remote_local": "遠程列表 覆蓋 本機列表",
"sync__overwrite_label": "覆蓋",
"sync__overwrite_tip": "覆蓋:",
"sync__overwrite_tip_desc": "被覆蓋者與覆蓋者列表ID相同的列表將被刪除後替換成覆蓋者的列表(列表ID不同的列表將被合併到一起),若勾選完全覆蓋,則被覆蓋者的所有列表將被移除,然後替換成覆蓋者的列表。",
"sync__title": "選擇與 {name} 的列表同步方式",
"tag__high_quality": "HQ",
"tag__lossless": "SQ",
"theme_black": "黑燈瞎火",
"theme_blue": "藍田生玉",
"theme_blue2": "清熱版藍",
"theme_green": "綠意盎然",
"theme_grey": "灰常美麗",
"theme_happy_new_year": "新年快樂",
"theme_mid_autumn": "月裡嫦娥",
"theme_ming": "青出於黑",
"theme_naruto": "木葉之村",
"theme_orange": "橙黃橘綠",
"theme_pink": "粉裝玉琢",
"theme_purple": "重斤球紫",
"theme_red": "熱情似火",
"theme_yellow": "信口雌黃",
"user_api__btn_export": "導出",
"user_api__btn_import": "導入",
"user_api__btn_remove": "移除",
"user_api__import_file": "選擇音樂API腳本文件",
"user_api__noitem": "這裡竟然是空的 😲",
"user_api__note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
"user_api__readme": "源編寫說明:",
"user_api__title": "自定義源管理"
}
+2 -2
View File
@@ -6,7 +6,7 @@
*/
const electron = require('electron')
const electronDebug = require('electron-debug')
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer')
const { default: installExtension, VUEJS3_DEVTOOLS } = require('electron-devtools-installer')
// Install `electron-debug` with `devtron`
electronDebug({
showDevTools: true,
@@ -15,7 +15,7 @@ electronDebug({
// Install `vue-devtools`
electron.app.on('ready', () => {
installExtension(VUEJS_DEVTOOLS)
installExtension(VUEJS3_DEVTOOLS)
.then(name => console.log(`Added Extension: ${name}`))
.catch(err => console.log('An error occurred: ', err))
})
+3 -2
View File
@@ -38,7 +38,7 @@ if (process.platform == 'linux') app.commandLine.appendSwitch('use-gl', 'desktop
app.commandLine.appendSwitch('wm-window-animations-disabled')
const { navigationUrlWhiteList } = require('../common/config')
const { navigationUrlWhiteList, themes } = require('../common/config')
const { getWindowSizeInfo, initSetting, updateSetting } = require('./utils')
const { isMac, isLinux, initHotKey } = require('../common/utils')
@@ -118,10 +118,11 @@ function createWindow() {
contextIsolation: false,
webSecurity: !isDev,
nodeIntegration: true,
spellcheck: false, // 禁用拼写检查器
},
})
global.modules.mainWindow.loadURL(winURL)
global.modules.mainWindow.loadURL(winURL + `?dt=${!!global.envParams.cmdParams.dt}&theme=${themes.find(t => t.id == global.appSetting.themeId)?.className ?? themes[0].className}`)
winEvent(global.modules.mainWindow)
// global.modules.mainWindow.webContents.openDevTools()
+46 -15
View File
@@ -156,14 +156,26 @@ const mergeList = (sourceListData, targetListData) => {
const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
for (const list of targetListData.userList) {
const targetList = userListDataObj[list.id]
if (targetList) {
targetList.list = handleMergeList(targetList, list, addMusicLocationType).list
targetListData.userList.forEach((list, index) => {
const targetUpdateTime = list?.locationUpdateTime ?? 0
const sourceList = userListDataObj[list.id]
if (sourceList) {
sourceList.list = handleMergeList(sourceList, list, addMusicLocationType).list
const sourceUpdateTime = sourceList?.locationUpdateTime ?? 0
if (targetUpdateTime >= sourceUpdateTime) return
// 调整位置
const [newList] = newListData.userList.splice(newListData.userList.findIndex(l => l.id == list.id), 1)
newList.locationUpdateTime = targetUpdateTime
newListData.userList.splice(index, 0, newList)
} else {
newListData.userList.push(list)
if (targetUpdateTime) {
newListData.userList.splice(index, 0, list)
} else {
newListData.userList.push(list)
}
}
}
})
return newListData
}
@@ -175,11 +187,14 @@ const overwriteList = (sourceListData, targetListData) => {
const userListDataObj = createUserListDataObj(sourceListData)
newListData.userList = [...sourceListData.userList]
for (const list of targetListData.userList) {
const targetList = userListDataObj[list.id]
if (targetList) continue
newListData.userList.push(list)
}
targetListData.userList.forEach((list, index) => {
if (userListDataObj[list.id]) return
if (list?.locationUpdateTime) {
newListData.userList.splice(index, 0, list)
} else {
newListData.userList.push(list)
}
})
return newListData
}
@@ -340,10 +355,26 @@ const handleMergeListDataFromSnapshot = async(socket, snapshot) => {
}
newUserList.push(newList)
}
for (const list of remoteListData.userList) {
if (removedListIds.has(list.id) || localUserListData[list.id]) continue
newUserList.push({ ...list })
}
remoteListData.userList.forEach((list, index) => {
if (removedListIds.has(list.id)) return
const remoteUpdateTime = list?.locationUpdateTime ?? 0
if (localUserListData[list.id]) {
const localUpdateTime = localUserListData[list.id]?.locationUpdateTime ?? 0
if (localUpdateTime >= remoteUpdateTime) return
// 调整位置
const [newList] = newUserList.splice(newUserList.findIndex(l => l.id == list.id), 1)
newList.locationUpdateTime = localUpdateTime
newUserList.splice(index, 0, newList)
} else {
if (remoteUpdateTime) {
newUserList.splice(index, 0, { ...list })
} else {
newUserList.push({ ...list })
}
}
})
newListData.userList = newUserList
setLocalList(newListData)
setRemotelList(socket, newListData)
+2 -1
View File
@@ -27,7 +27,7 @@ const setLyricsConfig = debounce(config => {
const winEvent = lyricWindow => {
// let bounds
// lyricWindow.on('close', event => {
// if (global.isQuitting || !global.appSetting.tray.isToTray || (!isWin && !global.isTrafficLightClose)) {
// if (global.isQuitting || !global.appSetting.tray.isShow || (!isWin && !global.isTrafficLightClose)) {
// lyricWindow.setProgressBar(-1)
// return
// }
@@ -116,6 +116,7 @@ const createWindow = () => {
contextIsolation: false,
webSecurity: !global.isDev,
nodeIntegration: true,
spellcheck: false, // 禁用拼写检查器
},
})
+1
View File
@@ -20,6 +20,7 @@ require('./data')
require('./lyric')
require('./musicUrl')
require('./systemFonts')
require('./wait')
// require('./kw_decodeLyric')
+3 -1
View File
@@ -8,16 +8,18 @@ mainHandle(ipcMainWindowNames.get_playlist, async(event, isIgnoredError = false)
return {
defaultList: electronStore_list.get('defaultList'),
loveList: electronStore_list.get('loveList'),
tempList: electronStore_list.get('tempList'),
userList: electronStore_list.get('userList'),
downloadList: getStore('downloadList').get('list'),
}
})
const handleSaveList = ({ defaultList, loveList, userList }) => {
const handleSaveList = ({ defaultList, loveList, userList, tempList }) => {
let data = {}
if (defaultList != null) data.defaultList = defaultList
if (loveList != null) data.loveList = loveList
if (userList != null) data.userList = userList
if (tempList != null) data.tempList = tempList
getStore('playList').set(data)
}
mainOn(ipcMainWindowNames.save_playlist, (event, { type, data }) => {
+1 -1
View File
@@ -1,4 +1,4 @@
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('../../common/ipc')
const { mainOn, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
mainOn(ipcMainWindowNames.set_lyric_info, (event, info) => {
+25
View File
@@ -0,0 +1,25 @@
const { mainOn, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc')
const timeoutMap = new Map()
mainHandle(ipcMainWindowNames.wait, (event, { time, id }) => {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
if (timeoutMap.has(id)) timeoutMap.delete(id)
resolve()
}, time)
timeoutMap.set(id, {
timeout,
resolve,
reject,
})
})
})
mainOn(ipcMainWindowNames.wait_cancel, (event, id) => {
if (!timeoutMap.has(id)) return
const timeout = timeoutMap.get(id)
timeoutMap.delete(id)
clearTimeout(timeout.timeout)
timeout.reject('cancelled')
})
+1 -1
View File
@@ -5,7 +5,7 @@ global.isTrafficLightClose = false // 是否点击软件上的关闭按钮关闭
module.exports = mainWindow => {
mainWindow.on('close', event => {
if (global.isQuitting || !global.appSetting.tray.isToTray || (!isWin && !global.isTrafficLightClose)) {
if (global.isQuitting || !global.appSetting.tray.isShow || (!isWin && !global.isTrafficLightClose)) {
mainWindow.setProgressBar(-1)
global.lx_event.winLyric.close()
return
+10 -1
View File
@@ -105,7 +105,7 @@ export default {
document.addEventListener('mousemove', this.handleMouseMove)
document.addEventListener('mouseup', this.handleMouseUp)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('mousemove', this.handleMouseMove)
document.removeEventListener('mouseup', this.handleMouseUp)
},
@@ -224,6 +224,15 @@ body {
opacity: .8;
}
body {
user-select: none;
height: 100vh;
box-sizing: border-box;
}
#root {
height: 100%;
}
#container {
padding: 8px;
box-sizing: border-box;
@@ -2,38 +2,38 @@
div(:class="$style.container")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated fadeOut")
div(:class="$style.btns" v-show="!isShowThemeList")
button(:class="$style.btn" @click="handleClose" :title="$t('desktopLyric.close')")
button(:class="$style.btn" @click="handleClose" :title="$t('desktop_lyric__close')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-close')
button(:class="$style.btn" @click="handleLock" :title="$t('desktopLyric.' + (lrcConfig.isLock ? 'unlock' : 'lock'))")
button(:class="$style.btn" @click="handleLock" :title="$t('desktop_lyric__' + (lrcConfig.isLock ? 'unlock' : 'lock'))")
svg(v-if="config.isLock" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-unlock')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-lock')
button(:class="$style.btn" :title="$t('desktopLyric.theme')" @click="isShowThemeList = true")
button(:class="$style.btn" :title="$t('desktop_lyric__theme')" @click="isShowThemeList = true")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-theme')
button(:class="$style.btn" @click="handleFontChange('increase', 10)" @contextmenu="handleFontChange('increase', 1)" :title="$t('desktopLyric.font_increase')")
button(:class="$style.btn" @click="handleFontChange('increase', 10)" @contextmenu="handleFontChange('increase', 1)" :title="$t('desktop_lyric__font_increase')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-font-increase')
button(:class="$style.btn" @click="handleFontChange('decrease', 10)" @contextmenu="handleFontChange('decrease', 1)" :title="$t('desktopLyric.font_decrease')")
button(:class="$style.btn" @click="handleFontChange('decrease', 10)" @contextmenu="handleFontChange('decrease', 1)" :title="$t('desktop_lyric__font_decrease')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-font-decrease')
button(:class="$style.btn" @click="handleOpactiyChange('increase', 10)" @contextmenu="handleOpactiyChange('increase', 2)" :title="$t('desktopLyric.opactiy_increase')")
button(:class="$style.btn" @click="handleOpactiyChange('increase', 10)" @contextmenu="handleOpactiyChange('increase', 2)" :title="$t('desktop_lyric__opactiy_increase')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-opactiy-increase')
button(:class="$style.btn" @click="handleOpactiyChange('decrease', 10)" @contextmenu="handleOpactiyChange('decrease', 2)" :title="$t('desktopLyric.opactiy_decrease')")
button(:class="$style.btn" @click="handleOpactiyChange('decrease', 10)" @contextmenu="handleOpactiyChange('decrease', 2)" :title="$t('desktop_lyric__opactiy_decrease')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-opactiy-decrease')
//- button(:class="$style.btn" v-text="lrcConfig.style.isZoomActiveLrc ? '取消缩放' : '缩放歌词'" @click="handleZoomLrc")
button(:class="$style.btn" @click="handleZoomLrc" :title="$t('desktopLyric.' + (lrcConfig.style.isZoomActiveLrc ? 'lrc_active_zoom_off' : 'lrc_active_zoom_on'))")
button(:class="$style.btn" @click="handleZoomLrc" :title="$t('desktop_lyric__' + (lrcConfig.style.isZoomActiveLrc ? 'lrc_active_zoom_off' : 'lrc_active_zoom_on'))")
svg(v-if="config.style.isZoomActiveLrc" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-vibrate-off')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-vibrate')
button(:class="$style.btn" @click="handleAlwaysOnTop" :title="$t('desktopLyric.' + (lrcConfig.isAlwaysOnTop ? 'win_top_off' : 'win_top_on'))")
button(:class="$style.btn" @click="handleAlwaysOnTop" :title="$t('desktop_lyric__' + (lrcConfig.isAlwaysOnTop ? 'win_top_off' : 'win_top_on'))")
svg(v-if="config.isAlwaysOnTop" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-top-off')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 24 24' space='preserve')
@@ -44,7 +44,7 @@ div(:class="$style.container")
div(:class="$style.themes" v-show="isShowThemeList")
ul(:class="$style.themeList")
li(:class="$style.btnBack")
button(:class="$style.btn" @click="isShowThemeList = false" :title="$t('desktopLyric.back')")
button(:class="$style.btn" @click="isShowThemeList = false" :title="$t('desktop_lyric__back')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='20px' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-back')
li(:class="[$style.themeItem, theme.className, lrcConfig.theme == theme.id ? $style.active : null]" v-for="theme in themes" @click="handleToggleTheme(theme)")
@@ -52,6 +52,7 @@ div(:class="$style.container")
<script>
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
import { toRaw } from 'vue'
export default {
props: {
@@ -113,7 +114,7 @@ export default {
},
methods: {
sendEvent() {
rendererSend(NAMES.winLyric.set_lyric_config, this.config)
rendererSend(NAMES.winLyric.set_lyric_config, toRaw(this.config))
},
handleClose() {
this.config.enable = false
+19 -9
View File
@@ -9,11 +9,12 @@ div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }, { [$style.
</template>
<script>
import { rendererOn, rendererSend, NAMES } from '../../../common/ipc'
import { scrollTo } from '../../../renderer/utils'
import { rendererOn, rendererSend, NAMES } from '@common/ipc'
import { scrollTo } from '@renderer/utils'
import Lyric from '@renderer/utils/lyric-font-player'
let cancelScrollFn = null
let delayScrollTimeout
export default {
props: {
@@ -122,10 +123,14 @@ export default {
immediate: true,
},
'lyric.line': {
handler(n) {
handler(n, o) {
if (n < 0) return
if (n == 0 && this.isSetedLines) return this.isSetedLines = false
this.handleScrollLrc()
if (o == null || n - o != 1) return this.handleScrollLrc()
delayScrollTimeout = setTimeout(() => {
delayScrollTimeout = null
this.handleScrollLrc(600)
}, 600)
},
immediate: true,
},
@@ -172,7 +177,7 @@ export default {
document.addEventListener('touchend', this.handleMouseMsUp)
rendererSend(NAMES.winLyric.get_lyric_info, 'info')
},
beforeDestroy() {
beforeUnmount() {
this.clearLyricScrollTimeout()
document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp)
@@ -229,7 +234,7 @@ export default {
handleResize() {
this.setProgressWidth()
},
handleScrollLrc() {
handleScrollLrc(duration = 300) {
if (!this.dom_lines.length) return
if (cancelScrollFn) {
cancelScrollFn()
@@ -237,7 +242,7 @@ export default {
}
if (this.lyricEvent.isStopScroll) return
let dom_p = this.dom_lines[this.lyric.line]
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0)
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.5 + dom_p.clientHeight / 2) : 0, duration)
},
handleLyricDown(target, x, y) {
if (target.classList.contains('font') ||
@@ -248,6 +253,10 @@ export default {
this.lyricEvent.msDownY = y
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
} else {
if (delayScrollTimeout) {
clearTimeout(delayScrollTimeout)
delayScrollTimeout = null
}
this.winEvent.isMsDown = true
this.winEvent.msDownX = x
this.winEvent.msDownY = y
@@ -332,7 +341,7 @@ export default {
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@lyric/assets/styles/layout.less';
.lyric {
text-align: center;
@@ -340,6 +349,7 @@ export default {
overflow: hidden;
font-size: 16px;
color: @color-theme-lyric;
contain: strict;
cursor: move;
:global {
@@ -453,7 +463,7 @@ export default {
.lrc-content {
&.active {
.translation {
font-size: 1em;
font-size: .94em;
}
span {
font-size: 1.2em;
+7 -6
View File
@@ -1,13 +1,14 @@
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context('./', true, /\.vue$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
export default app => {
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')))
const componentName = upperFirst(camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')))
Vue.component(componentName, componentConfig.default || componentConfig)
})
app.component(componentName, componentConfig.default || componentConfig)
})
}
+6 -8
View File
@@ -1,17 +1,15 @@
import Vue from 'vue'
import { createApp } from 'vue'
import i18n from './plugins/i18n'
import './components'
import mountComponents from './components'
import App from './App'
import '../common/error'
Vue.config.productionTip = false
new Vue({
i18n,
el: '#root',
render: h => h(App),
})
const app = createApp(App)
app.use(i18n)
mountComponents(app)
app.mount('#root')
+5 -5
View File
@@ -7,16 +7,16 @@
*/
// Lib imports
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '../../renderer/lang'
import { createI18n } from 'vue-i18n'
import { messages } from '@/lang'
Vue.use(VueI18n)
const i18n = window.i18n = new VueI18n({
const i18n = createI18n({
locale: 'zh-cn',
fallbackLocale: 'zh-cn',
messages,
})
window.i18n = i18n.global
export default i18n
+62 -727
View File
@@ -1,737 +1,52 @@
<template lang="pug">
#container(v-if="isProd && !isDT && !isLinux" :class="theme" @mouseenter="enableIgnoreMouseEvents" @mouseleave="dieableIgnoreMouseEvents")
#container
core-aside#left
#right
core-toolbar#toolbar
core-view#view
core-player#player
#view
core-view#view-container
core-play-bar#player
core-icons
material-version-modal(v-show="version.showModal")
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
material-sync-mode-modal(v-show="globalObj.sync.isShowSyncMode")
#container(v-else :class="theme")
core-aside#left
#right
core-toolbar#toolbar
core-view#view
core-player#player
core-icons
material-version-modal(v-show="version.showModal")
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
material-sync-mode-modal(v-show="globalObj.sync.isShowSyncMode")
core-version-modal
core-pact-modal
core-sync-mode-modal
core-play-detail
</template>
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
import { isLinux } from '../common/utils'
import music from './utils/music'
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils'
import { base as eventBaseName, sync as eventSyncName } from './event/names'
import apiSourceInfo from './utils/music/api-source-info'
import { initListPosition, initListPrevSelectId } from '@renderer/utils/data'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
const listThrottle = (fn, delay = 100) => {
let timer = null
let _data = {}
return function(data) {
Object.assign(_data, data)
if (timer) return
timer = setTimeout(() => {
timer = null
fn.call(this, _data)
_data = {}
}, delay)
}
}
import { useRefGetter, watch, onMounted } from '@renderer/utils/vueTools'
import useApp from '@renderer/core/useApp'
export default {
data() {
return {
isProd: process.env.NODE_ENV === 'production',
isDT: false,
isLinux,
globalObj: {
apiSource: null,
proxy: {},
isShowPact: false,
qualityList: {},
userApi: {
list: [],
status: false,
message: 'initing',
apis: {},
},
sync: {
enable: false,
isShowSyncMode: false,
deviceName: '',
},
},
updateTimeout: null,
envParams: {
nt: false,
},
}
},
computed: {
...mapGetters(['setting', 'theme', 'version', 'windowSizeActive']),
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
...mapGetters('download', {
downloadList: 'list',
downloadStatus: 'downloadStatus',
}),
...mapGetters('search', {
searchHistoryList: 'historyList',
}),
},
created() {
this.saveMyList = listThrottle(data => {
rendererSend(NAMES.mainWindow.save_playlist, {
type: 'myList',
data,
})
}, 300)
this.saveDownloadList = throttle(n => {
rendererSend(NAMES.mainWindow.save_playlist, {
type: 'downloadList',
data: n,
})
}, 1000)
this.saveSearchHistoryList = throttle(n => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'searchHistoryList',
data: n,
})
}, 500)
},
mounted() {
document.body.classList.add(this.isDT ? 'disableTransparent' : 'transparent')
window.eventHub.$emit(eventBaseName.bindKey)
this.init()
window.eventHub.$on(eventSyncName.handle_action_list, this.handleSyncAction)
window.eventHub.$on(eventSyncName.handle_sync_list, this.handleSyncList)
if (this.setting.sync.enable && this.setting.sync.port) {
rendererInvoke(NAMES.mainWindow.sync_enable, {
enable: this.setting.sync.enable,
port: this.setting.sync.port,
})
}
},
watch: {
setting: {
handler(n, o) {
saveSetting(n)
global.appSetting = n
},
deep: true,
},
defaultList: {
handler(n) {
this.saveMyList({ defaultList: n })
},
deep: true,
},
loveList: {
handler(n) {
this.saveMyList({ loveList: n })
},
deep: true,
},
userList: {
handler(n) {
this.saveMyList({ userList: n })
},
deep: true,
},
downloadList: {
handler(n) {
this.saveDownloadList(window.downloadListFull)
},
deep: true,
},
searchHistoryList(n) {
this.saveSearchHistoryList(n)
},
'globalObj.apiSource'(n, o) {
if (/^user_api/.test(n)) {
this.globalObj.qualityList = {}
this.globalObj.userApi.status = false
this.globalObj.userApi.message = 'initing'
} else {
this.globalObj.qualityList = music.supportQuality[n]
}
if (o === null) return
rendererInvoke(NAMES.mainWindow.set_user_api, n).catch(err => {
console.log(err)
let api = apiSourceInfo.find(api => !api.disabled)
if (api) this.globalObj.apiSource = api.id
})
if (n != this.setting.apiSource) {
this.setSetting(Object.assign({}, this.setting, {
apiSource: n,
}))
}
},
'globalObj.proxy.enable'(n, o) {
if (n != this.setting.network.proxy.enable) {
this.setSetting({
...this.setting,
network: {
...this.setting.network,
proxy: {
...this.setting.network.proxy,
enable: n,
},
},
setup() {
const theme = useRefGetter('theme')
const dom_root = document.getElementById('root')
watch(theme, (val) => {
dom_root.className = val
})
dom_root.className = theme.value
useApp()
onMounted(() => {
// 隐藏等待界面
/* inited.value = true
const dom_mask = document.getElementById('waiting-mask')
if (dom_mask) {
dom_mask.addEventListener('transitionend', () => {
dom_mask.parentNode.removeChild(dom_mask)
})
}
},
'windowSizeActive.fontSize'(n) {
document.documentElement.style.fontSize = n
},
'setting.isShowAnimation': {
handler(n) {
if (n) {
if (document.body.classList.contains('disableAnimation')) {
document.body.classList.remove('disableAnimation')
}
} else {
if (!document.body.classList.contains('disableAnimation')) {
document.body.classList.add('disableAnimation')
}
}
},
immediate: true,
},
},
methods: {
...mapActions(['getVersionInfo']),
...mapMutations(['setNewVersion', 'setVersionModalVisible', 'setDownloadProgress', 'setSetting', 'setDesktopLyricConfig']),
...mapMutations('list', {
list_initList: 'initList',
list_setList: 'setList',
list_listAdd: 'listAdd',
list_listMove: 'listMove',
list_listAddMultiple: 'listAddMultiple',
list_listMoveMultiple: 'listMoveMultiple',
list_listRemove: 'listRemove',
list_listRemoveMultiple: 'listRemoveMultiple',
list_listClear: 'listClear',
list_updateMusicInfo: 'updateMusicInfo',
list_createUserList: 'createUserList',
list_removeUserList: 'removeUserList',
list_setUserListName: 'setUserListName',
list_moveupUserList: 'moveupUserList',
list_movedownUserList: 'movedownUserList',
list_setMusicPosition: 'setMusicPosition',
list_setSyncListData: 'setSyncListData',
}),
...mapMutations('download', ['updateDownloadList']),
...mapMutations('search', {
setSearchHistoryList: 'setHistory',
}),
...mapMutations('player', {
setPlayList: 'setList',
}),
...mapActions('songList', ['getListDetailAll']),
init() {
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
const asyncTask = []
asyncTask.push(rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit))
document.body.addEventListener('click', this.handleBodyClick, true)
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
// this.showUpdateModal(true)
// console.log(info)
this.setVersionModalVisible({ isDownloading: true })
this.getVersionInfo().catch(() => ({
version: info.version,
desc: info.releaseNotes,
})).then(body => {
// console.log(body)
this.setNewVersion(body)
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
})
})
rendererOn(NAMES.mainWindow.update_error, (event, err) => {
// console.log(err)
this.clearUpdateTimeout()
this.setVersionModalVisible({ isError: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn(NAMES.mainWindow.update_progress, (event, progress) => {
// console.log(progress)
this.setDownloadProgress(progress)
})
rendererOn(NAMES.mainWindow.update_downloaded, info => {
// console.log(info)
this.clearUpdateTimeout()
this.setVersionModalVisible({ isDownloaded: true })
this.$nextTick(() => {
this.showUpdateModal()
})
})
rendererOn(NAMES.mainWindow.update_not_available, (event, info) => {
this.clearUpdateTimeout()
this.setNewVersion({
version: info.version,
desc: info.releaseNotes,
})
this.setVersionModalVisible({ isLatestVer: true })
})
rendererOn(NAMES.mainWindow.set_config, (event, config) => {
// console.log(config)
// this.setDesktopLyricConfig(config)
// console.log('set_config', JSON.stringify(this.setting) === JSON.stringify(config))
this.setSetting(Object.assign({}, this.setting, config))
window.eventHub.$emit(eventBaseName.set_config, config)
})
// 更新超时定时器
this.updateTimeout = setTimeout(() => {
this.updateTimeout = null
this.setVersionModalVisible({ isTimeOut: true })
this.$nextTick(() => {
this.showUpdateModal()
})
}, 60 * 30 * 1000)
this.listenEvent()
asyncTask.push(this.initData())
asyncTask.push(this.initUserApi())
this.globalObj.sync.enable = this.setting.sync.enable
this.globalObj.apiSource = this.setting.apiSource
if (/^user_api/.test(this.setting.apiSource)) {
rendererInvoke(NAMES.mainWindow.set_user_api, this.setting.apiSource)
} else {
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
}
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
window.globalObj = this.globalObj
// 初始化音乐sdk
asyncTask.push(music.init())
Promise.all(asyncTask).then(() => {
this.handleInitEnvParamSearch()
this.handleInitEnvParamPlay()
})
},
enableIgnoreMouseEvents() {
if (this.isDT) return
rendererSend(NAMES.mainWindow.set_ignore_mouse_events, false)
// console.log('content enable')
},
dieableIgnoreMouseEvents() {
if (this.isDT) return
// console.log('content disable')
rendererSend(NAMES.mainWindow.set_ignore_mouse_events, true)
},
initData() { // 初始化数据
return Promise.all([
this.initMyList(), // 初始化播放列表
this.initSearchHistoryList(), // 初始化搜索历史列表
initListPosition(), // 列表位置记录
initListPrevSelectId(), // 上次选中的列表记录
])
// this.initDownloadList() // 初始化下载列表
},
initMyList() {
return getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
if (!defaultList) defaultList = this.defaultList
if (!loveList) loveList = this.loveList
if (userList) {
let needSave = false
const getListId = id => id.includes('.') ? getListId(id.substring(0, id.lastIndexOf('_'))) : id
userList.forEach(l => {
if (!l.id.includes('__') || l.source) return
let [source, id] = l.id.split('__')
id = getListId(id)
l.source = source
l.sourceListId = id
if (!needSave) needSave = true
})
if (needSave) this.saveMyList({ userList })
} else {
userList = this.userList
}
if (!defaultList.list) defaultList.list = []
if (!loveList.list) loveList.list = []
this.list_initList({ defaultList, loveList, userList })
this.initDownloadList(downloadList) // 初始化下载列表
this.initPlayInfo()
})
},
initDownloadList(downloadList) {
if (downloadList) {
downloadList = downloadList.filter(item => item && item.key && item.musicInfo)
for (const item of downloadList) {
if (item.name == null) {
item.name = `${item.musicInfo.name} - ${item.musicInfo.singer}`
item.songmid = item.musicInfo.songmid
}
if (item.status == this.downloadStatus.RUN || item.status == this.downloadStatus.WAITING) {
item.status = this.downloadStatus.PAUSE
item.statusText = '暂停下载'
}
}
this.updateDownloadList(downloadList)
}
},
initSearchHistoryList() {
rendererInvoke(NAMES.mainWindow.get_data, 'searchHistoryList').then(historyList => {
if (historyList == null) {
historyList = []
rendererSend(NAMES.mainWindow.save_data, { path: 'searchHistoryList', data: historyList })
} else {
this.setSearchHistoryList(historyList)
}
})
},
initPlayInfo() {
rendererInvoke(NAMES.mainWindow.get_data, 'playInfo').then(info => {
// console.log(info, window.allList)
window.restorePlayInfo = null
if (!info) return
if (info.index < 0) return
if (info.listId) {
if (info.listId == 'download') {
const list = this.downloadList
// console.log(list)
if (!list || !list[info.index]) return
info.list = list
} else {
const list = window.allList[info.listId]
// console.log(list)
if (!list || !list.list[info.index]) return
info.list = list.list
}
}
if (!info.list || !info.list[info.index]) return
window.restorePlayInfo = info
this.setPlayList({
list: {
list: info.list,
id: info.listId,
},
index: info.index,
})
})
},
initUserApi() {
return Promise.all([
rendererOn(NAMES.mainWindow.user_api_status, (event, { status, message, apiInfo }) => {
// console.log(apiInfo)
this.globalObj.userApi.status = status
this.globalObj.userApi.message = message
if (status) {
if (apiInfo.id === this.setting.apiSource) {
let apis = {}
let qualitys = {}
for (const [source, { actions, type, qualitys: sourceQualitys }] of Object.entries(apiInfo.sources)) {
if (type != 'music') continue
apis[source] = {}
for (const action of actions) {
switch (action) {
case 'musicUrl':
apis[source].getMusicUrl = (songInfo, type) => {
const requestKey = `request__${Math.random().toString().substring(2)}`
return {
canceleFn() {
rendererInvoke(NAMES.mainWindow.request_user_api_cancel, requestKey)
},
promise: rendererInvoke(NAMES.mainWindow.request_user_api, {
requestKey,
data: {
source: source,
action: 'musicUrl',
info: {
type,
musicInfo: songInfo,
},
},
}).then(res => {
// console.log(res)
if (!/^https?:/.test(res.data.url)) return Promise.reject(new Error('Get url failed'))
return { type, url: res.data.url }
}).catch(err => {
console.log(err.message)
return Promise.reject(err)
}),
}
}
break
default:
break
}
}
qualitys[source] = sourceQualitys
}
this.globalObj.qualityList = qualitys
this.globalObj.userApi.apis = apis
}
}
}),
rendererInvoke(NAMES.mainWindow.get_user_api_list).then(res => {
// console.log(res)
if (![...apiSourceInfo.map(s => s.id), ...res.map(s => s.id)].includes(this.setting.apiSource)) {
console.warn('reset api')
let api = apiSourceInfo.find(api => !api.disabled)
if (api) this.globalObj.apiSource = api.id
}
this.globalObj.userApi.list = res
}),
])
},
showUpdateModal() {
(this.version.newVersion && this.version.newVersion.history
? Promise.resolve(this.version.newVersion)
: this.getVersionInfo().then(body => {
this.setNewVersion(body)
return body
})
).catch(() => {
if (this.version.newVersion) return this.version.newVersion
this.setVersionModalVisible({ isUnknow: true })
let result = {
version: '0.0.0',
desc: null,
}
this.setNewVersion(result)
return result
}).then(result => {
if (result.version == '0.0.0') return this.setVersionModalVisible({ isUnknow: true, isShow: true })
if (compareVer(this.version.version, result.version) != -1) return this.setVersionModalVisible({ isLatestVer: true })
if (result.version === this.setting.ignoreVersion) return
// console.log(this.version)
this.$nextTick(() => {
this.setVersionModalVisible({ isShow: true })
})
})
},
clearUpdateTimeout() {
if (!this.updateTimeout) return
clearTimeout(this.updateTimeout)
this.updateTimeout = null
},
handleBodyClick(event) {
if (event.target.tagName != 'A') return
if (event.target.host == window.location.host) return
event.preventDefault()
if (/^https?:\/\//.test(event.target.href)) openUrl(event.target.href)
},
handleEnvParamsInit(envParams) {
this.envParams = envParams
this.isDT = this.envParams.dt
if (this.isDT) {
document.body.classList.remove('transparent')
document.body.classList.add('disableTransparent')
}
if (this.isProd && !this.isDT && !this.isLinux) {
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
this.handleInitEnvParamSearch()
this.handleInitEnvParamPlay()
},
// 处理启动参数 search
handleInitEnvParamSearch() {
if (this.envParams.search == null) return
this.$router.push({
path: 'search',
query: {
text: this.envParams.search,
},
})
},
// 处理启动参数 play
handleInitEnvParamPlay() {
if (this.envParams.play == null || typeof this.envParams.play != 'string') return
// -play="source=kw&link=链接、ID"
// -play="source=myList&name=名字"
// -play="source=myList&name=名字&index=位置"
const params = parseUrlParams(this.envParams.play)
if (params.type != 'songList') return
this.handlePlaySongList(params)
},
handlePlaySongList(params) {
switch (params.source) {
case 'myList':
if (params.name != null) {
let targetList
const lists = Object.values(window.allList)
for (const list of lists) {
if (list.name === params.name) {
targetList = list
break
}
}
if (!targetList) return
this.setPlayList({
list: {
list: targetList.list,
id: targetList.id,
},
index: this.getListPlayIndex(targetList.list, params.index),
})
}
break
case 'kw':
case 'kg':
case 'tx':
case 'mg':
case 'wy':
this.playSongListDetail(params.source, params.link, params.index)
break
}
},
async playSongListDetail(source, link, playIndex) {
if (link == null) return
let list
try {
list = await this.getListDetailAll({ source, id: decodeURIComponent(link) })
} catch (err) {
console.log(err)
}
this.setPlayList({
list: {
list,
id: null,
},
index: this.getListPlayIndex(list, playIndex),
})
},
getListPlayIndex(list, index) {
if (index == null) {
index = 1
} else {
index = parseInt(index)
if (Number.isNaN(index)) {
index = 1
} else {
if (index < 1) index = 1
else if (index > list.length) index = list.length
}
}
return index - 1
},
listenEvent() {
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
},
unlistenEvent() {
window.eventHub.$off('key_escape_down', this.handle_key_esc_down)
},
handle_key_esc_down({ event }) {
if (event.repeat) return
if (event.target.tagName != 'INPUT' || event.target.classList.contains('ignore-esc')) return
event.target.value = ''
event.target.blur()
},
handleSyncAction({ action, data }) {
if (typeof data == 'object') data.isSync = true
// console.log(action, data)
switch (action) {
case 'set_list':
this.list_setList(data)
break
case 'list_add':
this.list_listAdd(data)
break
case 'list_move':
this.list_listMove(data)
break
case 'list_add_multiple':
this.list_listAddMultiple(data)
break
case 'list_move_multiple':
this.list_listMoveMultiple(data)
break
case 'list_remove':
this.list_listRemove(data)
break
case 'list_remove_multiple':
this.list_listRemoveMultiple(data)
break
case 'list_clear':
this.list_listClear(data)
break
case 'update_music_info':
this.list_updateMusicInfo(data)
break
case 'create_user_list':
this.list_createUserList(data)
break
case 'remove_user_list':
this.list_removeUserList(data)
break
case 'set_user_list_name':
this.list_setUserListName(data)
break
case 'moveup_user_list':
this.list_moveupUserList(data)
break
case 'movedown_user_list':
this.list_movedownUserList(data)
break
case 'set_music_position':
this.list_setMusicPosition(data)
break
default:
break
}
},
handleSyncList({ action, data }) {
switch (action) {
case 'getData':
global.eventHub.$emit(eventSyncName.send_sync_list, {
action: 'getData',
data: {
defaultList: this.defaultList,
loveList: this.loveList,
userList: this.userList,
},
})
break
case 'setData':
this.list_setSyncListData(data)
break
case 'selectMode':
this.globalObj.sync.deviceName = data.deviceName
this.globalObj.sync.isShowSyncMode = true
break
case 'closeSelectMode':
this.globalObj.sync.isShowSyncMode = false
break
}
},
},
beforeDestroy() {
this.clearUpdateTimeout()
this.unlistenEvent()
if (this.isProd) {
document.body.removeEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.removeEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
document.body.removeEventListener('click', this.handleBodyClick)
window.eventHub.$emit(eventBaseName.unbindKey)
dom_mask.classList.add('hide')
} */
dom_root.style.display = 'block'
})
},
}
</script>
<style lang="less">
@import './assets/styles/index.less';
@import './assets/styles/layout.less';
@@ -745,6 +60,16 @@ body {
height: 100vh;
box-sizing: border-box;
}
#root {
height: 100%;
position: relative;
overflow: hidden;
color: @color-theme_2-font;
background: @color-theme-bgimg @color-theme-bgposition no-repeat;
background-size: @color-theme-bgsize;
transition: background-color @transition-theme;
background-color: @color-theme;
}
.disableAnimation * {
transition: none !important;
@@ -753,11 +78,22 @@ body {
.transparent {
padding: @shadow-app;
#container {
#waiting-mask {
border-radius: @radius-border;
left: @shadow-app;
right: @shadow-app;
top: @shadow-app;
bottom: @shadow-app;
}
#root {
box-shadow: 0 0 @shadow-app rgba(0, 0, 0, 0.5);
border-radius: @radius-border;
background-color: transparent;
}
#container {
border-radius: @radius-border;
background-color: transparent;
}
}
.disableTransparent {
background-color: #fff;
@@ -776,12 +112,6 @@ body {
position: relative;
display: flex;
height: 100%;
overflow: hidden;
color: @color-theme_2-font;
background: @color-theme-bgimg @color-theme-bgposition no-repeat;
background-size: @color-theme-bgsize;
transition: background-color @transition-theme;
background-color: @color-theme;
}
#left {
@@ -804,12 +134,17 @@ body {
flex: none;
}
#view {
position: relative;
flex: auto;
display: flex;
min-height: 0;
}
#view-container {
flex: auto;
}
each(@themes, {
#container.@{value} {
#root.@{value} {
color: ~'@{color-@{value}-theme_2-font}';
background-color: ~'@{color-@{value}-theme}';
background-image: ~'@{color-@{value}-theme-bgimg}';
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 447.942 447.943">
<path fill="#5ed698" d="M203.806.482c-19.668-3.346-35.76 11.139-35.76 31.086v206.166c-11.642-4.271-24.165-6.725-37.281-6.725-59.905 0-108.469 48.566-108.469 108.473 0 59.903 48.564 108.461 108.469 108.461 34.141 0 64.54-15.82 84.406-40.482l-49.658-49.664c-15.116-15.112-11.708-28.901-9.542-34.14 2.166-5.233 9.514-17.4 30.883-17.4h18.082v-56.885c0-21.132 14.617-38.862 34.266-43.745.032-44.373.032-81.808.032-81.808 140.147 0 131.724 83.974 115.325 132.196-6.42 18.884-2.601 22.05 10.893 7.354C536.473 77.106 298.38 16.566 203.806.482z"/>
<path fill="#4daf7c" d="M301.061 223.876h-50.994c-3.911 0-7.574.95-10.889 2.523-8.616 4.09-14.615 12.798-14.615 22.973v76.51h-37.708c-14.082 0-17.428 8.071-7.466 18.029l46.893 46.898 31.25 31.246a25.424 25.424 0 0 0 18.033 7.474c6.523 0 13.052-2.484 18.029-7.474l78.152-78.145c9.951-9.958 6.608-18.029-7.47-18.029h-37.71v-76.51c.001-14.078-11.42-25.495-25.505-25.495z"/>
</svg>

After

Width:  |  Height:  |  Size: 1013 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

+60 -2
View File
@@ -33,6 +33,9 @@ html {
user-select: none;
}
.thead {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
table {
width: 100%;
border-spacing: 0;
@@ -44,7 +47,6 @@ table {
text-align: left;
line-height: 38px;
padding: 0 6px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
tbody {
tr {
@@ -231,8 +233,64 @@ input, textarea {
}
}
.gap-left {
+ .gap-left {
margin-left: 20px;
}
}
.gap-top {
&.top {
margin-top: 25px;
}
+ .gap-top {
margin-top: 10px;
}
}
/*
#waiting-mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #fff;
z-index: 999999;
transition: all .8s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
-webkit-app-region: drag;
background-color: @color-theme;
&.hide {
opacity: 0;
}
}
#logo-path-1 {fill: #5ed698; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
#logo-path-2 {fill: #4daf7c; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
each(@themes, {
#container.@{value} {
body.@{value} #waiting-mask {
background-color: ~'@{color-@{value}-theme_2-background_1}';
#logo-path-1 {fill: ~'@{color-@{value}-btn}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
#logo-path-2 {fill: ~'@{color-@{value}-theme}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
}
}) */
// each(@themes, {
// body.@{value} #waiting-mask {
// background-color: ~'@{color-@{value}-theme}';
// #logo-path-1 {fill: ~'@{color-@{value}-theme_2-background_1}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
// #logo-path-2 {fill: ~'@{color-@{value}-theme-font-label}'; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));}
// }
// })
each(@themes, {
#root.@{value} {
button, input, textarea, a {
color: ~'@{color-@{value}-theme_2-font}';
}
+43 -43
View File
@@ -20,7 +20,7 @@
@color-theme_2-hover: fadeout(lighten(@color-theme, 10%), 80%);
@color-theme_2-active: fadeout(lighten(@color-theme, 10%), 70%);
@color-theme_2-font:desaturate( darken(@color-theme_2, 70%), 90%);
@color-theme_2-font-label: fadeout(@color-theme_2-font, 50%);
@color-theme_2-font-label: fadeout(@color-theme_2-font, 55%);
// @color-theme_2-line: transparent;
@color-theme_2-line: lighten(@color-theme, 35%);
@color-reply-floor: fadeout(@color-theme, 95%);
@@ -91,7 +91,7 @@
@color-green-theme_2-hover: fadeout(lighten(@color-green-theme, 10%), 80%);
@color-green-theme_2-active: fadeout(lighten(@color-green-theme, 10%), 70%);
@color-green-theme_2-font: darken(desaturate(@color-green-theme_2, 70%), 90%);
@color-green-theme_2-font-label: fadeout(@color-green-theme_2-font, 50%);
@color-green-theme_2-font-label: fadeout(@color-green-theme_2-font, 55%);
// @color-green-theme_2-line: transparent;
@color-green-theme_2-line: lighten(@color-green-theme, 45%);
@color-green-reply-floor: fadeout(@color-green-theme, 95%);
@@ -151,7 +151,7 @@
@color-yellow-theme_2-hover: fadeout(lighten(@color-yellow-theme, 10%), 70%);
@color-yellow-theme_2-active: fadeout(lighten(@color-yellow-theme, 10%), 60%);
@color-yellow-theme_2-font: darken(desaturate(@color-yellow-theme_2, 70%), 90%);
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 50%);
@color-yellow-theme_2-font-label: fadeout(@color-yellow-theme_2-font, 55%);
// @color-yellow-theme_2-line: transparent;
@color-yellow-theme_2-line: lighten(@color-yellow-theme, 28%);
@color-yellow-reply-floor: fadeout(@color-yellow-theme, 94%);
@@ -210,7 +210,7 @@
@color-orange-theme_2-hover: fadeout(darken(@color-orange-theme, 5%), 80%);
@color-orange-theme_2-active: fadeout(darken(@color-orange-theme, 5%), 70%);
@color-orange-theme_2-font: darken(desaturate(@color-orange-theme_2, 70%), 90%);
@color-orange-theme_2-font-label: fadeout(@color-orange-theme_2-font, 50%);
@color-orange-theme_2-font-label: fadeout(@color-orange-theme_2-font, 55%);
// @color-orange-theme_2-line: transparent;
@color-orange-theme_2-line: lighten(@color-orange-theme, 36%);
@color-orange-reply-floor: fadeout(@color-orange-theme, 95%);
@@ -269,7 +269,7 @@
@color-blue-theme_2-hover: fadeout(lighten(@color-blue-theme, 10%), 80%);
@color-blue-theme_2-active: fadeout(lighten(@color-blue-theme, 10%), 70%);
@color-blue-theme_2-font: darken(desaturate(@color-blue-theme_2, 70%), 90%);
@color-blue-theme_2-font-label: fadeout(@color-blue-theme_2-font, 50%);
@color-blue-theme_2-font-label: fadeout(@color-blue-theme_2-font, 55%);
// @color-blue-theme_2-line: transparent;
@color-blue-theme_2-line: lighten(@color-blue-theme, 42%);
@color-blue-reply-floor: fadeout(@color-blue-theme, 95%);
@@ -328,7 +328,7 @@
@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(desaturate(@color-red-theme_2, 70%), 90%);
@color-red-theme_2-font-label: fadeout(@color-red-theme_2-font, 50%);
@color-red-theme_2-font-label: fadeout(@color-red-theme_2-font, 55%);
// @color-red-theme_2-line: transparent;
@color-red-theme_2-line: lighten(@color-red-theme, 42%);
@color-red-reply-floor: fadeout(@color-red-theme, 95%);
@@ -389,7 +389,7 @@
@color-pink-theme_2-hover: fadeout(lighten(@color-pink-theme, 10%), 75%);
@color-pink-theme_2-active: fadeout(lighten(@color-pink-theme, 10%), 60%);
@color-pink-theme_2-font: darken(desaturate(@color-pink-theme_2, 70%), 90%);
@color-pink-theme_2-font-label: fadeout(@color-pink-theme_2-font, 50%);
@color-pink-theme_2-font-label: fadeout(@color-pink-theme_2-font, 55%);
// @color-pink-theme_2-line: transparent;
@color-pink-theme_2-line: lighten(@color-pink-theme, 25%);
@color-pink-reply-floor: fadeout(@color-pink-theme, 93%);
@@ -448,7 +448,7 @@
@color-purple-theme_2-hover: fadeout(lighten(@color-purple-theme, 10%), 80%);
@color-purple-theme_2-active: fadeout(lighten(@color-purple-theme, 10%), 70%);
@color-purple-theme_2-font: darken(desaturate(@color-purple-theme_2, 70%), 90%);
@color-purple-theme_2-font-label: fadeout(@color-purple-theme_2-font, 50%);
@color-purple-theme_2-font-label: fadeout(@color-purple-theme_2-font, 55%);
// @color-purple-theme_2-line: transparent;
@color-purple-theme_2-line: lighten(@color-purple-theme, 43%);
@color-purple-reply-floor: fadeout(@color-purple-theme, 95%);
@@ -507,7 +507,7 @@
@color-grey-theme_2-hover: fadeout(lighten(@color-grey-theme, 10%), 80%);
@color-grey-theme_2-active: fadeout(lighten(@color-grey-theme, 10%), 70%);
@color-grey-theme_2-font: darken(desaturate(@color-grey-theme_2, 70%), 90%);
@color-grey-theme_2-font-label: fadeout(@color-grey-theme_2-font, 50%);
@color-grey-theme_2-font-label: fadeout(@color-grey-theme_2-font, 55%);
// @color-grey-theme_2-line: transparent;
@color-grey-theme_2-line: lighten(@color-grey-theme, 47%);
@color-grey-reply-floor: fadeout(@color-grey-theme, 95%);
@@ -567,7 +567,7 @@
@color-ming-theme_2-background_1: #fff;
@color-ming-theme_2-background_2: fadeout(@color-ming-theme_2-background_1, 2%);
@color-ming-theme_2-font: darken(desaturate(@color-ming-theme_2, 70%), 90%);
@color-ming-theme_2-font-label: fadeout(@color-ming-theme_2-font, 50%);
@color-ming-theme_2-font-label: fadeout(@color-ming-theme_2-font, 55%);
// @color-ming-theme_2-line: transparent;
@color-ming-theme_2-line: lighten(@color-ming-theme, 60%);
@color-ming-reply-floor: fadeout(@color-ming-theme, 95%);
@@ -629,7 +629,7 @@
@color-blue2-theme_2-hover: fadeout(lighten(@color-blue2-theme, 10%), 80%);
@color-blue2-theme_2-active: fadeout(lighten(@color-blue2-theme, 10%), 70%);
@color-blue2-theme_2-font: darken(desaturate(@color-blue2-theme_2, 70%), 90%);
@color-blue2-theme_2-font-label: fadeout(@color-blue2-theme_2-font, 50%);
@color-blue2-theme_2-font-label: fadeout(@color-blue2-theme_2-font, 55%);
// @color-blue2-theme_2-line: transparent;
@color-blue2-theme_2-line: lighten(@color-blue2-theme, 41%);
@color-blue2-reply-floor: fadeout(@color-blue2-theme, 95%);
@@ -699,7 +699,7 @@
@color-black-btn-hover: fadeout(lighten(@color-black-theme, 40%), 70%);
@color-black-btn-active: fadeout(lighten(@color-black-theme, 20%), 70%);
@color-black-btn-select: fadeout(lighten(@color-black-theme, 25%), 70%);
@color-black-pagination-background: fadeout(darken(@color-black-theme, 15%), 50%);
@color-black-pagination-background: fadeout(lighten(@color-black-theme, 25%), 20%);
@color-black-pagination-hover: fadeout(lighten(@color-black-theme, 1%), 70%);
@color-black-pagination-active: fadeout(darken(@color-black-theme, 5%), 60%);
@color-black-pagination-select: fadeout(darken(@color-black-theme, 3%), 60%);
@@ -913,63 +913,63 @@
@color-naruto-badge-info: lighten(@color-naruto-theme, 10%);
@color-naruto-badge-info-font: #fff;
@color-happy_new_year-theme: rgb(252, 57, 57);
@color-happy_new_year-theme-bgimg: url(../images/xnkl.jpg);
@color-happy_new_year-theme: rgba(192, 57, 43, 1);
@color-happy_new_year-theme-bgimg: url(../images/xnkl.png);
@color-happy_new_year-theme-bgposition: center;
@color-happy_new_year-theme-bgsize: cover;
@color-happy_new_year-theme-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 45%);
@color-happy_new_year-theme-active: fadeout(lighten(@color-happy_new_year-theme, 15%), 60%);
@color-happy_new_year-theme-font: rgba(255, 255, 255, 0.9);
@color-happy_new_year-theme-font-label: fadeout(lighten(@color-happy_new_year-theme, 20%), 20%);
@color-happy_new_year-theme_2: rgba(255, 199, 199, 0.8);
@color-happy_new_year-theme_2-background_1: #f2a4a4;
@color-happy_new_year-theme-font: rgba(148, 67, 67, 0.9);
@color-happy_new_year-theme-font-label: fadeout(darken(@color-happy_new_year-theme, 20%), 20%);
@color-happy_new_year-theme_2: rgba(255, 255, 255, 0.8);
@color-happy_new_year-theme_2-background_1: #fef9f2;
@color-happy_new_year-theme_2-background_2: fadeout(@color-happy_new_year-theme_2-background_1, 2%);
@color-happy_new_year-theme_2-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 65%);
@color-happy_new_year-theme_2-active: fadeout(lighten(@color-happy_new_year-theme, 10%), 55%);
@color-happy_new_year-theme_2-font: darken(@color-happy_new_year-theme_2, 90%);
@color-happy_new_year-theme_2-font-label: desaturate(darken(@color-happy_new_year-theme, 5%), 50%);
@color-happy_new_year-theme_2-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 75%);
@color-happy_new_year-theme_2-active: fadeout(lighten(@color-happy_new_year-theme, 10%), 65%);
@color-happy_new_year-theme_2-font: darken(desaturate(@color-happy_new_year-theme_2, 70%), 90%);
@color-happy_new_year-theme_2-font-label: desaturate(lighten(@color-happy_new_year-theme, 10%), 45%);
// @color-happy_new_year-theme_2-line: transparent;
@color-happy_new_year-theme_2-line: fadeout(lighten(@color-happy_new_year-theme, 16%), 70%);
@color-happy_new_year-reply-floor: fadeout(lighten(@color-happy_new_year-theme, 12%), 85%);
@color-happy_new_year-theme-sidebar: rgba(119, 37, 18, 0.1);
@color-happy_new_year-btn: desaturate(@color-happy_new_year-theme, 10%);
@color-happy_new_year-btn-background: fadeout(lighten(@color-happy_new_year-theme, 15%), 60%);
@color-happy_new_year-theme_2-line: fadeout(lighten(@color-happy_new_year-theme, 36%), 80%);
@color-happy_new_year-reply-floor: fadeout(lighten(@color-happy_new_year-theme, 12%), 90%);
@color-happy_new_year-theme-sidebar: rgba(255, 255, 255, .3);
@color-happy_new_year-btn: lighten(@color-happy_new_year-theme, 2%);
@color-happy_new_year-btn-background: fadeout(lighten(@color-happy_new_year-theme, 35%), 70%);
@color-happy_new_year-btn-hover: fadeout(lighten(@color-happy_new_year-theme, 5%), 70%);
@color-happy_new_year-btn-active: fadeout(darken(@color-happy_new_year-theme, 5%), 70%);
@color-happy_new_year-btn-select: fadeout(lighten(@color-happy_new_year-theme, 5%), 50%);
@color-happy_new_year-pagination-background: fadeout(lighten(@color-happy_new_year-theme, 15%), 40%);
@color-happy_new_year-pagination-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-pagination-background: fadeout(lighten(@color-happy_new_year-theme, 20%), 70%);
@color-happy_new_year-pagination-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 60%);
@color-happy_new_year-pagination-active: fadeout(darken(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-pagination-select: fadeout(lighten(@color-happy_new_year-theme, 10%), 55%);
@color-happy_new_year-search-form-background: fadeout(lighten(@color-happy_new_year-theme, 15%), 20%);
@color-happy_new_year-search-list-hover: fadeout(darken(@color-happy_new_year-theme, 10%), 50%);
@color-happy_new_year-scrollbar-track: fadeout(lighten(@color-happy_new_year-theme, 6%), 75%);
@color-happy_new_year-scrollbar-thumb: fadeout(lighten(@color-happy_new_year-theme, 6%), 50%);
@color-happy_new_year-scrollbar-thumb-hover: fadeout(lighten(@color-happy_new_year-theme, 6%), 35%);
@color-happy_new_year-pagination-select: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%);
@color-happy_new_year-search-form-background: fadeout(lighten(@color-happy_new_year-theme, 30%), 20%);
@color-happy_new_year-search-list-hover: fadeout(darken(@color-happy_new_year-theme, 10%), 65%);
@color-happy_new_year-scrollbar-track: fadeout(lighten(@color-happy_new_year-theme, 10%), 70%);
@color-happy_new_year-scrollbar-thumb: fadeout(lighten(@color-happy_new_year-theme, 10%), 50%);
@color-happy_new_year-scrollbar-thumb-hover: fadeout(lighten(@color-happy_new_year-theme, 10%), 35%);
@color-happy_new_year-player-pic-c1: fadeout(@color-happy_new_year-theme_2, 50%);
@color-happy_new_year-player-pic-c2: darken(@color-happy_new_year-theme_2, 30%);
@color-happy_new_year-player-progress: darken(@color-happy_new_year-theme_2, 10%);
@color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 12%);
@color-happy_new_year-player-progress-bar2: lighten(@color-happy_new_year-theme, 9%);
@color-happy_new_year-player-progress-bar1: darken(@color-happy_new_year-theme_2, 5%);
@color-happy_new_year-player-progress-bar2: lighten(@color-happy_new_year-theme, 5%);
@color-happy_new_year-player-status-text: lighten(@color-happy_new_year-theme_2-font, 32%);
@color-happy_new_year-player-detail-lyric: fadeout(@color-happy_new_year-theme_2-font, 40%);
@color-happy_new_year-player-detail-lyric-active: darken(@color-happy_new_year-theme, 2%);
@color-happy_new_year-player-detail-lyric: fadeout(@color-happy_new_year-theme_2-font, 36%);
@color-happy_new_year-player-detail-lyric-active: darken(@color-happy_new_year-theme, 7%);
@color-happy_new_year-player-detail-play-btn: lighten(@color-happy_new_year-theme, 7%);
@color-happy_new_year-tab-btn-background: fadeout(lighten(@color-happy_new_year-theme, 10%), 80%);
@color-happy_new_year-tab-btn-background-hover: @color-happy_new_year-theme_2-hover;
@color-happy_new_year-tab-border-top: fadeout(lighten(@color-happy_new_year-theme, 5%), 50%);
@color-happy_new_year-tab-border-bottom: lighten(@color-happy_new_year-theme, 5%);
@color-happy_new_year-hideBtn: #64a130;
@color-happy_new_year-minBtn: #c4943b;
@color-happy_new_year-hideBtn: #3bc2b2;
@color-happy_new_year-minBtn: #85c43b;
@color-happy_new_year-maxBtn: #e7aa36;
@color-happy_new_year-closeBtn: #ea6e4d;
@color-happy_new_year-hideBtn-hover: fadeout(@color-hideBtn, 10%);
@color-happy_new_year-minBtn-hover: fadeout(@color-happy_new_year-minBtn, 10%);
@color-happy_new_year-maxBtn-hover: fadeout(@color-happy_new_year-maxBtn, 10%);
@color-happy_new_year-closeBtn-hover: fadeout(@color-happy_new_year-closeBtn, 10%);
@color-happy_new_year-badge-success: @color-happy_new_year-theme;
@color-happy_new_year-badge-success: lighten(@color-happy_new_year-theme, 10%);
@color-happy_new_year-badge-success-font: #fff;
@color-happy_new_year-badge-info: #c4943b;
@color-happy_new_year-badge-info: #dfbb6b;
@color-happy_new_year-badge-info-font: #fff;
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z" /></svg>

After

Width:  |  Height:  |  Size: 597 B

@@ -1,6 +1,9 @@
<template lang="pug">
button(:class="[$style.btn, min ? $style.min : null, outline ? $style.outline : null]" :disabled="disabled" @click="$emit('click', $event)")
slot
<template>
<button
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
:disabled="disabled">
<slot />
</button>
</template>
<script>
@@ -23,7 +26,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.btn {
display: inline-block;
@@ -58,7 +61,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.btn {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
@@ -15,12 +15,8 @@ div(:class="$style.checkbox")
<script>
export default {
model: {
prop: 'checked',
event: 'input',
},
props: {
checked: {},
modelValue: {},
value: {},
id: {
type: String,
@@ -51,18 +47,18 @@ export default {
}
},
watch: {
checked(n) {
modelValue(n) {
this.setValue(n)
},
},
mounted() {
this.setValue(this.checked)
this.setValue(this.modelValue)
},
methods: {
change() {
let checked
if (Array.isArray(this.checked)) {
checked = [...this.checked]
if (Array.isArray(this.modelValue)) {
checked = [...this.modelValue]
const index = checked.indexOf(this.value)
if (index < 0) {
checked.push(this.value)
@@ -71,7 +67,7 @@ export default {
}
} else {
let bool = this.bool
switch (typeof this.checked) {
switch (typeof this.modelValue) {
case 'boolean':
if (this.indeterminate) {
bool = true
@@ -89,7 +85,7 @@ export default {
break
}
}
this.$emit('input', checked)
this.$emit('update:modelValue', checked)
this.$emit('change', checked)
},
setValue(value) {
@@ -117,7 +113,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.checkbox {
display: inline-block;
@@ -189,7 +185,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.checkbox {
> input {
&:checked {
@@ -1,7 +1,15 @@
<template lang="pug">
input(:class="$style.input" ref="dom_input" :type="type" :placeholder="placeholder" :value="value" :disabled="disabled"
@focus="$emit('focus', $event)" @blur="$emit('blur', $event)" @input="handleInput" @change="$emit('change', $event.target.value.trim())"
@keyup.enter="$emit('submit', $event.target.value.trim())")
<template>
<input
:class="$style.input"
ref="dom_input"
:type="type"
:placeholder="placeholder"
:value="modelValue"
:disabled="disabled"
@input="handleInput"
@change="$emit('change', $event.target.value.trim())"
@keyup.enter="$emit('submit', $event.target.value.trim())"
/>
</template>
<script>
@@ -15,7 +23,7 @@ export default {
type: Boolean,
default: false,
},
value: {
modelValue: {
type: String,
default: '',
},
@@ -24,11 +32,12 @@ export default {
default: 'text',
},
},
emits: ['update:modelValue', 'submit', 'change'],
methods: {
handleInput(event) {
let value = event.target.value.trim()
event.target.value = value
this.$emit('input', value)
this.$emit('update:modelValue', value)
},
focus() {
this.$refs.dom_input.focus()
@@ -39,7 +48,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.input {
display: inline-block;
@@ -76,7 +85,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.input {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
@@ -1,6 +1,11 @@
<template lang="pug">
ul(:class="$style.list" :style="listStyles" ref="dom_list")
li(v-for="item in menus" @click="handleClick(item)" v-if="!item.hide && (item.action == 'download' ? setting.download.enable : true)" :disabled="item.disabled") {{item[itemName]}}
<template>
<ul :class="$style.list" :style="listStyles" ref="dom_list">
<li v-for="item in menus"
:key="item.action"
@click="handleClick(item)"
v-show="!item.hide && (item.action == 'download' ? setting.download.enable : true)"
:disabled="item.disabled ? true : null">{{item[itemName]}}</li>
</ul>
</template>
<script>
@@ -29,6 +34,7 @@ export default {
},
},
},
emits: ['menu-click'],
computed: {
...mapGetters(['setting']),
},
@@ -69,7 +75,7 @@ export default {
mounted() {
document.addEventListener('click', this.handleDocumentClick)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('click', this.handleDocumentClick)
},
methods: {
@@ -120,7 +126,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.list {
font-size: 12px;
@@ -168,7 +174,7 @@ export default {
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.list {
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
@@ -1,8 +1,10 @@
<template lang="pug">
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")
li(v-for="item in list" @click="handleClick(itemKey ? item[itemKey] : item)") {{itemName ? item[itemName] : item}}
<template>
<div :class="[$style.select, {[$style.active]: show}]">
<div :class="$style.label" ref="dom_btn" @click="handleShow">{{modelValue ? itemName ? list.find(l => l.id == modelValue).name : modelValue : ''}}</div>
<ul :class="$style.list">
<li v-for="(item, index) in list" :key="index" @click="handleClick(itemKey ? item[itemKey] : item)">{{itemName ? item[itemName] : item}}</li>
</ul>
</div>
</template>
<script>
@@ -15,7 +17,7 @@ export default {
return []
},
},
value: {
modelValue: {
type: [String, Number],
},
itemName: {
@@ -33,7 +35,7 @@ export default {
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('click', this.handleHide)
},
methods: {
@@ -45,8 +47,8 @@ export default {
}, 50)
},
handleClick(item) {
if (item === this.value) return
this.$emit('input', item)
if (item === this.modelValue) return
this.$emit('update:modelValue', item)
this.$emit('change', item)
},
handleShow() {
@@ -58,7 +60,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.select {
font-size: 12px;
@@ -131,7 +133,7 @@ export default {
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.label {
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
@@ -1,12 +1,18 @@
<template lang="pug">
div.content(:class="[$style.select, show ? $style.active : '']")
div.label-content(:class="$style.label" ref="dom_btn" @click="handleShow")
span.label {{label}}
div.icon(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.847 451.847' space='preserve')
use(xlink:href='#icon-down')
ul.selection-list.scroll(:class="$style.list" :style="listStyles" ref="dom_list")
li(v-for="item in list" :class="(itemKey ? item[itemKey] : item) == value ? $style.active : null" @click="handleClick(item)" :tips="itemName ? item[itemName] : item") {{itemName ? item[itemName] : item}}
<template>
<div class="content" :class="[$style.select, show ? $style.active : '']">
<div class="label-content" :class="$style.label" ref="dom_btn" @click="handleShow">
<span class="label">{{label}}</span>
<div class="icon" :class="$style.icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
<use xlink:href="#icon-down"></use>
</svg>
</div>
</div>
<ul class="selection-list scroll" :class="$style.list" :style="listStyles" ref="dom_list">
<li v-for="(item, index) in list" :key="index" :class="(itemKey ? item[itemKey] : item) == modelValue ? $style.active : null"
@click="handleClick(item)" :tips="itemName ? item[itemName] : item">{{itemName ? item[itemName] : item}}</li>
</ul>
</div>
</template>
<script>
@@ -19,7 +25,7 @@ export default {
return []
},
},
value: {
modelValue: {
type: [String, Number],
},
itemName: {
@@ -40,14 +46,14 @@ export default {
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('click', this.handleHide)
},
computed: {
label() {
if (this.value == null) return ''
if (this.itemName == null) return this.value
const item = this.list.find(l => l[this.itemKey] == this.value)
if (this.modelValue == null) return ''
if (this.itemName == null) return this.modelValue
const item = this.list.find(l => l[this.itemKey] == this.modelValue)
if (!item) return ''
return item[this.itemName]
},
@@ -62,9 +68,9 @@ export default {
}, 50)
},
handleClick(item) {
// console.log(this.value)
if (item === this.value) return
this.$emit('input', this.itemKey ? item[this.itemKey] : item)
// console.log(this.modelValue)
if (item === this.modelValue) return
this.$emit('update:modelValue', this.itemKey ? item[this.itemKey] : item)
this.$emit('change', item)
},
handleShow() {
@@ -87,7 +93,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
@selection-height: 28px;
@@ -189,7 +195,7 @@ export default {
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.label {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
@@ -2,7 +2,7 @@
div.scroll(:class="$style.tab")
//- div(:class="$style.content")
ul
li.ignore-to-rem(v-for="item in list" :key="itemKey ? item[itemKey] : item" :class="value === (itemKey ? item[itemKey] : item) ? $style.active : ''")
li.ignore-to-rem(v-for="item in list" :key="itemKey ? item[itemKey] : item" :class="modelValue === (itemKey ? item[itemKey] : item) ? $style.active : ''")
button(type="button"
@click="handleClick(itemKey ? item[itemKey] : item)") {{ itemName ? item[itemName] : item }}
//- div(:class="$style.control")
@@ -23,7 +23,7 @@ export default {
return []
},
},
value: {
modelValue: {
type: [String, Number],
},
itemName: {
@@ -35,8 +35,8 @@ export default {
},
methods: {
handleClick(item) {
if (item === this.value) return
this.$emit('input', item)
if (item === this.modelValue) return
this.$emit('update:modelValue', item)
this.$emit('change', item)
},
},
@@ -45,7 +45,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.tab {
// overflow: hidden;
@@ -171,7 +171,7 @@ export default {
// }
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.tab {
ul {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
@@ -0,0 +1,257 @@
<template>
<component :is="containerEl" :class="containerClass" ref="dom_scrollContainer" style="height: 100%; overflow: auto; position: relative; display: block; contain: strict;">
<component :is="contentEl" :class="contentClass" :style="contentStyle">
<div v-for="item in views" :key="item.key" :style="item.style">
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
</div>
</component>
<slot name="footer" />
</component>
</template>
<script>
import {
computed,
ref,
nextTick,
watch,
onMounted,
onBeforeUnmount,
} from 'vue'
const easeInOutQuad = (t, b, c, d) => {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel = () => {}) => {
if (!element) return callback()
const start = element.scrollTop || element.scrollY || 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
} else if (to < start) {
if (to < 0) to = 0
} else return callback()
const change = to - start
const increment = 10
if (!change) return callback()
let currentTime = 0
let val
let cancelCallback
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(0, val)
} else {
element.scrollTop = val
}
if (currentTime < duration) {
if (cancel) {
cancelCallback()
onCancel()
return
}
setTimeout(animateScroll, increment)
} else {
callback()
}
}
animateScroll()
return (callback) => {
cancelCallback = callback
cancel = true
}
}
export default {
name: 'VirtualizedList',
props: {
containerEl: {
type: String,
default: 'div',
},
containerClass: {
type: String,
default: 'virtualized-list',
},
contentEl: {
type: String,
default: 'div',
},
contentClass: {
type: String,
default: 'virtualized-list-content',
},
outsideNum: {
type: Number,
default: 1,
},
itemHeight: {
type: Number,
required: true,
},
keyName: {
type: String,
require: true,
},
list: {
type: Array,
require: true,
},
},
emits: ['scroll'],
setup(props, { emit }) {
const views = ref([])
const dom_scrollContainer = ref(null)
let startIndex = -1
let endIndex = -1
let scrollTop = -1
let cachedList = []
let cancelScroll = null
let isScrolling = false
let scrollToValue = 0
const createList = (startIndex, endIndex) => {
const cache = cachedList.slice(startIndex, endIndex)
const list = props.list.slice(startIndex, endIndex).map((item, i) => {
if (cache[i]) return cache[i]
const top = (startIndex + i) * props.itemHeight
const index = startIndex + i
return cachedList[index] = {
item,
top,
style: { position: 'absolute', left: 0, right: 0, top: top + 'px', height: props.itemHeight + 'px' },
index,
key: item[props.keyName],
}
})
return list
}
const updateView = (currentScrollTop = dom_scrollContainer.value.scrollTop) => {
// const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
const itemHeight = props.itemHeight
const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
const currentStartRenderIndex = Math.max(Math.floor(currentScrollTop / itemHeight) - props.outsideNum, 0)
const currentEndRenderIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight) + props.outsideNum
// console.log(continuous)
// debugger
if (continuous) {
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * this.outsideNum * 0.6) return
// console.log('update')
// if (currentScrollTop > scrollTop) { // scroll down
// // console.log('scroll down')
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
// // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
// // // if (this.views.length > 100) {
// // nextTick(() => {
// // views.value.splice(0, views.value.indexOf(list[0]))
// // })
// // }
// } else if (currentScrollTop < scrollTop) { // scroll up
// // console.log('scroll up')
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
// } else return
if (currentScrollTop == scrollTop) return
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
} else {
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
}
startIndex = currentStartIndex
endIndex = currentEndIndex
scrollTop = currentScrollTop
}
const onScroll = event => {
const currentScrollTop = dom_scrollContainer.value.scrollTop
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * props.outsideNum * 0.6) {
updateView(currentScrollTop)
}
emit('scroll', event)
}
const scrollTo = (scrollTop, animate = false) => {
return new Promise(resolve => {
if (cancelScroll) {
cancelScroll(resolve)
} else {
resolve()
}
}).then(() => {
return new Promise((resolve, reject) => {
if (animate) {
isScrolling = true
scrollToValue = scrollTop
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
cancelScroll = null
isScrolling = false
resolve()
}, () => {
cancelScroll = null
isScrolling = false
reject('canceled')
})
} else {
dom_scrollContainer.value.scrollTop = scrollTop
}
})
})
}
const scrollToIndex = (index, offset = 0, animate = false) => {
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
}
const getScrollTop = () => {
return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
}
const contentStyle = computed(() => ({
display: 'block',
height: props.list.length * props.itemHeight + 'px',
}))
watch(() => props.itemHeight, updateView)
watch(() => props.list, (list) => {
cachedList = Array(list.length)
startIndex = -1
endIndex = -1
nextTick(() => {
updateView()
})
}, {
deep: true,
})
onMounted(() => {
dom_scrollContainer.value.addEventListener('scroll', onScroll, false)
cachedList = Array(props.list.length)
startIndex = -1
endIndex = -1
updateView()
})
onBeforeUnmount(() => {
dom_scrollContainer.value.removeEventListener('scroll', onScroll)
if (cancelScroll) cancelScroll()
})
return {
views,
dom_scrollContainer,
contentStyle,
scrollTo,
scrollToIndex,
getScrollTop,
}
},
}
</script>
@@ -0,0 +1,135 @@
<template>
<div :class="$style.content">
<canvas :class="$style.canvas" ref="dom_canvas"></canvas>
</div>
</template>
<script>
import { ref, onBeforeUnmount, onMounted, useRefGetter, watch } from '@renderer/utils/vueTools'
import { getAnalyser } from '@renderer/plugins/player'
import { isPlay } from '@renderer/core/share/player'
import { player as eventPlayerNames } from '@renderer/event/names'
const themes = {
green: 'rgba(77,175,124,.1)',
blue: 'rgba(52,152,219,.1)',
yellow: 'rgba(233,212,96,.16)',
orange: 'rgba(245,171,53,.1)',
red: 'rgba(214,69,65,.08)',
pink: 'rgba(241,130,141,.1)',
purple: 'rgba(155,89,182,.1)',
grey: 'rgba(108,122,137,.1)',
ming: 'rgba(51,110,123,.1)',
blue2: 'rgba(79,98,208,.1)',
black: 'rgba(39,39,39,.26)',
mid_autumn: 'rgba(74,55,82,.05)',
naruto: 'rgba(87,144,167,.1)',
happy_new_year: 'rgba(192,57,43,.1)',
}
export default {
setup() {
const dom_canvas = ref(null)
const analyser = getAnalyser()
let ctx
let bufferLength
let dataArray
let WIDTH
let HEIGHT
let barWidth
let barHeight
let x = 0
let isPlaying = false
let animationFrameId
const theme = useRefGetter('theme')
// const setting = useRefGetter('setting')
let themeColor = themes[theme.value || 'green']
watch(theme, theme => {
themeColor = themes[theme || 'green']
})
// https://codepen.io/nfj525/pen/rVBaab
const renderFrame = () => {
animationFrameId = null
if (isPlaying) animationFrameId = window.requestAnimationFrame(renderFrame)
x = 0
analyser.getByteFrequencyData(dataArray)
ctx.clearRect(0, 0, WIDTH, HEIGHT)
// ctx.fillRect(0, 0, WIDTH, HEIGHT)
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i]
// let r = barHeight + (25 * (i / bufferLength))
// let g = 250 * (i / bufferLength)
// let b = 50
// ctx.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')'
ctx.fillStyle = themeColor
ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight)
x += barWidth
}
}
const handlePlay = () => {
isPlaying = true
analyser.fftSize = 256
bufferLength = analyser.frequencyBinCount
// console.log(bufferLength)
barWidth = (WIDTH / bufferLength) * 2.5
dataArray = new Uint8Array(bufferLength)
renderFrame()
}
const handlePause = () => {
if (animationFrameId) window.cancelAnimationFrame(animationFrameId)
isPlaying = false
}
window.eventHub.on(eventPlayerNames.play, handlePlay)
window.eventHub.on(eventPlayerNames.pause, handlePause)
window.eventHub.on(eventPlayerNames.error, handlePause)
onBeforeUnmount(() => {
handlePause()
window.eventHub.off(eventPlayerNames.play, handlePlay)
window.eventHub.off(eventPlayerNames.pause, handlePause)
window.eventHub.off(eventPlayerNames.error, handlePause)
})
onMounted(() => {
const canvas = dom_canvas.value
ctx = canvas.getContext('2d')
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
WIDTH = canvas.width
HEIGHT = canvas.height
if (isPlay.value) handlePlay()
})
return {
dom_canvas,
}
},
}
</script>
<style lang="less" module>
.content {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
.canvas {
width: 100%;
height: 100%;
// opacity: 0.1;
}
</style>
@@ -0,0 +1,114 @@
<template>
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport">
<main :class="$style.main">
<h2>{{ info.name }}<br/>{{ info.singer }}</h2>
<base-btn :class="$style.btn" :key="type.type" @click="handleClick(type.type)" v-for="type in types"
>{{getTypeName(type.type)}} {{ type.type.toUpperCase() }}{{ type.size && ` - ${type.size.toUpperCase()}` }}</base-btn>
</main>
</material-modal>
</template>
<script>
import { mapActions } from 'vuex'
import { qualityList } from '@renderer/core/share'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
},
bgClose: {
type: Boolean,
default: true,
},
teleport: String,
},
emits: ['update:show'],
setup() {
return {
qualityList,
}
},
computed: {
info() {
return this.musicInfo || {}
},
sourceQualityList() {
return this.qualityList[this.musicInfo.source] || []
},
types() {
return this.info?.types?.filter(type => this.checkSource(type.type)) || []
},
},
methods: {
...mapActions('download', ['createDownload']),
handleClick(type) {
this.createDownload({ musicInfo: this.musicInfo, type })
this.handleClose()
},
handleClose() {
this.$emit('update:show', false)
},
getTypeName(type) {
switch (type) {
case 'flac':
case 'ape':
case 'wav':
return this.$t('download__lossless')
case '320k':
return this.$t('download__high_quality')
case '192k':
case '128k':
return this.$t('download__normal')
}
},
checkSource(type) {
return this.sourceQualityList.includes(type)
},
},
}
</script>
<style lang="less" module>
@import '@renderer/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(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>
@@ -0,0 +1,86 @@
<template>
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport">
<main :class="$style.main">
<h2>{{$t('download__multiple_tip', { len: list.length })}}<br/>{{$t('download__multiple_tip2')}}</h2>
<base-btn :class="$style.btn" @click="handleClick('128k')">{{$t('download__normal')}} - 128K</base-btn>
<base-btn :class="$style.btn" @click="handleClick('320k')">{{$t('download__high_quality')}} - 320K</base-btn>
<base-btn :class="$style.btn" @click="handleClick('flac')">{{$t('download__lossless')}} - FLAC</base-btn>
</main>
</material-modal>
</template>
<script>
import { mapActions } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
bgClose: {
type: Boolean,
default: true,
},
list: {
type: Array,
default() {
return []
},
},
teleport: String,
},
emits: ['update:show', 'confirm'],
methods: {
...mapActions('download', ['createDownloadMultiple']),
handleClick(type) {
this.createDownloadMultiple({ list: [...this.list], type })
this.handleClose()
this.$emit('confirm')
},
handleClose() {
this.$emit('update:show', false)
},
},
}
</script>
<style lang="less" module>
@import '@renderer/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(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>
@@ -1,21 +1,27 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| {{$t('material.list_add_modal.' + (isMove ? 'title_first_move' : 'title_first_add'))}}&nbsp;
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
| &nbsp;{{$t('material.list_add_modal.title_last')}}
div.scroll(:class="$style.btnContent")
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
material-btn(:class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('view.list.lists_new_list_btn')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
input.key-bind(:class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('view.list.lists_new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)")
span(:class="$style.btn" v-for="i in spaceNum")
<template>
<material-modal :show="show" :bg-close="bgClose" @close="handleClose" :teleport="teleport">
<main :class="$style.main">
<h2>{{$t('list_add__' + (isMove ? 'title_first_move' : 'title_first_add'))}}&nbsp;<span :class="$style.name">{{this.musicInfo && `${musicInfo.name}`}}</span>&nbsp;{{$t('list_add__title_last')}}</h2>
<div class="scroll" :class="$style.btnContent">
<base-btn :class="$style.btn" :tips="$t('list_add__btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn>
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('lists__new_list_btn')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve">
<use xlink:href="#icon-addTo"></use>
</svg>
<input class="key-bind" :class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('lists__new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)"/>
</base-btn>
<span :class="$style.btn" :key="i" v-for="i in spaceNum"></span>
</div>
</main>
</material-modal>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { mapMutations } from 'vuex'
import { computed } from '@renderer/utils/vueTools'
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
import { getList } from '@renderer/core/share/utils'
export default {
props: {
show: {
@@ -35,10 +41,10 @@ export default {
return []
},
},
listName: {
type: String,
default: '',
},
// listName: {
// type: String,
// default: '',
// },
fromListId: {
type: String,
default: null,
@@ -47,6 +53,22 @@ export default {
type: Boolean,
default: false,
},
teleport: String,
},
emits: ['update:show'],
setup(props) {
const lists = computed(() => {
if (!props.musicInfo) return []
const targetMid = props.musicInfo.songmid
return [
defaultList,
loveList,
...userLists,
].filter(l => !props.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: getList(l.id).some(s => s.songmid == targetMid) }))
})
return {
lists,
}
},
data() {
return {
@@ -55,15 +77,6 @@ export default {
}
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
if (!this.musicInfo) return []
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => !this.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: l.list.some(s => s.songmid == this.musicInfo.songmid) }))
},
spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
},
@@ -79,11 +92,11 @@ export default {
})
},
handleClose() {
this.$emit('close')
this.$emit('update:show', false)
},
handleEditing(event) {
if (this.isEditing) return
if (!this.newListName) this.newListName = this.listName
// if (!this.newListName) this.newListName = this.listName
this.isEditing = true
this.$nextTick(() => event.currentTarget.querySelector('.' + this.$style.newListInput).focus())
},
@@ -100,11 +113,11 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.main {
// padding: 15px 0;
max-width: 530px;
max-width: 620px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
@@ -141,7 +154,7 @@ export default {
height: 36px;
line-height: 36px;
padding: 0 10px !important;
width: 150px;
width: 180px;
.mixin-ellipsis-1;
}
@@ -182,7 +195,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
@@ -1,18 +1,26 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2 {{$t('material.list_add_multiple_modal.' + (isMove ? 'title_move' : 'title_add'), { num: musicList.length })}}
div.scroll(:class="$style.btnContent")
material-btn(:class="$style.btn" :tips="$t('material.list_add_multiple_modal.btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
material-btn(:class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('view.list.lists_new_list_btn')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
input.key-bind(:class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('view.list.lists_new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)")
span(:class="$style.btn" v-for="i in spaceNum")
<template>
<material-modal :show="show" :bg-close="bgClose" @close="handleClose">
<main :class="$style.main">
<h2>{{$t('list_add__multiple_' + (isMove ? 'title_move' : 'title_add'), { num: musicList.length })}}</h2>
<div class="scroll" :class="$style.btnContent">
<base-btn :class="$style.btn" :tips="$t('list_add__multiple_btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists">{{item.name}}</base-btn>
<base-btn :class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('lists__new_list_btn')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 0 42 42" space="preserve">
<use xlink:href="#icon-addTo"></use>
</svg>
<input class="key-bind" :class="$style.newListInput" :value="newListName" type="text" :placeholder="$t('lists__new_list_input')" @keyup.enter="handleSaveList($event)" @blur="handleSaveList($event)"/>
</base-btn>
<span :class="$style.btn" :key="i" v-for="i in spaceNum"></span>
</div>
</main>
</material-modal>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { mapMutations } from 'vuex'
import { computed } from '@renderer/utils/vueTools'
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
export default {
props: {
show: {
@@ -35,10 +43,10 @@ export default {
return []
},
},
listName: {
type: String,
default: '',
},
// listName: {
// type: String,
// default: '',
// },
fromListId: {
type: String,
default: null,
@@ -47,6 +55,20 @@ export default {
type: Boolean,
default: false,
},
teleport: String,
},
emits: ['update:show', 'confirm'],
setup(props) {
const lists = computed(() => {
return [
defaultList,
loveList,
...userLists,
].filter(l => !props.excludeListId.includes(l.id))
})
return {
lists,
}
},
data() {
return {
@@ -55,15 +77,7 @@ export default {
}
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => !this.excludeListId.includes(l.id))
},
spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
},
@@ -75,15 +89,16 @@ export default {
? this.listMoveMultiple({ fromId: this.fromListId, toId: this.lists[index].id, list: this.musicList })
: this.listAddMultiple({ id: this.lists[index].id, list: this.musicList })
this.$nextTick(() => {
this.handleClose(true)
this.handleClose()
this.$emit('confirm')
})
},
handleClose(isSelect = false) {
this.$emit('close', isSelect)
handleClose() {
this.$emit('update:show', false)
},
handleEditing(event) {
if (this.isEditing) return
if (!this.newListName) this.newListName = this.listName
// if (!this.newListName) this.newListName = this.listName
this.isEditing = true
this.$nextTick(() => event.currentTarget.querySelector('.' + this.$style.newListInput).focus())
},
@@ -100,11 +115,11 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.main {
// padding: 15px 0;
max-width: 530px;
max-width: 620px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
@@ -137,7 +152,7 @@ export default {
height: 36px;
line-height: 36px;
padding: 0 10px !important;
width: 150px;
width: 180px;
.mixin-ellipsis-1;
}
@@ -178,7 +193,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
@@ -0,0 +1,160 @@
<template>
<div :class="[$style.progress, className]">
<div :class="[$style.progressBar, $style.progressBar2, {[$style.barTransition]: isActiveTransition}]" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${progress || 0})` }"></div>
<div v-show="dragging" :class="[$style.progressBar, $style.progressBar3]" :style="{ transform: `scaleX(${dragProgress || 0})` }"></div>
</div>
<div :class="$style.progressMask" @mousedown="handleMsDown" ref="dom_progress"></div>
</template>
<script>
import { ref, onBeforeUnmount } from '@renderer/utils/vueTools'
import { player as eventPlayerNames } from '@renderer/event/names'
import { playProgress } from '@renderer/core/share/playProgress'
export default {
props: {
className: String,
progress: {
type: Number,
required: true,
},
isActiveTransition: {
type: Boolean,
required: true,
},
handleTransitionEnd: {
type: Function,
required: true,
},
},
setup(props) {
const msEvent = {
isMsDown: false,
msDownX: 0,
msDownProgress: 0,
}
const dom_progress = ref(null)
const dragging = ref(false)
const dragProgress = ref(0)
const handleMsDown = event => {
msEvent.isMsDown = true
msEvent.msDownX = event.clientX
let val = event.offsetX / dom_progress.value.clientWidth
if (val < 0) val = 0
if (val > 1) val = 1
dragProgress.value = msEvent.msDownProgress = val
}
const handleMsUp = () => {
if (msEvent.isMsDown) setProgress(dragProgress.value * playProgress.maxPlayTime)
msEvent.isMsDown = false
dragging.value = false
}
const handleMsMove = event => {
if (!msEvent.isMsDown) return
if (!dragging.value) dragging.value = true
let progress = msEvent.msDownProgress + (event.clientX - msEvent.msDownX) / dom_progress.value.clientWidth
if (progress > 1) progress = 1
else if (progress < 0) progress = 0
dragProgress.value = progress
}
document.addEventListener('mousemove', handleMsMove)
document.addEventListener('mouseup', handleMsUp)
onBeforeUnmount(() => {
document.removeEventListener('mousemove', handleMsMove)
document.removeEventListener('mouseup', handleMsUp)
})
const setProgress = num => {
window.eventHub.emit(eventPlayerNames.setProgress, num)
}
// const handleSetProgress = event => {
// // setProgress(event.offsetX / dom_progress.value.clientWidth * playProgress.maxPlayTime)
// }
return {
dom_progress,
// handleSetProgress,
dragging,
dragProgress,
handleMsDown,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.progress {
width: 100%;
height: 4px;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
// background-color: #f5f5f5;
position: relative;
border-radius: 20px;
}
.progress-mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.progress-bar {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
transform-origin: 0;
border-radius: 20px;
}
.progress-bar1 {
background-color: @color-player-progress-bar1;
}
.progress-bar2 {
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
opacity: 0.8;
}
.progress-bar3 {
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
opacity: 0.3;
}
.bar-transition {
transition-property: transform;
transition-timing-function: ease-out;
transition-duration: 0.2s;
}
each(@themes, {
:global(#root.@{value}) {
.progress {
background-color: ~'@{color-@{value}-player-progress}';
}
.progress-bar1 {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
}
.progress-bar3 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
}
}
})
</style>
@@ -0,0 +1,144 @@
<template>
<div :class="[$style.volumeContent, className]">
<div :class="[$style.volume, {[$style.muted]: setting.player.isMute} ]">
<div :class="$style.volumeBar" ref="dom_volumeBar" :style="{ transform: `scaleX(${volume || 0})` }"></div>
</div>
<div :class="$style.volumeMask" @mousedown="handleVolumeMsDown" :tips="`${$t('player__volume')}${parseInt(volume * 100)}%`"></div>
</div>
</template>
<script>
import { ref, onBeforeUnmount } from '@renderer/utils/vueTools'
// import { player as eventPlayerNames } from '@renderer/event/names'
import { volume, isMute, setVolume, setMute } from '@renderer/core/share/volume'
export default {
props: {
className: {
type: String,
},
setting: {
type: Object,
required: true,
},
},
setup(props) {
const volumeEvent = {
isMsDown: false,
msDownX: 0,
msDownVolume: 0,
}
const dom_volumeBar = ref(null)
const handleVolumeMsDown = event => {
volumeEvent.isMsDown = true
volumeEvent.msDownX = event.clientX
let val = event.offsetX / dom_volumeBar.value.clientWidth
if (val < 0) val = 0
if (val > 1) val = 1
setVolume(volumeEvent.msDownVolume = val)
if (isMute.value) setMute(false)
}
const handleVolumeMsUp = () => {
volumeEvent.isMsDown = false
}
const handleVolumeMsMove = event => {
if (!volumeEvent.isMsDown) return
let volume = volumeEvent.msDownVolume + (event.clientX - volumeEvent.msDownX) / dom_volumeBar.value.clientWidth
if (volume > 1) volume = 1
else if (volume < 0) volume = 0
setVolume(volume)
}
document.addEventListener('mousemove', handleVolumeMsMove)
document.addEventListener('mouseup', handleVolumeMsUp)
onBeforeUnmount(() => {
document.removeEventListener('mousemove', handleVolumeMsMove)
document.removeEventListener('mouseup', handleVolumeMsUp)
})
return {
handleVolumeMsDown,
dom_volumeBar,
volume,
isMute,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.volume-content {
flex: none;
position: relative;
width: 80px;
margin-right: 10px;
display: flex;
align-items: center;
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, opacity;
background-color: @color-player-progress-bar1;
// background-color: #f5f5f5;
position: relative;
border-radius: @radius-progress-border;
}
.muted {
opacity: .5;
}
.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-btn;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.2);
}
.volume-mask {
position: absolute;
top: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
each(@themes, {
:global(#root.@{value}) {
.volume {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.volume-bar {
background-color: ~'@{color-@{value}-btn}';
}
}
})
</style>
-299
View File
@@ -1,299 +0,0 @@
<template lang="pug">
div(:class="$style.aside")
div(:class="$style.controlBtn" v-if="setting.controlBtnPosition == 'left'")
button(type="button" :class="$style.close" :tips="$t('core.toolbar.close')" @click="close")
svg(:class="$style.controlBtniIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close')
button(type="button" :class="$style.min" :tips="$t('core.toolbar.min')" @click="min")
svg(:class="$style.controlBtniIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize')
div(:class="['animated', logoAnimate, $style.logo]" v-else) L X
//- svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' height='100%' viewBox='0 0 127 61' space='preserve')
use(xlink:href='#icon-logo')
div(:class="$style.menu")
dl
//- dt {{$t('core.aside.online_music')}}
dd
router-link(:active-class="$style.active" to="search" :tips="$t('core.aside.search')")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 425.2 425.2' space='preserve')
use(xlink:href='#icon-search-2')
dd
router-link(:active-class="$style.active" to="songList" :tips="$t('core.aside.song_list')")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 425.2 425.2' space='preserve')
use(xlink:href='#icon-album')
//- span {{$t('core.aside.song_list')}}
dd
router-link(:active-class="$style.active" to="leaderboard" :tips="$t('core.aside.leaderboard')")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 425.22 425.2' space='preserve')
use(xlink:href='#icon-leaderboard')
//- span {{$t('core.aside.leaderboard')}}
dl
//- dt {{$t('core.aside.my_music')}}
dd
router-link(:active-class="$style.active" to="list" :tips="$t('core.aside.my_list')")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 444.87 391.18' space='preserve')
use(xlink:href='#icon-love')
dl
//- dt {{$t('core.aside.other')}}
dd(v-if="setting.download.enable")
router-link(:active-class="$style.active" to="download" :tips="$t('core.aside.download')")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 425.2 425.2' space='preserve')
use(xlink:href='#icon-download-2')
//- span {{$t('core.aside.download')}}
dd
router-link(:active-class="$style.active" to="setting" :tips="$t('core.aside.setting')")
div(:class="$style.icon")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 493.23 436.47' space='preserve')
use(xlink:href='#icon-setting')
//- span {{$t('core.aside.setting')}}
</template>
<script>
import { mapGetters } from 'vuex'
import { base as eventBaseName } from '../../event/names'
// import { getRandom } from '../../utils'
export default {
data() {
return {
active: 'search',
animates: [
'bounce',
// 'flash',
// 'pulse',
'rubberBand',
// 'shake',
// 'headShake',
'swing',
'tada',
// 'wobble',
'jello',
// 'heartBeat',
],
logoAnimate: '',
}
},
computed: {
...mapGetters(['setting']),
...mapGetters('list', ['defaultList']),
},
// mounted() {
// this.logoAnimate = this.animates[getRandom(0, this.animates.length)]
// },
methods: {
// handleMouseEnter() {
// console.log('object')
// this.logoAnimate = this.animates[getRandom(0, this.animates.length)]
// },
min() {
window.eventHub.$emit(eventBaseName.min)
},
max() {
window.eventHub.$emit(eventBaseName.max)
},
close() {
window.eventHub.$emit(eventBaseName.close)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.aside {
// box-shadow: 0 0 5px rgba(0, 0, 0, .3);
transition: @transition-theme;
transition-property: background-color;
background-color: @color-theme-sidebar;
// background-color: @color-aside-background;
// border-right: 2px solid @color-theme;
-webkit-app-region: drag;
-webkit-user-select: none;
display: flex;
flex-flow: column nowrap;
}
@control-btn-width: @height-toolbar * .26;
@control-btn-height: 6%;
.controlBtn {
box-sizing: border-box;
padding: 0 7px;
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
height: @control-btn-height;
-webkit-app-region: no-drag;
opacity: .5;
transition: opacity @transition-theme;
&:hover {
opacity: .8;
.controlBtniIcon {
opacity: 1;
}
}
button {
position: relative;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
display: flex;
// justify-content: center;
// align-items: center;
outline: none;
padding: 1px;
cursor: pointer;
border-radius: 50%;
color: @color-theme_2;
&.min {
background-color: @color-minBtn;
}
&.max {
background-color: @color-maxBtn;
}
&.close {
background-color: @color-closeBtn;
}
}
}
.controlBtniIcon {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.logo {
box-sizing: border-box;
padding: 0 13%;
height: 50px;
color: @color-theme-font;
flex: none;
text-align: center;
line-height: 50px;
font-weight: bold;
// -webkit-app-region: no-drag;
}
.menu {
flex: auto;
// &.controlBtnLeft {
// display: flex;
// flex-flow: column nowrap;
// justify-content: center;
// padding-bottom: @control-btn-height;
// }
// padding: 5px;
dl {
-webkit-app-region: no-drag;
// margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
dt {
padding-left: 5px;
font-size: 11px;
transition: @transition-theme;
transition-property: color;
color: @color-theme-font-label;
.mixin-ellipsis-1;
}
dd a {
display: block;
box-sizing: border-box;
text-decoration: none;
position: relative;
padding: 18px 3px;
// margin: 5px 0;
// border-left: 5px solid transparent;
transition: @transition-theme;
transition-property: color;
color: @color-theme-font;
cursor: pointer;
font-size: 11.5px;
text-align: center;
outline: none;
transition: background-color 0.3s ease;
// border-radius: @radius-border;
.mixin-ellipsis-1;
&.active {
// border-left-color: @color-theme-active;
background-color: @color-theme-active;
}
&:hover:not(.active) {
background-color: @color-theme-hover;
}
&:hover:not(.active) {
background-color: @color-theme-active;
}
}
}
}
.icon {
// margin-bottom: 5px;
&> svg {
width: 32%;
}
}
each(@themes, {
:global(#container.@{value}) {
.aside {
background-color: ~'@{color-@{value}-theme-sidebar}';
}
.controlBtn {
button {
color: ~'@{color-@{value}-theme_2}';
&.min {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
.logo {
color: ~'@{color-@{value}-theme-font}';
}
.menu {
dl {
dt {
color: ~'@{color-@{value}-theme-font-label}';
}
dd a {
color: ~'@{color-@{value}-theme-font}';
&.active {
background-color: ~'@{color-@{value}-theme-active}';
}
&:hover:not(.active) {
background-color: ~'@{color-@{value}-theme-hover}';
}
&:active:not(.active) {
background-color: ~'@{color-@{value}-theme-active}';
}
}
}
}
}
})
</style>
@@ -0,0 +1,111 @@
<template>
<div :class="$style.controlBtn">
<button type="button" :class="[$style.btn, $style.close]" :tips="$t('close')" @click="close">
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-close"></use>
</svg>
</button>
<button type="button" :class="[$style.btn, $style.min]" :tips="$t('min')" @click="min">
<svg :class="$style.controlBtniIcon" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-minimize"></use>
</svg>
</button>
</div>
</template>
<script>
import { base as eventBaseName } from '@renderer/event/names'
// import { getRandom } from '../../utils'
export default {
setup() {
return {
min() {
window.eventHub.emit(eventBaseName.min)
},
max() {
window.eventHub.emit(eventBaseName.max)
},
close() {
window.eventHub.emit(eventBaseName.close)
},
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
@control-btn-width: @height-toolbar * .26;
@control-btn-height: 6%;
.controlBtn {
box-sizing: border-box;
padding: 0 7px;
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
height: @control-btn-height;
-webkit-app-region: no-drag;
opacity: .5;
transition: opacity @transition-theme;
&:hover {
opacity: .8;
.controlBtniIcon {
opacity: 1;
}
}
}
.btn {
position: relative;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
display: flex;
// justify-content: center;
// align-items: center;
outline: none;
padding: 1px;
cursor: pointer;
border-radius: 50%;
color: @color-theme_2;
&.min {
background-color: @color-minBtn;
}
&.max {
background-color: @color-maxBtn;
}
&.close {
background-color: @color-closeBtn;
}
}
.controlBtniIcon {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
each(@themes, {
:global(#root.@{value}) {
.btn {
color: ~'@{color-@{value}-theme_2}';
&.min {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
})
</style>
@@ -0,0 +1,159 @@
<template>
<div :class="$style.menu">
<ul :class="$style.list">
<li v-for="item in menus" :key="item.to">
<router-link :class="$style.link" :active-class="$style.active" :to="item.to" :tips="item.tips">
<div :class="$style.icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" :viewBox="item.iconSize" space="preserve">
<use :xlink:href="item.icon"></use>
</svg>
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'Nav',
computed: {
...mapGetters(['setting']),
menus() {
return [
{
to: 'search',
tips: this.$t('search'),
icon: '#icon-search-2',
iconSize: '0 0 425.2 425.2',
enable: true,
},
{
to: 'songList',
tips: this.$t('song_list'),
icon: '#icon-album',
iconSize: '0 0 425.2 425.2',
enable: true,
},
{
to: 'leaderboard',
tips: this.$t('leaderboard'),
icon: '#icon-leaderboard',
iconSize: '0 0 425.22 425.2',
enable: true,
},
{
to: 'list',
tips: this.$t('my_list'),
icon: '#icon-love',
iconSize: '0 0 444.87 391.18',
enable: true,
},
{
to: 'download',
tips: this.$t('download'),
icon: '#icon-download-2',
iconSize: '0 0 425.2 425.2',
enable: this.setting.download.enable,
},
{
to: 'setting',
tips: this.$t('setting'),
icon: '#icon-setting',
iconSize: '0 0 493.23 436.47',
enable: true,
},
].filter(m => m.enable)
},
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.menu {
flex: auto;
// &.controlBtnLeft {
// display: flex;
// flex-flow: column nowrap;
// justify-content: center;
// padding-bottom: @control-btn-height;
// }
// padding: 5px;
}
.list {
-webkit-app-region: no-drag;
// margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
// dt {
// padding-left: 5px;
// font-size: 11px;
// transition: @transition-theme;
// transition-property: color;
// color: @color-theme-font-label;
// .mixin-ellipsis-1;
// }
}
.link {
display: block;
box-sizing: border-box;
text-decoration: none;
position: relative;
padding: 18px 3px;
// margin: 5px 0;
// border-left: 5px solid transparent;
transition: @transition-theme;
transition-property: color;
color: @color-theme-font !important;
cursor: pointer;
font-size: 11.5px;
text-align: center;
outline: none;
transition: background-color 0.3s ease;
// border-radius: @radius-border;
.mixin-ellipsis-1;
&.active {
// border-left-color: @color-theme-active;
background-color: @color-theme-active;
}
&:hover:not(.active) {
background-color: @color-theme-hover;
}
&:hover:not(.active) {
background-color: @color-theme-active;
}
}
.icon {
// margin-bottom: 5px;
&> svg {
width: 32%;
}
}
each(@themes, {
:global(#root.@{value}) {
.link {
color: ~'@{color-@{value}-theme-font}' !important;
&.active {
background-color: ~'@{color-@{value}-theme-active}';
}
&:hover:not(.active) {
background-color: ~'@{color-@{value}-theme-hover}';
}
&:active:not(.active) {
background-color: ~'@{color-@{value}-theme-active}';
}
}
}
})
</style>
@@ -0,0 +1,64 @@
<template>
<div :class="$style.aside">
<ControlBtns v-if="setting.controlBtnPosition == 'left'" />
<div :class="$style.logo" v-else>L X</div>
<NavBar />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import ControlBtns from './ControlBtns'
import NavBar from './NavBar'
export default {
name: 'CoreAside',
components: { ControlBtns, NavBar },
computed: {
...mapGetters(['setting']),
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.aside {
// box-shadow: 0 0 5px rgba(0, 0, 0, .3);
transition: @transition-theme;
transition-property: background-color;
background-color: @color-theme-sidebar;
// background-color: @color-aside-background;
// border-right: 2px solid @color-theme;
-webkit-app-region: drag;
-webkit-user-select: none;
display: flex;
flex-flow: column nowrap;
}
.logo {
box-sizing: border-box;
padding: 0 13%;
height: 50px;
color: @color-theme-font;
flex: none;
text-align: center;
line-height: 50px;
font-weight: bold;
// -webkit-app-region: no-drag;
}
each(@themes, {
:global(#root.@{value}) {
.aside {
background-color: ~'@{color-@{value}-theme-sidebar}';
}
.logo {
color: ~'@{color-@{value}-theme-font}';
}
}
})
</style>
+12
View File
@@ -162,10 +162,18 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
//- 0 0 24 24
path(d="M20,14H4V10H20")
g#icon-window-minimize-2(fill='currentColor')
//- 0 0 24 24
path(d="M19,13H5V11H19V13Z")
g#icon-window-close(fill='currentColor')
//- 0 0 24 24
path(d="M20 6.91L17.09 4L12 9.09L6.91 4L4 6.91L9.09 12L4 17.09L6.91 20L12 14.91L17.09 20L20 17.09L14.91 12L20 6.91Z")
g#icon-window-close-2(fill='currentColor')
//- 0 0 24 24
path(d="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z")
g#icon-list-loop(fill='currentColor')
// 0 0 24 24
@@ -222,5 +230,9 @@ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/19
// 0 0 24 24
path(fill='currentColor', d='M21,6V8H3V6H21M3,18H12V16H3V18M3,13H21V11H3V13Z')
g#icon-audio-wave(fill='currentColor')
// 0 0 24 24
path(fill='currentColor', d='M22 12L20 13L19 14L18 13L17 16L16 13L15 21L14 13L13 15L12 13L11 17L10 13L9 22L8 13L7 19L6 13L5 14L4 13L2 12L4 11L5 10L6 11L7 5L8 11L9 2L10 11L11 7L12 11L13 9L14 11L15 3L16 11L17 8L18 11L19 10L20 11L22 12Z')
</template>
@@ -1,5 +1,5 @@
<template lang="pug">
material-modal(:show="!setting.isAgreePact || globalObj.isShowPact" @close="handleClose(false)" :bgClose="setting.isAgreePact" :close-btn="setting.isAgreePact")
material-modal(:show="!setting.isAgreePact || isShowPact" @close="handleClose(false)" :bgClose="setting.isAgreePact" :close-btn="setting.isAgreePact")
main(:class="$style.main")
h2 许可协议
div.select.scroll(:class="$style.content")
@@ -51,21 +51,25 @@ material-modal(:show="!setting.isAgreePact || globalObj.isShowPact" @close="hand
div(:class="$style.btns" v-if="!setting.isAgreePact")
material-btn(:class="$style.btn" @click="handleClose(true)") 不接受
material-btn(:class="$style.btn" :disabled="!btnEnable" @click="handleClick()") 接受 {{timeStr}}
base-btn(:class="$style.btn" @click="handleClose(true)") {{$t('not_agree')}}
base-btn(:class="$style.btn" :disabled="!btnEnable" @click="handleClick()") {{$t('agree')}} {{timeStr}}
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend, NAMES } from '../../../common/ipc'
import { openUrl } from '../../utils'
import { rendererSend, NAMES } from '@common/ipc'
import { openUrl } from '@renderer/utils'
import { isShowPact } from '@renderer/core/share'
export default {
setup() {
return {
isShowPact,
}
},
data() {
return {
time: 20,
globalObj: {
isShowPact: false,
},
}
},
computed: {
@@ -86,7 +90,6 @@ export default {
},
mounted() {
this.$nextTick(() => {
this.globalObj = window.globalObj
if (!this.setting.isAgreePact) {
this.startTimeout()
}
@@ -101,11 +104,11 @@ export default {
message: Buffer.from('e69cace8bdafe4bbb6e5ae8ce585a8e5858de8b4b9e4b894e5bc80e6ba90efbc8ce5a682e69e9ce4bda0e698afe88ab1e992b1e8b4ade4b9b0e79a84efbc8ce8afb7e79bb4e68ea5e7bb99e5b7aee8af84efbc810a0a5468697320736f667477617265206973206672656520616e64206f70656e20736f757263652e', 'hex').toString(),
confirmButtonText: Buffer.from('e5a5bde79a8420284f4b29', 'hex').toString(),
})
}, 2000)
}, 2e3)
},
handleClose(isExit) {
if (isExit) return rendererSend(NAMES.mainWindow.close, true)
this.globalObj.isShowPact = false
isShowPact.value = false
},
openUrl(url) {
openUrl(url)
@@ -113,7 +116,7 @@ export default {
startTimeout() {
setTimeout(() => {
if (--this.time > 0) this.startTimeout()
}, 1000)
}, 1e3)
},
},
}
@@ -121,7 +124,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.main {
padding: 15px;
@@ -168,7 +171,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
@@ -0,0 +1,129 @@
<template>
<div :class="$style.controlBtn">
<common-volume-bar :setting="setting" />
<div :class="$style.titleBtn" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :tips="toggleDesktopLyricBtnTitle">
<svg v-show="setting.desktopLyric.enable" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
<use xlink:href="#icon-desktop-lyric-on"></use>
</svg>
<svg v-show="!setting.desktopLyric.enable" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 512 512" space="preserve">
<use xlink:href="#icon-desktop-lyric-off"></use>
</svg>
</div>
<div :class="$style.titleBtn" @click="toggleNextPlayMode" :tips="nextTogglePlayName">
<svg v-show="setting.player.togglePlayMethod == 'listLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-list-loop"></use>
</svg>
<svg v-show="setting.player.togglePlayMethod == 'random'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-list-random"></use>
</svg>
<svg v-show="setting.player.togglePlayMethod == 'list'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="120%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-list-order"></use>
</svg>
<svg v-show="setting.player.togglePlayMethod == 'singleLoop'" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-single-loop"></use>
</svg>
<svg v-show="!setting.player.togglePlayMethod" version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" width="120%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-single"></use>
</svg>
</div>
<div :class="$style.titleBtn" @click="addMusicTo" :tips="$t('player__add_music_to')">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="80%" viewBox="0 0 512 512" space="preserve">
<use xlink:href="#icon-add-2"></use>
</svg>
</div>
<common-list-add-modal v-model:show="isShowAddMusicTo" :musicInfo="musicInfoItem" />
</div>
</template>
<script>
import { useI18n, ref, useRefGetter } from '@renderer/utils/vueTools'
import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
import { musicInfo, musicInfoItem } from '@renderer/core/share/player'
export default {
setup() {
const { t } = useI18n()
const isShowAddMusicTo = ref(false)
const setting = useRefGetter('setting')
const {
nextTogglePlayName,
toggleNextPlayMode,
} = useNextTogglePlay({ setting, t })
const {
toggleDesktopLyricBtnTitle,
toggleDesktopLyric,
toggleLockDesktopLyric,
} = useToggleDesktopLyric({ setting, t })
const addMusicTo = () => {
if (!musicInfo.songmid) return
isShowAddMusicTo.value = true
}
return {
setting: setting,
isShowAddMusicTo,
nextTogglePlayName,
toggleNextPlayMode,
toggleDesktopLyricBtnTitle,
toggleDesktopLyric,
toggleLockDesktopLyric,
addMusicTo,
musicInfoItem,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.controlBtn {
flex: none;
display: flex;
flex-flow: row nowrap;
}
.titleBtn {
flex: none;
margin-left: 5px;
height: 100%;
width: 20px;
color: @color-btn;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
transition: opacity 0.2s ease;
opacity: .6;
cursor: pointer;
svg {
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:hover {
opacity: 1;
}
&:active {
opacity: 1;
}
}
each(@themes, {
:global(#root.@{value}) {
.titleBtn {
color: ~'@{color-@{value}-btn}';
}
}
})
</style>
@@ -0,0 +1,363 @@
<template lang="pug">
div(:class="$style.player")
div(:class="$style.left" @contextmenu="handleToMusicLocation" @click="showPlayerDetail" :tips="$t('player__pic_tip')")
img(v-if="musicInfo.img" :src="musicInfo.img" @error="imgError")
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='102%' width='100%' viewBox='0 0 60 60' space='preserve')
use(:xlink:href='`#${$style.iconPic}`')
div(:class="$style.middle")
div(:class="$style.column1")
div(:class="$style.container")
div(:class="$style.title" @click="handleCopy(title)" :tips="title + $t('copy_tip')") {{title}}
control-btns
div(:class="$style.column2")
common-progress-bar(:progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition")
div(:class="$style.column3")
span(:class="$style.statusText") {{statusText}}
span {{nowPlayTimeStr}}
span(style="margin: 0 5px;") /
span {{maxPlayTimeStr}}
div(:class="$style.right")
div(:class="$style.playBtn" @click='playPrev' :tips="$t('player__prev')" style="transform: rotate(180deg);")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :tips="isPlay ? $t('player__pause') : $t('player__play')" @click='togglePlay')
svg(v-if="isPlay" 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')
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.playBtn" @click='playNext' :tips="$t('player__next')")
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')
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style="display: none;")
g(:id="$style.iconPic")
path(d='M29,0C12.984,0,0,12.984,0,29c0,16.016,12.984,29,29,29s29-12.984,29-29C58,12.984,45.016,0,29,0zM29,36.08c-3.91,0-7.08-3.17-7.08-7.08c0-3.91,3.17-7.08,7.08-7.08s7.08,3.17,7.08,7.08C36.08,32.91,32.91,36.08,29,36.08z')
path(:class="$style.c1" d='M6.487,22.932c-0.077,0-0.156-0.009-0.234-0.027c-0.537-0.13-0.868-0.67-0.739-1.206c0.946-3.935,2.955-7.522,5.809-10.376s6.441-4.862,10.376-5.809c0.536-0.127,1.077,0.202,1.206,0.739c0.129,0.536-0.202,1.076-0.739,1.206c-3.575,0.859-6.836,2.685-9.429,5.277s-4.418,5.854-5.277,9.429C7.349,22.624,6.938,22.932,6.487,22.932z')
path(:class="$style.c1" d='M36.066,52.514c-0.451,0-0.861-0.308-0.972-0.767c-0.129-0.536,0.202-1.076,0.739-1.206c3.576-0.859,6.837-2.685,9.43-5.277s4.418-5.854,5.277-9.429c0.129-0.538,0.668-0.868,1.206-0.739c0.537,0.13,0.868,0.67,0.739,1.206c-0.946,3.935-2.955,7.522-5.809,10.376s-6.441,4.862-10.377,5.809C36.223,52.505,36.144,52.514,36.066,52.514z')
path(:class="$style.c1" d='M11.313,24.226c-0.075,0-0.151-0.008-0.228-0.026c-0.538-0.125-0.873-0.663-0.747-1.2c0.72-3.09,2.282-5.904,4.52-8.141c2.236-2.237,5.051-3.8,8.141-4.52c0.535-0.131,1.075,0.209,1.2,0.747c0.126,0.537-0.209,1.075-0.747,1.2c-2.725,0.635-5.207,2.014-7.18,3.986s-3.352,4.455-3.986,7.18C12.179,23.914,11.768,24.226,11.313,24.226z')
path(:class="$style.c1" d='M34.773,47.688c-0.454,0-0.865-0.312-0.973-0.773c-0.126-0.537,0.209-1.075,0.747-1.2c2.725-0.635,5.207-2.014,7.18-3.986s3.352-4.455,3.986-7.18c0.125-0.538,0.662-0.88,1.2-0.747c0.538,0.125,0.873,0.663,0.747,1.2c-0.72,3.09-2.282,5.904-4.52,8.141c-2.236,2.237-5.051,3.8-8.141,4.52C34.925,47.68,34.849,47.688,34.773,47.688z')
path(:class="$style.c1" d='M16.14,25.519c-0.071,0-0.143-0.008-0.215-0.023c-0.539-0.118-0.881-0.651-0.763-1.19c0.997-4.557,4.586-8.146,9.143-9.143c0.537-0.116,1.071,0.222,1.19,0.763c0.118,0.539-0.224,1.072-0.763,1.19c-3.796,0.831-6.786,3.821-7.617,7.617C17.013,25.2,16.6,25.519,16.14,25.519z')
path(:class="$style.c1" d='M33.48,42.861c-0.46,0-0.873-0.318-0.976-0.786c-0.118-0.539,0.224-1.072,0.763-1.19c3.796-0.831,6.786-3.821,7.617-7.617c0.118-0.541,0.65-0.881,1.19-0.763c0.539,0.118,0.881,0.651,0.763,1.19c-0.997,4.557-4.586,8.146-9.143,9.143C33.623,42.854,33.552,42.861,33.48,42.861z')
path(:class="$style.c2" d='M29,38.08c-5.007,0-9.08-4.073-9.08-9.08s4.073-9.08,9.08-9.08s9.08,4.073,9.08,9.08S34.007,38.08,29,38.08z M29,23.92c-2.801,0-5.08,2.279-5.08,5.08s2.279,5.08,5.08,5.08s5.08-2.279,5.08-5.08S31.801,23.92,29,23.92z')
</template>
<script>
import { useRefGetter, computed, useRouter } from '@renderer/utils/vueTools'
import { clipboardWriteText } from '@renderer/utils'
import { player as eventPlayerNames } from '@renderer/event/names'
import ControlBtns from './ControlBtns'
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
// import { lyric } from '@renderer/core/share/lyric'
import { statusText, musicInfo, setMusicInfo, setShowPlayerDetail, isPlay, musicInfoItem, playInfo, playMusicInfo } from '@renderer/core/share/player'
export default {
name: 'CorePlayBar',
components: {
ControlBtns,
},
data() {
return {
isShowAddMusicTo: false,
}
},
setup() {
const setting = useRefGetter('setting')
const router = useRouter()
const {
nowPlayTimeStr,
maxPlayTimeStr,
progress,
isActiveTransition,
handleTransitionEnd,
} = usePlayProgress()
const showPlayerDetail = () => {
if (!musicInfoItem.value.songmid) return
setShowPlayerDetail(true)
}
const handleCopy = (text) => {
clipboardWriteText(text)
}
const imgError = () => {
// console.log(e)
setMusicInfo({ img: null })
}
const handleToMusicLocation = () => {
const listId = playMusicInfo.listId
if (!listId || listId == '__temp__' || listId == 'download' || !musicInfoItem.value.songmid) return
if (playInfo.playIndex == -1) return
router.push({
path: 'list',
query: {
id: listId,
scrollIndex: playInfo.playIndex,
},
})
}
const title = computed(() => {
return musicInfo.name
? setting.value.download.fileName.replace('歌名', musicInfo.name).replace('歌手', musicInfo.singer)
: '^-^'
})
const togglePlay = () => {
window.eventHub.emit(eventPlayerNames.setTogglePlay)
}
const playNext = () => {
window.eventHub.emit(eventPlayerNames.setPlayNext)
}
const playPrev = () => {
window.eventHub.emit(eventPlayerNames.setPlayPrev)
}
// onBeforeUnmount(() => {
// window.eventHub.emit(eventPlayerNames.setTogglePlay)
// })
return {
musicInfo,
nowPlayTimeStr,
maxPlayTimeStr,
progress,
isActiveTransition,
handleTransitionEnd,
handleCopy,
imgError,
statusText,
title,
showPlayerDetail,
isPlay,
togglePlay,
setting,
playNext,
playPrev,
handleToMusicLocation,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.player {
height: @height-player;
background-color: @color-theme_2;
transition: @transition-theme;
transition-property: border-color;
border-top: 2px solid @color-theme;
box-sizing: border-box;
display: flex;
contain: strict;
z-index: 2;
* {
box-sizing: border-box;
}
}
.left {
width: @height-player - 2;
height: 100%;
color: @color-theme;
transition: @transition-theme;
transition-property: color;
flex: none;
padding: 2PX;
opacity: 1;
transition: @transition-theme;
transition-property: opacity;
display: flex;
justify-content: center;
// align-items: center;
cursor: pointer;
&:hover {
opacity: .8;
}
svg {
fill: currentColor;
}
img {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
max-width: 100%;
max-height: 100%;
transition: @transition-theme;
transition-property: border-color;
// border-radius: 50%;
border-radius: @radius-border;
// border: 2px solid @color-theme_2-background_1;
}
}
.middle {
flex: auto;
height: 100%;
padding: 5px 10px 5px 8px;
display: flex;
flex-flow: column nowrap;
}
.right {
height: 100%;
flex: none;
display: flex;
flex-flow: row nowrap;
align-items: center;
padding-left: 15px;
padding-right: 20px;
}
.column1 {
flex: auto;
position: relative;
font-size: 16px;
.container {
position: absolute;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
}
.title {
flex: 0 1 auto;
min-width: 0;
padding-right: 5px;
font-size: 14px;
line-height: 18px;
.mixin-ellipsis-1;
}
.play-btn {
+ .play-btn {
margin-left: 15px;
}
flex: none;
height: 46%;
// margin-top: -2px;
// transition: @transition-theme;
// transition-property: color;
// color: @color-theme;
transition: opacity 0.2s ease;
opacity: 1;
cursor: pointer;
svg {
fill: currentColor;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
.column2 {
flex: none;
padding: 3px 0;
position: relative;
}
.column3 {
transition: @transition-theme;
transition-property: color;
color: @color-theme_2-font;
flex: none;
font-size: 12px;
display: flex;
padding-top: 2px;
// justify-content: space-between;
height: 16px;
align-items: center;
}
.status-text {
font-size: 0.98em;
transition: @transition-theme;
transition-property: color;
color: @color-player-status-text;
.mixin-ellipsis-1;
// padding: 0 5px;
padding-right: 5px;
flex: 1 1 0;
// text-align: center;
line-height: 1.2;
width: 0;
}
#icon-pic {
color: @color-theme;
.c1 {
transition: @transition-theme;
transition-property: fill;
fill: @color-player-pic-c1;
}
.c2 {
transition: @transition-theme;
transition-property: fill;
fill: @color-player-pic-c2;
}
}
each(@themes, {
:global(#root.@{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-background_1}';
// }
}
.titleBtn {
color: ~'@{color-@{value}-btn}';
}
.play-btn {
color: ~'@{color-@{value}-btn}';
svg {
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3));
}
}
.volume {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.volume-bar {
background-color: ~'@{color-@{value}-btn}';
}
.column3 {
color: ~'@{color-@{value}-theme_2-font}';
}
.status-text {
color: ~'@{color-@{value}-player-status-text}';
}
#icon-pic {
color: ~'@{color-@{value}-theme}';
.c1 {
fill: ~'@{color-@{value}-player-pic-c1}';
}
.c2 {
fill: ~'@{color-@{value}-player-pic-c2}';
}
}
}
})
</style>
@@ -0,0 +1,230 @@
<template>
<div :class="['right', $style.right]">
<div :class="['lyric', $style.lyric, { [$style.draging]: isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric">
<div :class="$style.lyricSpace"></div>
<div :class="[$style.lyricText]" ref="dom_lyric_text"></div>
<div :class="$style.lyricSpace"></div>
</div>
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
<div :class="[$style.lyricSelectContent, 'select', 'scroll']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText">
<div v-for="(info, index) in lyric.lines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]">
<span>{{info.text}}</span>
<br v-if="info.translation"/>
<span :class="$style.lyricSelectlineTransition">{{info.translation}}</span>
</div>
</div>
</transition>
</div>
</template>
<script>
import { clipboardWriteText } from '@renderer/utils'
import { lyric } from '@renderer/core/share/lyric'
import { isPlay, isShowLrcSelectContent } from '@renderer/core/share/player'
// import { ref } from '@renderer/utils/vueTools'
import useLyric from '@renderer/utils/compositions/useLyric'
export default {
setup() {
const {
dom_lyric,
dom_lyric_text,
isMsDown,
handleLyricMouseDown,
handleWheel,
} = useLyric({ isPlay, lyric })
return {
dom_lyric,
dom_lyric_text,
isMsDown,
handleLyricMouseDown,
handleWheel,
lyric,
isShowLrcSelectContent,
}
},
methods: {
handleCopySelectText() {
let str = window.getSelection().toString()
str = str.trim()
if (!str.length) return
clipboardWriteText(str)
},
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.right {
flex: 0 0 60%;
// padding: 0 30px;
position: relative;
transition: flex-basis @transition-theme;
&:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
&:after {
position: absolute;
bottom: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
}
.lyric {
text-align: center;
height: 100%;
overflow: hidden;
font-size: 16px;
cursor: grab;
&.draging {
cursor: grabbing;
}
:global {
.lrc-content {
line-height: 1.2;
margin: 16px 0;
overflow-wrap: break-word;
color: @color-player-detail-lyric;
.translation {
transition: @transition-theme !important;
transition-property: font-size, color;
font-size: .9em;
margin-top: 5px;
}
.line {
transition-property: font-size, color !important;
background: none !important;
-webkit-text-fill-color: unset;
// -webkit-text-fill-color: none !important;
}
&.active {
.line {
color: @color-theme;
}
.translation {
font-size: .94em;
color: @color-theme;
}
span {
// color: @color-theme;
font-size: 1.1em;
}
}
span {
transition: @transition-theme !important;
transition-property: font-size !important;
font-size: 1em;
background-repeat: no-repeat;
background-color: rgba(77, 77, 77, 0.9);
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
}
}
// p {
// padding: 8px 0;
// line-height: 1.2;
// overflow-wrap: break-word;
// transition: @transition-theme !important;
// transition-property: color, font-size;
// }
// .lrc-active {
// color: @color-theme;
// font-size: 1.2em;
// }
}
.lyricSelectContent {
position: absolute;
left: 0;
top: 0;
// text-align: center;
height: 100%;
width: 100%;
font-size: 16px;
background-color: @color-theme_2-background_1;
z-index: 10;
color: @color-player-detail-lyric;
.lyricSelectline {
padding: 8px 0;
overflow-wrap: break-word;
transition: @transition-theme !important;
transition-property: color, font-size;
line-height: 1.3;
}
.lyricSelectlineTransition {
font-size: 14px;
}
.lrc-active {
color: @color-theme;
}
}
.lyric-space {
height: 70%;
}
each(@themes, {
:global(#root.@{value}) {
.right {
&:before {
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
&:after {
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
}
.lyric {
:global {
.lrc-content {
color: ~'@{color-@{value}-player-detail-lyric}';
&.active {
.translation {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
.line {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
}
span {
background-color: ~'@{color-@{value}-player-detail-lyric}';
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-player-detail-lyric-active}', ~'@{color-@{value}-player-detail-lyric-active}');
}
}
}
}
// .lrc-active {
// color: ~'@{color-@{value}-theme}';
// }
.lyricSelectContent {
background-color: ~'@{color-@{value}-theme_2-background_1}';
color: ~'@{color-@{value}-player-detail-lyric}';
.lrc-active {
color: ~'@{color-@{value}-theme}';
}
}
}
})
</style>
@@ -0,0 +1,160 @@
<template lang="pug">
div(:class="$style.footer")
div(:class="$style.footerLeft")
control-btns
div(:class="$style.progressContainer")
div(:class="$style.progressContent")
common-progress-bar(:class-name="$style.progress" :progress="progress" :handleTransitionEnd="handleTransitionEnd" :isActiveTransition="isActiveTransition")
div(:class="$style.timeLabel")
span(style="margin-left: 15px") {{status}}
div
span {{nowPlayTimeStr}}
span(style="margin: 0 5px;") /
span {{maxPlayTimeStr}}
div(:class="$style.playControl")
div(:class="$style.playBtn" @click="playPrev" style="transform: rotate(180deg);" :tips="$t('player__prev')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" @click="togglePlay" :tips="isPlay ? $t('player__pause') : $t('player__play')")
svg(v-if="isPlay" 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')
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.playBtn" @click="playNext" :tips="$t('player__next')")
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')
</template>
<script>
import { status, isPlay } from '@renderer/core/share/player'
import usePlayProgress from '@renderer/utils/compositions/usePlayProgress'
import { player as eventPlayerNames } from '@renderer/event/names'
import ControlBtns from './components/ControlBtns'
export default {
components: {
ControlBtns,
},
setup() {
const {
nowPlayTimeStr,
maxPlayTimeStr,
progress,
isActiveTransition,
handleTransitionEnd,
} = usePlayProgress()
return {
nowPlayTimeStr,
maxPlayTimeStr,
status,
progress,
isActiveTransition,
handleTransitionEnd,
isPlay,
togglePlay() {
window.eventHub.emit(eventPlayerNames.setTogglePlay)
},
playNext() {
window.eventHub.emit(eventPlayerNames.setPlayNext)
},
playPrev() {
window.eventHub.emit(eventPlayerNames.setPlayPrev)
},
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.footer {
flex: 0 0 100px;
overflow: hidden;
display: flex;
align-items: center;
}
.footerLeft {
flex: auto;
display: flex;
flex-flow: column nowrap;
padding: 13px;
overflow: hidden;
}
.progress-container {
width: 100%;
position: relative;
padding: 3px 0;
}
.progress-content {
position: relative;
height: 15px;
padding: 5px 0;
width: 100%;
}
.progress {
height: 100%;
}
.bar-transition {
transition-property: transform;
transition-timing-function: ease-out;
transition-duration: 0.2s;
}
.time-label {
width: 100%;
height: 18px;
display: flex;
justify-content: space-between;
span {
font-size: 13px;
}
}
.play-control {
flex: none;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 25px;
color: @color-btn;
}
.play-btn {
height: 40%;
padding: 5px;
cursor: pointer;
flex: none;
// transition: @transition-theme;
// transition-property: color;
color: @color-player-detail-play-btn;
transition: opacity 0.2s ease;
opacity: 1;
cursor: pointer;
+.play-btn {
margin-left: 10px;
}
svg {
fill: currentColor;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
each(@themes, {
:global(#root.@{value}) {
.play-btn {
color: ~'@{color-@{value}-player-detail-play-btn}';
}
}
})
</style>
@@ -0,0 +1,150 @@
<template lang="pug">
div(:class="$style.footerLeftControlBtns")
common-volume-bar(:setting="setting")
div(:class="[$style.footerLeftControlBtn, $style.lrcBtn]" @click="toggleDesktopLyric" @contextmenu="toggleLockDesktopLyric" :tips="toggleDesktopLyricBtnTitle")
svg(v-show="setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-desktop-lyric-on')
svg(v-show="!setting.desktopLyric.enable" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='125%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-desktop-lyric-off')
div(:class="[$style.footerLeftControlBtn, { [$style.active]: setting.player.audioVisualization }]" @click="toggleAudioVisualization" :tips="$t('audio_visualization')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-audio-wave')
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="toggleVisibleLrc" :tips="$t('lyric__select')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-text')
div(:class="[$style.footerLeftControlBtn, {[$style.active]: isShowPlayComment}]" @click="toggleVisibleComment" :tips="$t('comment__show')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-comment')
div(:class="$style.footerLeftControlBtn" @click="toggleNextPlayMode" :tips="nextTogglePlayName")
svg(v-show="setting.player.togglePlayMethod == 'listLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-loop')
svg(v-show="setting.player.togglePlayMethod == 'random'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-random')
svg(v-show="setting.player.togglePlayMethod == 'list'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-order')
svg(v-show="setting.player.togglePlayMethod == 'singleLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='110%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-single-loop')
svg(v-show="!setting.player.togglePlayMethod" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-single')
div(:class="$style.footerLeftControlBtn" @click="isShowAddMusicTo = true" :tips="$t('player__add_music_to')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-add-2')
common-list-add-modal(v-model:show="isShowAddMusicTo" :musicInfo="musicInfoItem")
</template>
<script>
import { useI18n, useRefGetter, ref, useCommit } from '@renderer/utils/vueTools'
import {
isShowLrcSelectContent,
setShowPlayLrcSelectContentLrc,
isShowPlayComment,
setShowPlayComment,
musicInfoItem,
} from '@renderer/core/share/player'
import useNextTogglePlay from '@renderer/utils/compositions/useNextTogglePlay'
import useToggleDesktopLyric from '@renderer/utils/compositions/useToggleDesktopLyric'
export default {
setup() {
const { t } = useI18n()
const setting = useRefGetter('setting')
const setAudioVisualization = useCommit('setAudioVisualization')
const toggleVisibleLrc = () => {
setShowPlayLrcSelectContentLrc(!isShowLrcSelectContent.value)
}
const toggleVisibleComment = () => {
setShowPlayComment(!isShowPlayComment.value)
}
const {
nextTogglePlayName,
toggleNextPlayMode,
} = useNextTogglePlay({ setting, t })
const {
toggleDesktopLyricBtnTitle,
toggleDesktopLyric,
toggleLockDesktopLyric,
} = useToggleDesktopLyric({ setting, t })
const isShowAddMusicTo = ref(false)
const toggleAudioVisualization = () => {
setAudioVisualization(!setting.value.player.audioVisualization)
}
return {
setting,
isShowLrcSelectContent,
toggleVisibleLrc,
isShowPlayComment,
toggleVisibleComment,
nextTogglePlayName,
toggleNextPlayMode,
toggleDesktopLyricBtnTitle,
toggleDesktopLyric,
toggleLockDesktopLyric,
toggleAudioVisualization,
isShowAddMusicTo,
musicInfoItem,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.footerLeftControlBtns {
color: @color-theme_2-font;
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
.footerLeftControlBtn {
width: 18px;
height: 18px;
opacity: .5;
cursor: pointer;
transition: opacity @transition-theme;
display: flex;
align-items: center;
justify-content: center;
&:hover {
opacity: .9;
}
+.footerLeftControlBtn {
margin-left: 6px;
}
&.active {
color: @color-theme;
opacity: .8;
}
}
.lrcBtn {
width: 20px;
}
}
each(@themes, {
:global(#root.@{value}) {
.footerLeftControlBtns {
color: ~'@{color-@{value}-theme_2-font}';
}
.footerLeftControlBtn {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
}
})
</style>
@@ -1,27 +1,28 @@
<template lang="pug">
div(:class="$style.container")
ul
li(v-for="(item, index) in comments" :key="item.id" :class="$style.listItem")
div(:class="$style.content")
div(:class="$style.left")
img( :class="$style.avatar" :src="item.avatar || commentDefImg" @error="handleUserImg")
div(:class="$style.right")
div(:class="$style.info")
div.select(:class="$style.name") {{item.userName}}
time(:class="$style.time" v-if="item.timeStr") {{timeFormat(item.timeStr)}}
div(:class="$style.likes" v-if="item.likedCount != null")
svg(:class="$style.likesIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-thumbs-up')
| {{item.likedCount}}
div.select(:class="$style.comment_text")
p(v-for="text in item.text") {{text}}
material-comment-floor(v-if="item.reply && item.reply.length" :class="$style.reply_floor" :comments="item.reply")
div(:class="$style.container")
ul
li(v-for="(item, index) in comments" :key="item.id" :class="$style.listItem")
div(:class="$style.content")
div(:class="$style.left")
img( :class="$style.avatar" :src="item.avatar || commentDefImg" @error="handleUserImg")
div(:class="$style.right")
div(:class="$style.info")
div.select(:class="$style.name") {{item.userName}}
time(:class="$style.time" v-if="item.timeStr") {{timeFormat(item.timeStr)}}
div(:class="$style.likes" v-if="item.likedCount != null")
svg(:class="$style.likesIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-thumbs-up')
| {{item.likedCount}}
div.select(:class="$style.comment_text")
p(v-for="text in item.text") {{text}}
comment-floor(v-if="item.reply && item.reply.length" :class="$style.reply_floor" :comments="item.reply")
</template>
<script>
import commentDefImg from '../../assets/images/defaultUser.jpg'
import commentDefImg from '@renderer/assets/images/defaultUser.jpg'
export default {
name: 'commentFloor',
props: {
comments: {
type: Array,
@@ -48,7 +49,7 @@ export default {
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
@padding: 15px;
@@ -140,7 +141,7 @@ export default {
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.listItem {
border-bottom-color: ~'@{color-@{value}-theme_2-active}';
}
@@ -1,48 +1,51 @@
<template lang="pug">
div(:class="$style.comment")
div.comment(:class="$style.comment")
div(:class="$style.commentHeader")
h3 {{$t('core.player.comment_title', { name: title })}}
h3 {{$t('comment__title', { name: title })}}
div(:class="$style.commentHeaderBtns")
div(:class="$style.commentHeaderBtn" @click="handleShowComment" :tips="$t('core.player.comment_refresh')")
div(:class="$style.commentHeaderBtn" @click="handleShowComment" :tips="$t('comment__refresh')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' style='transform: rotate(45deg);' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-refresh')
div(:class="$style.commentHeaderBtn" @click="$emit('input', false)")
div(:class="$style.commentHeaderBtn" @click="$emit('close')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-close')
div.scroll(:class="$style.commentMain" ref="dom_comment")
div(v-if="page == 1")
h2(:class="$style.commentType") {{$t('core.player.comment_hot_title')}}
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="isHotLoadError" @click="handleGetHotComment(currentMusicInfo)") {{$t('core.player.comment_hot_load_error')}}
p(:class="$style.commentLabel" v-else-if="isHotLoading && !hotComments.length") {{$t('core.player.comment_hot_loading')}}
material-comment-floor(v-if="!isHotLoadError && hotComments.length" :class="[$style.commentFloor, isHotLoading ? $style.loading : null]" :comments="hotComments")
p(:class="$style.commentLabel" v-else-if="!isHotLoadError && !isHotLoading") {{$t('core.player.comment_no_content')}}
h2(:class="$style.commentType") {{$t('comment__hot_title')}}
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="isHotLoadError" @click="handleGetHotComment(currentMusicInfo)") {{$t('comment__hot_load_error')}}
p(:class="$style.commentLabel" v-else-if="isHotLoading && !hotComments.length") {{$t('comment__hot_loading')}}
comment-floor(v-if="!isHotLoadError && hotComments.length" :class="[$style.commentFloor, isHotLoading ? $style.loading : null]" :comments="hotComments")
p(:class="$style.commentLabel" v-else-if="!isHotLoadError && !isHotLoading") {{$t('comment__no_content')}}
div
h2(:class="$style.commentType") {{$t('core.player.comment_new_title')}} ({{total}})
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="isNewLoadError" @click="handleGetNewComment(currentMusicInfo, nextPage, limit)") {{$t('core.player.comment_new_load_error')}}
p(:class="$style.commentLabel" v-else-if="isNewLoading && !newComments.length") {{$t('core.player.comment_new_loading')}}
material-comment-floor(v-if="!isNewLoadError && newComments.length" :class="[$style.commentFloor, isNewLoading ? $style.loading : null]" :comments="newComments")
p(:class="$style.commentLabel" v-else-if="!isNewLoadError && !isNewLoading") {{$t('core.player.comment_no_content')}}
h2(:class="$style.commentType") {{$t('comment__new_title')}} ({{total}})
p(:class="$style.commentLabel" style="cursor: pointer;" v-if="isNewLoadError" @click="handleGetNewComment(currentMusicInfo, nextPage, limit)") {{$t('comment__new_load_error')}}
p(:class="$style.commentLabel" v-else-if="isNewLoading && !newComments.length") {{$t('comment__new_loading')}}
comment-floor(v-if="!isNewLoadError && newComments.length" :class="[$style.commentFloor, isNewLoading ? $style.loading : null]" :comments="newComments")
p(:class="$style.commentLabel" v-else-if="!isNewLoadError && !isNewLoading") {{$t('comment__no_content')}}
div(:class="$style.pagination")
material-pagination(:count="total" :btnLength="5" :limit="limit" :page="page" @btn-click="handleToggleCommentPage")
</template>
<script>
import { scrollTo } from '../../utils'
import music from '../../utils/music'
import { mapGetters } from 'vuex'
import { scrollTo } from '@renderer/utils'
import music from '@renderer/utils/music'
import CommentFloor from './CommentFloor'
export default {
name: 'MusicComment',
props: {
value: Boolean,
titleFormat: {
type: String,
default: '歌名 - 歌手',
},
show: Boolean,
musicInfo: {
type: Object,
required: true,
},
},
components: {
CommentFloor,
},
emits: ['close'],
data() {
return {
currentMusicInfo: {
@@ -92,14 +95,15 @@ export default {
}
},
computed: {
...mapGetters(['setting']),
title() {
return this.currentMusicInfo.name
? this.titleFormat.replace('歌名', this.currentMusicInfo.name).replace('歌手', this.currentMusicInfo.singer)
? this.setting.download.fileName.replace('歌名', this.currentMusicInfo.name).replace('歌手', this.currentMusicInfo.singer)
: '^-^'
},
},
watch: {
value(n) {
show(n) {
if (n) this.handleShowComment()
},
},
@@ -179,7 +183,7 @@ export default {
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.comment {
display: flex;
@@ -253,7 +257,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.commentHeaderBtns {
color: ~'@{color-@{value}-theme}';
}
@@ -0,0 +1,598 @@
<template lang="pug">
transition(enter-active-class="animated lightSpeedIn" leave-active-class="animated slideOutDown" @after-enter="handleAfterEnter" @after-leave="handleAfterLeave")
div(:class="$style.container" @contextmenu="handleContextMenu" v-if="isShowPlayerDetail")
//- div(:class="$style.bg" :style="bgStyle")
//- div(:class="$style.bg2")
div(:class="[$style.header, $style.controlBtnLeft]" v-if="setting.controlBtnPosition == 'left'")
div(:class="$style.controBtn")
button(type="button" :class="$style.hide" :tips="$t('player__hide_detail_tip')" @click="hide")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve')
use(xlink:href='#icon-window-hide')
button(type="button" :class="$style.min" :tips="$t('min')" @click="min")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize')
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :tips="$t('close')" @click="close")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close')
div(:class="[$style.header, $style.controlBtnRight]" v-else)
div(:class="$style.controBtn")
button(type="button" :class="$style.hide" :tips="$t('player__hide_detail_tip')" @click="hide")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='35%' viewBox='0 0 30.727 30.727' space='preserve')
use(xlink:href='#icon-window-hide')
button(type="button" :class="$style.min" :tips="$t('min')" @click="min")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize-2')
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :tips="$t('close')" @click="close")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='60%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close-2')
div(:class="[$style.main, {[$style.showComment]: isShowPlayComment}]")
div.left(:class="$style.left")
//- div(:class="$style.info")
div(:class="$style.info")
img(:class="$style.img" :src="musicInfo.img" v-if="musicInfo.img")
div.description(:class="$style.description")
p {{$t('player__music_name')}}{{musicInfo.name}}
p {{$t('player__music_singer')}}{{musicInfo.singer}}
p(v-if="musicInfo.album") {{$t('player__music_album')}}{{musicInfo.album}}
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
LyricPlayer(v-if="visibled")
music-comment(:class="$style.comment" :musicInfo="musicInfoItem" :show="isShowPlayComment" @close="hideComment" v-if="visibled")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
play-bar(v-if="visibled")
transition(enter-active-class="animated-slow fadeIn" leave-active-class="animated-slow fadeOut")
common-audio-visualizer(v-if="setting.player.audioVisualization && visibled")
</template>
<script>
import { useRefGetter, ref } from '@renderer/utils/vueTools'
import { base as eventBaseName } from '@renderer/event/names'
import {
isShowPlayerDetail,
setShowPlayerDetail,
isShowPlayComment,
setShowPlayComment,
setShowPlayLrcSelectContentLrc,
musicInfoItem,
musicInfo,
} from '@renderer/core/share/player'
import LyricPlayer from './LyricPlayer'
import PlayBar from './PlayBar'
import MusicComment from './components/MusicComment'
export default {
name: 'CorePlayDetail',
components: {
LyricPlayer,
PlayBar,
MusicComment,
},
setup() {
const setting = useRefGetter('setting')
const visibled = ref(false)
let clickTime = 0
const hide = () => {
setShowPlayerDetail(false)
}
const handleContextMenu = () => {
if (window.performance.now() - clickTime > 400) {
clickTime = window.performance.now()
return
}
clickTime = 0
hide()
}
const hideComment = () => {
setShowPlayComment(false)
}
const handleAfterEnter = () => {
visibled.value = true
}
const handleAfterLeave = () => {
setShowPlayLrcSelectContentLrc(false)
hideComment(false)
visibled.value = false
}
return {
setting,
isShowPlayerDetail,
isShowPlayComment,
musicInfoItem,
musicInfo,
hide,
handleContextMenu,
hideComment,
handleAfterEnter,
handleAfterLeave,
visibled,
min() {
window.eventHub.emit(eventBaseName.min)
},
max() {
window.eventHub.emit(eventBaseName.max)
},
close() {
window.eventHub.emit(eventBaseName.close)
},
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
@control-btn-width: @height-toolbar * .26;
.container {
position: absolute;
display: flex;
flex-flow: column nowrap;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: @color-theme_2-background_1;
z-index: 10;
// -webkit-app-region: drag;
overflow: hidden;
border-radius: @radius-border;
color: @color-theme_2-font;
border-left: 12px solid @color-theme;
-webkit-app-region: no-drag;
contain: strict;
box-sizing: border-box;
* {
box-sizing: border-box;
}
}
.bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-size: 110% 110%;
filter: blur(60px);
z-index: -1;
}
.bg2 {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
background-color: rgba(255, 255, 255, .8);
}
.header {
position: relative;
flex: 0 0 @height-toolbar;
-webkit-app-region: drag;
width: 100%;
.controBtn {
position: absolute;
top: 0;
display: flex;
-webkit-app-region: no-drag;
}
&.controlBtnLeft {
.controBtn {
align-items: center;
padding: 0 @control-btn-width;
left: 0;
flex-direction: row-reverse;
height: @height-toolbar * .7;
&:hover {
.controBtnIcon {
opacity: 1;
}
}
button {
position: relative;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
outline: none;
padding: 1px;
cursor: pointer;
border-radius: 50%;
color: @color-theme_2;
display: flex;
justify-content: center;
align-items: center;
+ button {
margin-right: (@control-btn-width / 2);
}
&.hide {
background-color: @color-hideBtn;
}
&.min {
background-color: @color-minBtn;
}
&.max {
background-color: @color-maxBtn;
}
&.close {
background-color: @color-closeBtn;
}
}
}
.controBtnIcon {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
}
&.controlBtnRight {
align-self: flex-start;
.controBtn {
right: 0;
button {
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 46px;
height: 30px;
background: none;
border: none;
outline: none;
padding: 1px;
cursor: pointer;
color: @color-theme;
transition: background-color 0.2s ease-in-out;
&:hover {
&.hide, &.min, &.max {
background-color: @color-btn-hover;
}
&.close {
background-color: @color-closeBtn;
}
}
}
}
}
}
.main {
flex: auto;
min-height: 0;
overflow: hidden;
display: flex;
margin: 0 30px;
position: relative;
&.showComment {
:global {
.left {
flex-basis: 18%;
.description p {
font-size: 12px;
}
}
.right {
flex-basis: 30%;
.lyric {
font-size: 13px;
}
}
.comment {
opacity: 1;
transform: scaleX(1);
}
}
}
}
.left {
flex: 0 0 40%;
display: flex;
flex-flow: column nowrap;
align-items: center;
padding: 13px;
overflow: hidden;
transition: flex-basis @transition-theme;
}
.info {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
max-width: 300px;
}
.img {
max-width: 100%;
max-height: 100%;
min-width: 100%;
box-shadow: 0 0 4px @color-theme-hover;
border-radius: 6px;
opacity: .8;
// border: 5px solid @color-theme-hover;
// border-radius: @radius-border;
// border: 5px solid #fff;
}
.description {
max-width: 300px;
padding: 15px 0;
overflow: hidden;
p {
line-height: 1.5;
font-size: 14px;
overflow-wrap: break-word;
}
}
.right {
flex: 0 0 60%;
// padding: 0 30px;
position: relative;
transition: flex-basis @transition-theme;
&:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
&:after {
position: absolute;
bottom: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
}
.lyric {
text-align: center;
height: 100%;
overflow: hidden;
font-size: 16px;
cursor: grab;
&.draging {
cursor: grabbing;
}
:global {
.lrc-content {
line-height: 1.2;
margin: 16px 0;
overflow-wrap: break-word;
color: @color-player-detail-lyric;
.translation {
transition: @transition-theme !important;
transition-property: font-size, color;
font-size: .9em;
margin-top: 5px;
}
.line {
transition-property: font-size, color !important;
background: none !important;
-webkit-text-fill-color: unset;
// -webkit-text-fill-color: none !important;
}
&.active {
.line {
color: @color-theme;
}
.translation {
font-size: 1em;
color: @color-theme;
}
span {
// color: @color-theme;
font-size: 1.2em;
}
}
span {
transition: @transition-theme !important;
transition-property: font-size !important;
font-size: 1em;
background-repeat: no-repeat;
background-color: rgba(77, 77, 77, 0.9);
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
}
}
// p {
// padding: 8px 0;
// line-height: 1.2;
// overflow-wrap: break-word;
// transition: @transition-theme !important;
// transition-property: color, font-size;
// }
// .lrc-active {
// color: @color-theme;
// font-size: 1.2em;
// }
}
.lyricSelectContent {
position: absolute;
left: 0;
top: 0;
// text-align: center;
height: 100%;
width: 100%;
font-size: 16px;
background-color: @color-theme_2-background_1;
z-index: 10;
color: @color-player-detail-lyric;
.lyricSelectline {
padding: 8px 0;
overflow-wrap: break-word;
transition: @transition-theme !important;
transition-property: color, font-size;
line-height: 1.3;
}
.lyricSelectlineTransition {
font-size: 14px;
}
.lrc-active {
color: @color-theme;
}
}
.lyric-space {
height: 70%;
}
.comment {
position: absolute;
right: 0;
top: 0;
width: 50%;
height: 100%;
opacity: 1;
margin-left: 10px;
transform: scaleX(0);
}
each(@themes, {
:global(#root.@{value}) {
.container {
border-left-color: ~'@{color-@{value}-theme}';
background-color: ~'@{color-@{value}-theme_2-background_1}';
// color: ~'@{color-@{value}-theme_2-font}';
}
.right {
&:before {
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
&:after {
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
}
.header {
&.controlBtnLeft {
.controBtn {
button {
color: ~'@{color-@{value}-theme_2}';
// &.hide:after {
// background-color: ~'@{color-@{value}-hideBtn}';
// }
&.hide {
background-color: ~'@{color-@{value}-hideBtn}';
}
&.min {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
}
&.controlBtnRight {
.controBtn {
button {
color: ~'@{color-@{value}-theme_2-font-label}';
&:hover {
&.hide, &.min, &.max {
background-color: ~'@{color-@{value}-btn-hover}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
}
}
}
.img {
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
// border-color: ~'@{color-@{value}-theme-hover}';
}
.lyric {
:global {
.lrc-content {
color: ~'@{color-@{value}-player-detail-lyric}';
&.active {
.translation {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
.line {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
}
span {
background-color: ~'@{color-@{value}-player-detail-lyric}';
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-player-detail-lyric-active}', ~'@{color-@{value}-player-detail-lyric-active}');
}
}
}
}
// .lrc-active {
// color: ~'@{color-@{value}-theme}';
// }
.lyricSelectContent {
background-color: ~'@{color-@{value}-theme_2-background_1}';
color: ~'@{color-@{value}-player-detail-lyric}';
.lrc-active {
color: ~'@{color-@{value}-theme}';
}
}
.footerLeftControlBtns {
color: ~'@{color-@{value}-theme_2-font}';
}
.footerLeftControlBtn {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
.progress {
background-color: ~'@{color-@{value}-player-progress}';
}
.progress-bar1 {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
}
.play-btn {
color: ~'@{color-@{value}-player-detail-play-btn}';
}
}
})
</style>
File diff suppressed because it is too large Load Diff
@@ -1,948 +0,0 @@
<template lang="pug">
div(:class="[$style.container, setting.controlBtnPosition == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]" @contextmenu="handleContextMenu")
//- div(:class="$style.bg" :style="bgStyle")
//- div(:class="$style.bg2")
div(:class="$style.header")
div(:class="$style.controBtn")
button(type="button" :class="$style.hide" :tips="$t('core.player.hide_detail')" @click="hide")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='80%' viewBox='0 0 30.727 30.727' space='preserve')
use(xlink:href='#icon-window-hide')
button(type="button" :class="$style.min" :tips="$t('core.toolbar.min')" @click="min")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize')
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :tips="$t('core.toolbar.close')" @click="close")
svg(:class="$style.controBtnIcon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close')
div(:class="[$style.main, isShowComment ? $style.showComment : null]")
div(:class="$style.left")
//- div(:class="$style.info")
div(:class="$style.info")
img(:class="$style.img" :src="musicInfo.img" v-if="musicInfo.img")
div(:class="$style.description")
p {{$t('core.player.name')}}{{musicInfo.name}}
p {{$t('core.player.singer')}}{{musicInfo.singer}}
p(v-if="musicInfo.album") {{$t('core.player.album')}}{{musicInfo.album}}
div(:class="$style.right")
div(:class="[$style.lyric, { [$style.draging]: lyricEvent.isMsDown }]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
div(:class="$style.lyricSpace")
div(:class="[$style.lyricText]" ref="dom_lyric_text")
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
div(:class="$style.lyricSpace")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
div(:class="[$style.lyricSelectContent, 'select', 'scroll']" v-if="isShowLrcSelectContent" @contextmenu="handleCopySelectText")
//- div(:class="$style.lyricSpace")
div(v-for="(info, index) in lyricLines" :key="index" :class="[$style.lyricSelectline, { [$style.lrcActive]: lyric.line == index }]")
span {{info.text}}
br(v-if="info.translation")
span(:class="$style.lyricSelectlineTransition") {{info.translation}}
//- div(:class="$style.lyricSpace")
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
div(:class="$style.footer")
div(:class="$style.footerLeft")
div(:class="$style.footerLeftControlBtns")
div(:class="[$style.footerLeftControlBtn, { [$style.active]: isShowLrcSelectContent }]" @click="isShowLrcSelectContent = !isShowLrcSelectContent" :tips="$t('core.player.lyric_select')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-text')
div(:class="[$style.footerLeftControlBtn, isShowComment ? $style.active : null]" @click="isShowComment = !isShowComment" :tips="$t('core.player.comment_show')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='95%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-comment')
div(:class="$style.footerLeftControlBtn" @click="$emit('toggle-next-play-mode')" :tips="nextTogglePlayName")
svg(v-if="setting.player.togglePlayMethod == 'listLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-loop')
svg(v-else-if="setting.player.togglePlayMethod == 'random'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-random')
svg(v-else-if="setting.player.togglePlayMethod == 'list'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-order')
svg(v-else-if="setting.player.togglePlayMethod == 'singleLoop'" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='110%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-single-loop')
svg(v-else version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='120%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-single')
div(:class="$style.footerLeftControlBtn" @click="$emit('add-music-to', musicInfo)" :tips="$t('core.player.add_music_to')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-add-2')
div(:class="$style.progressContainer")
div(:class="$style.progressContent")
div(:class="$style.progress")
//- div(:class="[$style.progressBar, $style.progressBar1]" :style="{ transform: `scaleX(${progress || 0})` }")
div(:class="[$style.progressBar, $style.progressBar2, isActiveTransition ? $style.barTransition : '']" @transitionend="handleTransitionEnd" :style="{ transform: `scaleX(${playInfo.progress || 0})` }")
div(:class="$style.progressMask" @click='setProgress' ref="dom_progress")
div(:class="$style.timeLabel")
span(style="margin-left: 15px") {{playInfo.status}}
div
span {{playInfo.nowPlayTimeStr}}
span(style="margin: 0 5px;") /
span {{playInfo.maxPlayTimeStr}}
div(:class="$style.playControl")
//- div(:class="$style.playBtn")
//- div(:class="$style.playBtn")
div(:class="$style.playBtn" @click="$emit('action', { type: 'prev' })" style="transform: rotate(180deg);" :tips="$t('core.player.prev')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :tips="isPlay ? $t('core.player.pause') : $t('core.player.play')" @click="$emit('action', { type: 'togglePlay' })")
svg(v-if="isPlay" 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')
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.playBtn" @click="$emit('action', { type: 'next' })" :tips="$t('core.player.next')")
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')
</template>
<script>
import { mapGetters } from 'vuex'
import { base as eventBaseName } from '../../event/names'
import { clipboardWriteText, scrollTo } from '../../utils'
let cancelScrollFn = null
export default {
props: {
visible: {
type: Boolean,
required: true,
},
musicInfo: {
type: Object,
default() {
return {
songmid: null,
img: null,
lrc: null,
url: null,
name: '^',
singer: '^',
}
},
},
lyric: {
type: Object,
default() {
return {
line: 0,
text: '',
lines: [],
}
},
},
playInfo: {
type: Object,
default() {
return {
nowPlayTimeStr: '00:00',
maxPlayTimeStr: '00:00',
progress: 0,
nowPlayTime: 0,
status: 0,
}
},
},
list: {
type: Array,
default() {
return []
},
},
listId: {
type: String,
default() {
return ''
},
},
isPlay: {
type: Boolean,
default: false,
},
nextTogglePlayName: String,
},
watch: {
// 'musicInfo.img': {
// handler(n) {
// if (n) {
// this.bgStyle.backgroundImage = `url(${n})`
// }
// },
// immediate: true,
// },
'lyric.lines': {
handler(n, o) {
this.isSetedLines = true
if (o) {
this.$refs.dom_lyric_text.textContent = ''
this.setLyric(n)
this._lyricLines = n
if (n.length) {
this.lyricLines = n
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
this.handleScrollLrc()
})
} else {
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
cancelScrollFn = scrollTo(this.$refs.dom_lyric, 0, 300, () => {
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
this.lyricLines = this._lyricLines
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
this.handleScrollLrc()
})
}, 50)
}
} else {
this.lyricLines = n
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
this.handleScrollLrc()
})
}
},
immediate: true,
},
'lyric.line': {
handler(n) {
if (n < 0) return
if (n == 0 && this.isSetedLines) return this.isSetedLines = false
this.handleScrollLrc()
},
immediate: true,
},
'playInfo.nowPlayTime'(n, o) {
if (Math.abs(n - o) > 2) this.isActiveTransition = true
},
},
data() {
return {
bgStyle: {
backgroundImage: null,
},
dom_lines: [],
isActiveTransition: false,
pregessWidth: 0,
clickTime: 0,
volumeEvent: {
isMsDown: false,
msDownX: 0,
msDownValue: 0,
},
lyricEvent: {
isMsDown: false,
msDownY: 0,
msDownScrollY: 0,
isStopScroll: false,
timeout: null,
},
_lyricLines: [],
lyricLines: [],
isSetedLines: false,
isShowComment: false,
isShowLrcSelectContent: false,
}
},
mounted() {
this.$nextTick(() => {
this.setProgressWidth()
})
this.listenEvent()
// console.log('object', this.$refs.dom_lyric_text)
this.setLyric(this.lyricLines)
},
beforeDestroy() {
this.unlistenEvent()
this.clearLyricScrollTimeout()
},
computed: {
...mapGetters(['setting']),
},
methods: {
hide() {
this.$emit('update:visible', false)
},
listenEvent() {
document.addEventListener('mousemove', this.handleMouseMsMove)
document.addEventListener('mouseup', this.handleMouseMsUp)
window.addEventListener('resize', this.handleResize)
},
unlistenEvent() {
document.removeEventListener('mousemove', this.handleMouseMsMove)
document.removeEventListener('mouseup', this.handleMouseMsUp)
window.removeEventListener('resize', this.handleResize)
},
setLyric(lines) {
const dom_lines = document.createDocumentFragment()
for (const line of lines) {
dom_lines.appendChild(line.dom_line)
}
this.$refs.dom_lyric_text.appendChild(dom_lines)
},
handleResize() {
this.setProgressWidth()
},
handleScrollLrc() {
if (!this.dom_lines.length) return
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
if (this.lyricEvent.isStopScroll) return
let dom_p = this.dom_lines[this.lyric.line]
cancelScrollFn = scrollTo(this.$refs.dom_lyric, dom_p ? (dom_p.offsetTop - this.$refs.dom_lyric.clientHeight * 0.38) : 0)
},
handleTransitionEnd() {
this.isActiveTransition = false
},
setProgress(event) {
this.$emit('action', {
type: 'progress',
data: event.offsetX / this.pregessWidth,
})
},
setProgressWidth() {
this.pregessWidth = parseInt(
window.getComputedStyle(this.$refs.dom_progress, null).width,
)
},
handleContextMenu() {
if (window.performance.now() - this.clickTime > 400) {
this.clickTime = window.performance.now()
return
}
this.clickTime = 0
this.hide()
},
handleLyricMouseDown(e) {
// console.log(e)
this.lyricEvent.isMsDown = true
this.lyricEvent.msDownY = e.clientY
this.lyricEvent.msDownScrollY = this.$refs.dom_lyric.scrollTop
},
handleMouseMsUp(e) {
this.lyricEvent.isMsDown = this.volumeEvent.isMsDown = false
},
handleMouseMsMove(e) {
if (this.lyricEvent.isMsDown) {
if (!this.lyricEvent.isStopScroll) this.lyricEvent.isStopScroll = true
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
this.$refs.dom_lyric.scrollTop = this.lyricEvent.msDownScrollY + this.lyricEvent.msDownY - e.clientY
this.startLyricScrollTimeout()
}
// if (this.volumeEvent.isMsDown) {
// let val = this.volumeEvent.msDownValue + (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)
},
startLyricScrollTimeout() {
this.clearLyricScrollTimeout()
this.lyricEvent.timeout = setTimeout(() => {
this.lyricEvent.timeout = null
this.lyricEvent.isStopScroll = false
if (!this.isPlay) return
this.handleScrollLrc()
}, 3000)
},
handleWheel(event) {
console.log(event.deltaY)
if (!this.lyricEvent.isStopScroll) this.lyricEvent.isStopScroll = true
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
this.$refs.dom_lyric.scrollTop = this.$refs.dom_lyric.scrollTop + event.deltaY
this.startLyricScrollTimeout()
},
clearLyricScrollTimeout() {
if (!this.lyricEvent.timeout) return
clearTimeout(this.lyricEvent.timeout)
this.lyricEvent.timeout = null
},
min() {
window.eventHub.$emit(eventBaseName.min)
},
max() {
window.eventHub.$emit(eventBaseName.max)
},
close() {
window.eventHub.$emit(eventBaseName.close)
},
handleCopySelectText() {
let str = window.getSelection().toString()
str = str.trim()
if (!str.length) return
clipboardWriteText(str)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@control-btn-width: @height-toolbar * .26;
.container {
position: absolute;
display: flex;
flex-flow: column nowrap;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: @color-theme_2-background_1;
z-index: 10;
// -webkit-app-region: drag;
overflow: hidden;
border-radius: @radius-border;
color: @color-theme_2-font;
border-left: 12px solid @color-theme;
-webkit-app-region: no-drag;
&.controlBtnLeft {
.controBtn {
left: 0;
flex-direction: row-reverse;
height: @height-toolbar * .7;
button + button {
margin-right: @control-btn-width / 2;
}
}
}
&.controlBtnRight {
.controBtn {
right: @control-btn-width * .5;
button + button {
margin-left: @control-btn-width * 1.2;
}
}
}
}
.bg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-size: 110% 110%;
filter: blur(60px);
z-index: -1;
}
.bg2 {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
background-color: rgba(255, 255, 255, .8);
}
.header {
position: relative;
flex: 0 0 @height-toolbar;
-webkit-app-region: drag;
}
.controBtn {
position: absolute;
top: 0;
display: flex;
align-items: center;
height: @height-toolbar;
-webkit-app-region: no-drag;
padding: 0 @control-btn-width;
&:hover {
.controBtnIcon {
opacity: 1;
}
}
button {
position: relative;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
outline: none;
padding: 1px;
cursor: pointer;
border-radius: 50%;
color: @color-theme_2;
display: flex;
justify-content: center;
align-items: center;
&.hide {
background-color: @color-hideBtn;
}
&.min {
background-color: @color-minBtn;
}
&.max {
background-color: @color-maxBtn;
}
&.close {
background-color: @color-closeBtn;
}
}
}
.controBtnIcon {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.main {
flex: auto;
min-height: 0;
overflow: hidden;
display: flex;
margin: 0 30px;
position: relative;
&.showComment {
.left {
flex-basis: 18%;
.description p {
font-size: 12px;
}
}
.right {
flex-basis: 30%;
.lyric {
font-size: 13px;
}
}
.comment {
opacity: 1;
transform: scaleX(1);
}
}
}
.left {
flex: 0 0 40%;
display: flex;
flex-flow: column nowrap;
align-items: center;
padding: 13px;
overflow: hidden;
transition: flex-basis @transition-theme;
}
.info {
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
max-width: 300px;
}
.img {
max-width: 100%;
max-height: 100%;
min-width: 100%;
box-shadow: 0 0 4px @color-theme-hover;
border-radius: 6px;
opacity: .8;
// border: 5px solid @color-theme-hover;
// border-radius: @radius-border;
// border: 5px solid #fff;
}
.description {
max-width: 300px;
padding: 15px 0;
overflow: hidden;
p {
line-height: 1.5;
font-size: 14px;
overflow-wrap: break-word;
}
}
.right {
flex: 0 0 60%;
// padding: 0 30px;
position: relative;
transition: flex-basis @transition-theme;
&:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
&:after {
position: absolute;
bottom: 0;
left: 0;
content: ' ';
height: 100px;
width: 100%;
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,@color-theme_2-background_1 95%);
pointer-events: none;
}
}
.lyric {
text-align: center;
height: 100%;
overflow: hidden;
font-size: 16px;
cursor: grab;
&.draging {
cursor: grabbing;
}
:global {
.lrc-content {
line-height: 1.2;
margin: 16px 0;
overflow-wrap: break-word;
color: @color-player-detail-lyric;
.translation {
transition: @transition-theme !important;
transition-property: font-size, color;
font-size: .9em;
margin-top: 5px;
}
.line {
transition-property: font-size, color !important;
background: none !important;
-webkit-text-fill-color: unset;
// -webkit-text-fill-color: none !important;
}
&.active {
.line {
color: @color-theme;
}
.translation {
font-size: 1em;
color: @color-theme;
}
span {
// color: @color-theme;
font-size: 1.2em;
}
}
span {
transition: @transition-theme !important;
transition-property: font-size !important;
font-size: 1em;
background-repeat: no-repeat;
background-color: rgba(77, 77, 77, 0.9);
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
}
}
// p {
// padding: 8px 0;
// line-height: 1.2;
// overflow-wrap: break-word;
// transition: @transition-theme !important;
// transition-property: color, font-size;
// }
// .lrc-active {
// color: @color-theme;
// font-size: 1.2em;
// }
}
.lyricSelectContent {
position: absolute;
left: 0;
top: 0;
// text-align: center;
height: 100%;
width: 100%;
font-size: 16px;
background-color: @color-theme_2-background_1;
z-index: 10;
color: @color-player-detail-lyric;
.lyricSelectline {
padding: 8px 0;
overflow-wrap: break-word;
transition: @transition-theme !important;
transition-property: color, font-size;
line-height: 1.3;
}
.lyricSelectlineTransition {
font-size: 14px;
}
.lrc-active {
color: @color-theme;
}
}
.lyric-space {
height: 70%;
}
.comment {
position: absolute;
right: 0;
top: 0;
width: 50%;
height: 100%;
opacity: 1;
margin-left: 10px;
transform: scaleX(0);
}
.footer {
flex: 0 0 100px;
overflow: hidden;
display: flex;
align-items: center;
}
.footerLeft {
flex: auto;
display: flex;
flex-flow: column nowrap;
padding: 13px;
overflow: hidden;
}
.footerLeftControlBtns {
color: @color-theme_2-font;
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
}
.footerLeftControlBtn {
width: 18px;
height: 18px;
opacity: .5;
cursor: pointer;
transition: opacity @transition-theme;
&:hover {
opacity: .9;
}
+.footerLeftControlBtn {
margin-left: 6px;
}
&.active {
color: @color-theme;
opacity: .8;
}
}
.progress-container {
width: 100%;
position: relative;
padding: 3px 0;
}
.progress-content {
position: relative;
height: 15px;
padding: 5px 0;
width: 100%;
}
.progress {
height: 100%;
width: 100%;
border-radius: 0.2rem;
// overflow: hidden;
transition: @transition-theme;
transition-property: background-color;
background-color: @color-player-progress;
// background-color: #f5f5f5;
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: 100%;
height: 100%;
transform-origin: 0;
border-top-right-radius: @radius-progress-border;
border-bottom-right-radius: @radius-progress-border;
}
.progress-bar1 {
background-color: @color-player-progress-bar1;
}
.progress-bar2 {
background-color: @color-player-progress-bar2;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
}
.bar-transition {
transition-property: transform;
transition-timing-function: ease-out;
transition-duration: 0.2s;
}
.time-label {
width: 100%;
height: 18px;
display: flex;
justify-content: space-between;
span {
font-size: 13px;
}
}
.play-control {
flex: none;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 25px;
color: @color-btn;
}
.play-btn {
height: 40%;
padding: 5px;
cursor: pointer;
flex: none;
// transition: @transition-theme;
// transition-property: color;
color: @color-player-detail-play-btn;
transition: opacity 0.2s ease;
opacity: 1;
cursor: pointer;
+.play-btn {
margin-left: 10px;
}
svg {
fill: currentColor;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.2));
}
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
each(@themes, {
:global(#container.@{value}) {
.container {
border-left-color: ~'@{color-@{value}-theme}';
background-color: ~'@{color-@{value}-theme_2-background_1}';
// color: ~'@{color-@{value}-theme_2-font}';
}
.right {
&:before {
background-image: linear-gradient(0deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
&:after {
background-image: linear-gradient(-180deg,rgba(255,255,255,0) 0%,~'@{color-@{value}-theme_2-background_1}' 95%);
}
}
.controBtn {
button {
color: ~'@{color-@{value}-theme_2}';
// &.hide:after {
// background-color: ~'@{color-@{value}-hideBtn}';
// }
&.hide {
background-color: ~'@{color-@{value}-hideBtn}';
}
&.min {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
.img {
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
// border-color: ~'@{color-@{value}-theme-hover}';
}
.lyric {
:global {
.lrc-content {
color: ~'@{color-@{value}-player-detail-lyric}';
&.active {
.translation {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
.line {
color: ~'@{color-@{value}-player-detail-lyric-active}';
}
}
span {
background-color: ~'@{color-@{value}-player-detail-lyric}';
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-player-detail-lyric-active}', ~'@{color-@{value}-player-detail-lyric-active}');
}
}
}
}
// .lrc-active {
// color: ~'@{color-@{value}-theme}';
// }
.lyricSelectContent {
background-color: ~'@{color-@{value}-theme_2-background_1}';
color: ~'@{color-@{value}-player-detail-lyric}';
.lrc-active {
color: ~'@{color-@{value}-theme}';
}
}
.footerLeftControlBtns {
color: ~'@{color-@{value}-theme_2-font}';
}
.footerLeftControlBtn {
&.active {
color: ~'@{color-@{value}-theme}';
}
}
.progress {
background-color: ~'@{color-@{value}-player-progress}';
}
.progress-bar1 {
background-color: ~'@{color-@{value}-player-progress-bar1}';
}
.progress-bar2 {
background-color: ~'@{color-@{value}-player-progress-bar2}';
}
.play-btn {
color: ~'@{color-@{value}-player-detail-play-btn}';
}
}
})
</style>
@@ -0,0 +1,159 @@
<template lang="pug">
material-modal(:show="sync.isShowSyncMode" @close="handleClose(false)" :bgClose="false" :close-btn="false")
main(:class="$style.main")
h2 {{$t('sync__title', { name: sync.deviceName })}}
div.scroll(:class="$style.content")
dl(:class="$style.btnGroup")
dt(:class="$style.label") {{$t('sync__merge_label')}}
dd(:class="$style.btns")
base-btn(:class="$style.btn" @click="handleSelectMode('merge_local_remote')") {{$t('sync__merge_btn_local_remote')}}
base-btn(:class="$style.btn" @click="handleSelectMode('merge_remote_local')") {{$t('sync__merge_btn_remote_local')}}
dl(:class="$style.btnGroup")
dt(:class="$style.label") {{$t('sync__overwrite_label')}}
dd(:class="$style.btns")
base-btn(:class="$style.btn" @click="handleSelectMode('overwrite_local_remote')") {{$t('sync__overwrite_btn_local_remote')}}
base-btn(:class="$style.btn" @click="handleSelectMode('overwrite_remote_local')") {{$t('sync__overwrite_btn_remote_local')}}
dd(style="font-size: 14px; margin-top: 5px;")
base-checkbox(id="sync_mode_modal_isOverwrite" v-model="isOverwrite" :label="$t('sync__overwrite')")
dl(:class="$style.btnGroup")
dt(:class="$style.label") {{$t('sync__other_label')}}
dd(:class="$style.btns")
base-btn(:class="$style.btn" @click="handleSelectMode('none')") {{$t('sync__overwrite_btn_none')}}
base-btn(:class="$style.btn" @click="handleSelectMode('cancel')") {{$t('sync__overwrite_btn_cancel')}}
dl(:class="$style.btnGroup")
dd
section(:class="$style.tipGroup")
h3(:class="$style.title") {{$t('sync__merge_tip')}}
p(:class="$style.tip") {{$t('sync__merge_tip_desc')}}
section(:class="$style.tipGroup")
h3(:class="$style.title") {{$t('sync__overwrite_tip')}}
p(:class="$style.tip") {{$t('sync__overwrite_tip_desc')}}
section(:class="$style.tipGroup")
h3(:class="$style.title") {{$t('sync__other_tip')}}
p(:class="$style.tip") {{$t('sync__other_tip_desc')}}
</template>
<script>
import { sync as eventSyncName } from '@renderer/event/names'
import { sync } from '@renderer/core/share'
export default {
setup() {
return {
sync,
}
},
data() {
return {
isOverwrite: false,
}
},
computed: {
},
methods: {
handleSelectMode(mode) {
if (mode.startsWith('overwrite') && this.isOverwrite) mode += '_full'
window.eventHub.emit(eventSyncName.send_sync_list, {
action: 'selectMode',
data: mode,
})
this.handleClose()
},
handleClose() {
sync.isShowSyncMode = false
},
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.main {
padding: 15px;
max-width: 700px;
min-width: 200px;
min-height: 0;
display: flex;
flex-flow: column nowrap;
justify-content: center;
h2 {
font-size: 16px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
.content {
flex: auto;
padding: 15px 0 5px;
padding-right: 5px;
.btnGroup + .btnGroup {
margin-top: 10px;
}
.label {
color: @color-theme_2-font-label;
font-size: 14px;
line-height: 2;
}
.desc {
line-height: 1.5;
font-size: 14px;
text-align: justify;
}
.tipGroup {
display: flex;
flex-direction: row;
font-size: 12px;
+ .tipGroup {
margin-top: 5px;
}
.title {
white-space: nowrap;
font-weight: bold;
margin-right: 5px;
}
.tip {
line-height: 1.3;
}
}
}
.btns {
display: flex;
align-items: center;
}
.btn {
display: block;
white-space: nowrap;
+.btn {
margin-left: 15px;
}
&:last-child {
margin-bottom: 0;
}
}
each(@themes, {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.name {
color: ~'@{color-@{value}-theme}';
}
}
})
</style>
-242
View File
@@ -1,242 +0,0 @@
<template lang="pug">
div(:class="[$style.toolbar, setting.controlBtnPosition == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]")
//- img(v-if="icon")
//- h1 {{title}}
material-search-input(:class="$style.input"
@event="handleEvent" :list="tipList" :visibleList="visibleList"
v-model="searchText")
div(:class="$style.logo" v-if="setting.controlBtnPosition == 'left'") L X
div(:class="$style.control" v-else)
button(type="button" :class="$style.min" :tips="$t('core.toolbar.min')" @click="min")
svg(:class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-minimize')
//- button(type="button" :class="$style.max" @click="max")
button(type="button" :class="$style.close" :tips="$t('core.toolbar.close')" @click="close")
svg(:class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' width='100%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-window-close')
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import music from '../../utils/music'
import { debounce } from '../../utils'
import { base as eventBaseName } from '../../event/names'
export default {
data() {
return {
searchText: '',
visibleList: false,
tipList: [],
tipSearch: null,
isFocused: false,
}
},
computed: {
...mapGetters(['route', 'setting']),
...mapGetters('search', {
storeSearchText: 'searchText',
}),
source() {
return this.setting.search.tempSearchSource
},
isAutoClearSearchInput() {
return this.setting.odc.isAutoClearSearchInput
},
isAutoClearSearchList() {
return this.setting.odc.isAutoClearSearchList
},
},
watch: {
route(n) {
if (n.name != 'search') {
if (this.isAutoClearSearchInput && this.searchText) this.searchText = ''
if (this.isAutoClearSearchList) this.clearSearchList()
}
},
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: {
...mapMutations('search', {
clearSearchList: 'clearList',
}),
handleEvent({ action, data }) {
switch (action) {
case 'focus':
this.isFocused = true
if (!this.visibleList) this.visibleList = true
if (this.searchText) this.handleTipSearch()
break
case 'blur':
this.isFocused = false
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.isFocused) 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() {
window.eventHub.$emit(eventBaseName.min)
},
max() {
window.eventHub.$emit(eventBaseName.max)
},
close() {
window.eventHub.$emit(eventBaseName.close)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@control-btn-width: @height-toolbar * .26;
.toolbar {
display: flex;
height: @height-toolbar;
align-items: center;
justify-content: space-between;
padding-left: 15px;
-webkit-app-region: drag;
z-index: 2;
&.controlBtnLeft {
.control {
display: none;
}
}
&.controlBtnRight {
justify-content: space-between;
}
}
.logo {
box-sizing: border-box;
padding: 0 @height-toolbar * .4;
height: @height-toolbar;
color: @color-theme;
flex: none;
text-align: center;
line-height: @height-toolbar;
font-weight: bold;
// -webkit-app-region: no-drag;
}
.control {
display: flex;
align-items: center;
height: 100%;
left: 15px;
-webkit-app-region: no-drag;
padding: 0 @control-btn-width * 1.5;
&:hover {
.icon {
opacity: 1;
}
}
button {
display: flex;
position: relative;
width: @control-btn-width;
height: @control-btn-width;
background: none;
border: none;
outline: none;
padding: 1px;
cursor: pointer;
border-radius: 50%;
color: @color-theme_2;
+ button {
margin-left: @control-btn-width * 1.2;
}
&.min {
background-color: @color-minBtn;
}
&.max {
background-color: @color-maxBtn;
}
&.close {
background-color: @color-closeBtn;
}
}
}
.icon {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
each(@themes, {
:global(#container.@{value}) {
.control {
button {
color: ~'@{color-@{value}-theme_2}';
&.min {
background-color: ~'@{color-@{value}-minBtn}';
}
&.max {
background-color: ~'@{color-@{value}-maxBtn}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
.logo {
color: ~'@{color-@{value}-theme}';
}
}
})
</style>
@@ -0,0 +1,88 @@
<template>
<div :class="$style.control">
<button type="button" :class="[$style.btn, $style.min]" :tips="$t('min')" @click="min">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-minimize-2"></use>
</svg>
</button>
<button type="button" :class="[$style.btn, $style.close]" :tips="$t('close')" @click="close">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="60%" viewBox="0 0 24 24" space="preserve">
<use xlink:href="#icon-window-close-2"></use>
</svg>
</button>
</div>
</template>
<script>
import { base as eventBaseName } from '@renderer/event/names'
// import { getRandom } from '../../utils'
export default {
setup() {
return {
min() {
window.eventHub.emit(eventBaseName.min)
},
max() {
window.eventHub.emit(eventBaseName.max)
},
close() {
window.eventHub.emit(eventBaseName.close)
},
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.control {
display: flex;
align-self: flex-start;
-webkit-app-region: no-drag;
height: 30px;
.btn {
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 46px;
height: 30px;
background: none;
border: none;
outline: none;
padding: 1px;
cursor: pointer;
color: @color-theme;
transition: background-color 0.2s ease-in-out;
&:hover {
&.min, &.max {
background-color: @color-btn-hover;
}
&.close {
background-color: @color-closeBtn;
}
}
}
}
each(@themes, {
:global(#root.@{value}) {
.btn {
color: ~'@{color-@{value}-theme_2-font-label}';
&:hover {
&.min, &.max {
background-color: ~'@{color-@{value}-btn-hover}';
}
&.close {
background-color: ~'@{color-@{value}-closeBtn}';
}
}
}
}
})
</style>
@@ -0,0 +1,108 @@
<template>
<material-search-input @event="handleEvent" :list="tipList" :visibleList="visibleList" v-model="searchText" />
</template>
<script>
import music from '@renderer/utils/music'
import { debounce } from '@renderer/utils'
import {
ref,
useRoute,
watch,
useRefGetter,
useRouter,
nextTick,
useCommit,
} from '@renderer/utils/vueTools'
export default {
setup() {
const searchText = ref('')
const visibleList = ref(false)
const tipList = ref([])
let isFocused = false
const route = useRoute()
const router = useRouter()
const setting = useRefGetter('setting')
const clearSearchList = useCommit('search', 'clearList')
watch(() => route.name, (newValue, oldValue) => {
if (newValue.name != 'search') {
if (setting.value.odc.isAutoClearSearchInput && searchText.value) searchText.value = ''
if (setting.value.odc.isAutoClearSearchList) clearSearchList()
}
})
const storeSearchText = useRefGetter('search', 'searchText')
watch(storeSearchText, (newValue, oldValue) => {
searchText.value = newValue
if (newValue !== searchText.value) searchText.value = newValue
})
watch(searchText, () => {
handleTipSearch()
})
const tipSearch = debounce(() => {
if (searchText.value === '') {
tipList.value = []
music[setting.value.search.tempSearchSource].tempSearch.cancelTempSearch()
return
}
music[setting.value.search.tempSearchSource].tempSearch.search(searchText.value).then(list => {
tipList.value = list
}).catch(() => {})
}, 50)
const handleTipSearch = () => {
if (!visibleList.value && isFocused) visibleList.value = true
tipSearch()
}
const handleSearch = () => {
if (visibleList.value) visibleList.value = false
setTimeout(() => {
router.push({
path: 'search',
query: {
text: searchText.value,
},
}).catch(_ => _)
}, 200)
}
const handleEvent = ({ action, data }) => {
switch (action) {
case 'focus':
isFocused = true
if (!visibleList.value) visibleList.value = true
if (searchText.value) handleTipSearch()
break
case 'blur':
isFocused = false
setTimeout(() => {
if (visibleList.value) visibleList.value = false
}, 50)
break
case 'submit':
handleSearch()
break
case 'listClick':
searchText.value = tipList.value[data]
nextTick(handleSearch)
}
}
return {
searchText,
visibleList,
tipList,
handleEvent,
}
},
}
</script>
@@ -0,0 +1,66 @@
<template>
<div :class="[$style.toolbar, setting.controlBtnPosition == 'left' ? $style.controlBtnLeft : $style.controlBtnRight]">
<SearchInput />
<div :class="$style.logo" v-if="setting.controlBtnPosition == 'left'">L X</div>
<ControlBtns v-else />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import ControlBtns from './ControlBtns'
import SearchInput from './SearchInput'
export default {
name: 'CoreToolBar',
components: { SearchInput, ControlBtns },
computed: {
...mapGetters(['setting']),
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.toolbar {
display: flex;
height: @height-toolbar;
align-items: center;
justify-content: space-between;
padding-left: 15px;
-webkit-app-region: drag;
z-index: 2;
&.controlBtnLeft {
.control {
display: none;
}
}
&.controlBtnRight {
justify-content: space-between;
}
}
.logo {
box-sizing: border-box;
padding: 0 @height-toolbar * .4;
height: @height-toolbar;
color: @color-theme;
flex: none;
text-align: center;
line-height: @height-toolbar;
font-weight: bold;
// -webkit-app-region: no-drag;
}
each(@themes, {
:global(#root.@{value}) {
.logo {
color: ~'@{color-@{value}-theme}';
}
}
})
</style>
@@ -1,14 +1,14 @@
<template lang="pug">
material-modal(:show="version.showModal" @close="handleClose" v-if="version.newVersion")
main(:class="$style.main" v-if="version.isDownloaded")
material-modal(:show="versionInfo.showModal" @close="handleClose")
main(:class="$style.main" v-if="versionInfo.isDownloaded")
h2 🚀程序更新🚀
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 最新版本:{{versionInfo.newVersion?.version}}
h3 当前版本:{{versionInfo.version}}
h3 版本变化:
pre(:class="$style.desc" v-text="version.newVersion.desc")
pre(:class="$style.desc" v-text="versionInfo.newVersion?.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
@@ -23,16 +23,16 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
| 或稍后
strong 关闭程序时
| 自动更新~
material-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
main(:class="$style.main" v-else-if="version.isError && !version.isUnknow && version.newVersion.version != version.version")
base-btn(:class="$style.btn" @click.onec="handleRestartClick") 立即重启更新
main(:class="$style.main" v-else-if="versionInfo.isError && !versionInfo.isUnknow && versionInfo.newVersion?.version != versionInfo.version")
h2 ❌ 版本更新出错 ❌
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 最新版本:{{versionInfo.newVersion?.version}}
h3 当前版本:{{versionInfo.version}}
h3 版本变化:
pre(:class="$style.desc" v-text="version.newVersion.desc")
pre(:class="$style.desc" v-text="versionInfo.newVersion?.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
@@ -54,16 +54,16 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
| 国内Windows/MAC用户推荐到
strong 网盘
| 下载。
material-btn(:class="$style.btn" @click.onec="handleIgnoreClick") {{ isIgnored ? '恢复当前版本的更新失败提醒' : '忽略当前版本的更新失败提醒'}}
main(:class="$style.main" v-else-if="version.isDownloading && version.isTimeOut && !version.isUnknow")
base-btn(:class="$style.btn" @click.onec="handleIgnoreClick") {{ isIgnored ? '恢复当前版本的更新失败提醒' : '忽略当前版本的更新失败提醒'}}
main(:class="$style.main" v-else-if="versionInfo.isDownloading && versionInfo.isTimeOut && !versionInfo.isUnknow")
h2 ❗️ 新版本下载超时 ❗️
div(:class="$style.desc")
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),你仍可选择继续等,但墙裂建议手动更新版本!
p
| 你可以去
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
base-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
| 或
material-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘
base-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘
| (密码:
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
| )下载新版本,
@@ -72,34 +72,34 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
strong 网盘
| 下载。
p 当前下载进度:{{progress}}
main(:class="$style.main" v-else-if="version.isUnknow")
main(:class="$style.main" v-else-if="versionInfo.isUnknow")
h2 ❓ 获取最新版本信息失败 ❓
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 当前版本:{{version.version}}
h3 当前版本:{{versionInfo.version}}
div(:class="$style.desc")
p 更新信息获取失败,可能是无法访问Github导致的,请手动检查更新!
p
| 检查方法:打开
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
base-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
| 或
material-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘
base-btn(min @click="handleOpenUrl('https://www.lanzoui.com/b0bf2cfa/')" tips="点击打开") 网盘
| (密码:
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
| )查看它们的
strong 版本号
| 与当前版本({{version.version}})对比是否一样,
| 与当前版本({{versionInfo.version}})对比是否一样,
p 若一样则不必理会该弹窗,直接关闭即可,否则请手动下载新版本更新。
main(:class="$style.main" v-else)
h2 🌟发现新版本🌟
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 最新版本:{{versionInfo.newVersion?.version}}
h3 当前版本:{{versionInfo.version}}
h3 版本变化:
pre(:class="$style.desc" v-text="version.newVersion.desc")
pre(:class="$style.desc" v-text="versionInfo.newVersion?.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
@@ -127,37 +127,41 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
<script>
import { mapGetters, mapMutations } from 'vuex'
import { rendererSend, NAMES } from '../../../common/ipc'
import { compareVer, openUrl, clipboardWriteText, sizeFormate } from '../../utils'
import { rendererSend, NAMES } from '@common/ipc'
import { compareVer, openUrl, clipboardWriteText, sizeFormate } from '@renderer/utils'
import { versionInfo } from '@renderer/core/share'
export default {
setup() {
return {
versionInfo,
}
},
computed: {
...mapGetters(['version', 'setting']),
...mapGetters(['setting']),
history() {
if (!this.version.newVersion || !this.version.newVersion.history) return []
if (!this.versionInfo.newVersion || !this.versionInfo.newVersion?.history) return []
let arr = []
let currentVer = this.version.version
this.version.newVersion.history.forEach(ver => {
let currentVer = this.versionInfo.version
this.versionInfo.newVersion?.history.forEach(ver => {
if (compareVer(currentVer, ver.version) < 0) arr.push(ver)
})
return arr
},
progress() {
return this.version.downloadProgress
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
return this.versionInfo.downloadProgress
? `${this.versionInfo.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.versionInfo.downloadProgress.transferred)}/${sizeFormate(this.versionInfo.downloadProgress.total)} - ${sizeFormate(this.versionInfo.downloadProgress.bytesPerSecond)}/s`
: '处理更新中...'
},
isIgnored() {
return this.setting.ignoreVersion == this.version.newVersion.version
return this.setting.ignoreVersion == this.versionInfo.newVersion?.version
},
},
methods: {
...mapMutations(['setVersionModalVisible', 'setIgnoreVersion']),
...mapMutations(['setIgnoreVersion']),
handleClose() {
this.setVersionModalVisible({
isShow: false,
})
versionInfo.showModal = false
},
handleOpenUrl(url) {
openUrl(url)
@@ -171,7 +175,7 @@ export default {
clipboardWriteText(text)
},
handleIgnoreClick() {
this.setIgnoreVersion(this.isIgnored ? null : this.version.newVersion.version)
this.setIgnoreVersion(this.isIgnored ? null : this.versionInfo.newVersion?.version)
this.handleClose()
},
},
@@ -180,7 +184,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.main {
position: relative;
@@ -289,7 +293,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
+13 -10
View File
@@ -1,21 +1,24 @@
<template lang="pug">
div(:class="$style.view")
transition(enter-active-class="animated-fast fadeIn"
leave-active-class="animated-fast fadeOut")
router-view
//- core-title-bar
//- router-view
<template>
<div :class="$style.view">
<router-view v-slot="{ Component }">
<transition enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut">
<component :is="Component" />
</transition>
</router-view>
</div>
</template>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.view {
position: relative;
z-index: 1;
> * {
position: absolute;
position: absolute !important;
left: 0;
top: 0;
height: 100%;
width: 100%;
}
// background: #fff;
+18 -6
View File
@@ -1,13 +1,25 @@
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context('./', true, /\.vue$/)
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName)
const vueFileRxp = /\.vue$/
const componentName = upperFirst(camelCase(fileName.replace(/^\.\//, '').replace(/\.\w+$/, '')))
export default app => {
requireComponent.keys().forEach(fileName => {
const filePath = fileName.replace(/^\.\//, '')
Vue.component(componentName, componentConfig.default || componentConfig)
})
if (!filePath.split('/').every((path, index, arr) => {
const char = path.charAt(0)
return vueFileRxp.test(path) || char.toUpperCase() !== char || arr[index + 1] == 'index.vue'
})) return
const componentConfig = requireComponent(fileName)
let componentName = upperFirst(camelCase(filePath.replace(/\.\w+$/, '')))
if (componentName.endsWith('Index')) componentName = componentName.replace(/Index$/, '')
app.component(componentName, componentConfig.default || componentConfig)
})
}
@@ -1,101 +0,0 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| {{ info.name }}
br
| {{ info.singer }}
material-btn(:class="$style.btn" v-if="checkSource(type.type)" :tips="!checkSource(type.type) && $t('material.download_modal.btn_tip')" :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>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
},
bgClose: {
type: Boolean,
default: true,
},
},
computed: {
info() {
return this.musicInfo || {}
},
qualityList() {
return window.globalObj.qualityList[this.musicInfo.source] || []
},
},
methods: {
handleClick(type) {
this.$emit('select', type)
},
handleClose() {
this.$emit('close')
},
getTypeName(type) {
switch (type) {
case 'flac':
case 'ape':
case 'wav':
return this.$t('material.download_modal.lossless')
case '320k':
return this.$t('material.download_modal.high_quality')
case '192k':
case '128k':
return this.$t('material.download_modal.normal')
}
},
checkSource(type) {
return this.qualityList.includes(type)
},
},
}
</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>
@@ -1,82 +0,0 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main(:class="$style.main")
h2
| {{$t('material.download_multiple_modal.tip', { len: list.length })}}
br
| {{$t('material.download_multiple_modal.tip2')}}
material-btn(:class="$style.btn" @click="handleClick('128k')") {{$t('material.download_multiple_modal.normal')}} - 128K
material-btn(:class="$style.btn" @click="handleClick('320k')") {{$t('material.download_multiple_modal.high_quality')}} - 320K
//- material-btn(:class="$style.btn" @click="handleClick('ape')") 无损音质 - APE
material-btn(:class="$style.btn" @click="handleClick('flac')") {{$t('material.download_multiple_modal.lossless')}} - FLAC/WAV
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
bgClose: {
type: Boolean,
default: true,
},
list: {
type: Array,
default() {
return []
},
},
},
methods: {
handleClick(type) {
this.$emit('select', type)
},
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;
}
}
.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>
@@ -1,30 +1,30 @@
<template lang="pug">
div(:class="$style.btns")
button(type="button" v-if="playBtn" @contextmenu.capture.stop :tips="$t('material.list_buttons.play')" @click.stop="handleClick('play')")
button(type="button" v-if="playBtn" @contextmenu.capture.stop :tips="$t('list__play')" @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' v-once)
use(xlink:href='#icon-testPlay')
button(type="button" v-if="listAddBtn" @contextmenu.capture.stop :tips="$t('material.list_buttons.add_to')" @click.stop="handleClick('listAdd')")
button(type="button" v-if="listAddBtn" @contextmenu.capture.stop :tips="$t('list__add_to')" @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' v-once)
use(xlink:href='#icon-addTo')
button(type="button" v-if="downloadBtn && setting.download.enable" @contextmenu.capture.stop :tips="$t('material.list_buttons.download')" @click.stop="handleClick('download')")
button(type="button" v-if="downloadBtn && setting.download.enable" @contextmenu.capture.stop :tips="$t('list__download')" @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' v-once)
use(xlink:href='#icon-download')
//- button(type="button" :tips="$t('material.list_buttons.add')" v-if="userInfo" @click.stop="handleClick('add')")
//- button(type="button" :tips="$t('list__add')" v-if="userInfo" @click.stop="handleClick('add')")
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="startBtn" @contextmenu.capture.stop :tips="$t('material.list_buttons.start')" @click.stop="handleClick('start')")
button(type="button" v-if="startBtn" @contextmenu.capture.stop :tips="$t('list__start')" @click.stop="handleClick('start')")
svg(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' v-once)
use(xlink:href='#icon-play')
button(type="button" v-if="pauseBtn" @contextmenu.capture.stop :tips="$t('material.list_buttons.pause')" @click.stop="handleClick('pause')")
button(type="button" v-if="pauseBtn" @contextmenu.capture.stop :tips="$t('list__pause')" @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' v-once)
use(xlink:href='#icon-pause')
button(type="button" v-if="fileBtn" @contextmenu.capture.stop :tips="$t('material.list_buttons.file')" @click.stop="handleClick('file')")
button(type="button" v-if="fileBtn" @contextmenu.capture.stop :tips="$t('list__file')" @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' v-once)
use(xlink:href='#icon-musicFile')
button(type="button" v-if="searchBtn" @contextmenu.capture.stop :tips="$t('material.list_buttons.search')" @click.stop="handleClick('search')")
button(type="button" v-if="searchBtn" @contextmenu.capture.stop :tips="$t('list__search')" @click.stop="handleClick('search')")
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' v-once)
use(xlink:href='#icon-search')
button(type="button" v-if="removeBtn" :tips="$t('material.list_buttons.remove')" @click.stop="handleClick('remove')")
button(type="button" v-if="removeBtn" :tips="$t('list__remove')" @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' v-once)
use(xlink:href='#icon-delete')
@@ -85,7 +85,7 @@ export default {
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.btns {
line-height: 1;
@@ -119,7 +119,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.btns {
button {
color: ~'@{color-@{value}-btn}';
+60 -19
View File
@@ -1,22 +1,21 @@
<template lang="pug">
transition(enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut")
div(:class="$style.modal" v-show="show" @click="bgClose && close()")
transition(:enter-active-class="inClass"
:leave-active-class="outClass"
@after-leave="$emit('after-leave', $event)"
)
div(:class="$style.content" v-show="show" @click.stop)
header(:class="$style.header")
button(type="button" @click="close" v-if="closeBtn")
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')
slot
teleport(:to="teleport")
div(:class="$style.container" v-if="showModal")
transition(enter-active-class="animated fadeIn" leave-active-class="animated fadeOut")
div(:class="$style.modal" v-show="showContent" @click="bgClose && close()")
transition(:enter-active-class="inClass" :leave-active-class="outClass" @after-enter="$emit('after-enter', $event)" @after-leave="handleAfterLeave")
div(:class="$style.content" v-show="showContent" @click.stop)
header(:class="$style.header")
button(type="button" @click="close" v-if="closeBtn")
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')
slot
</template>
<script>
import { getRandom } from '../../utils'
import { getRandom } from '@renderer/utils'
import { mapGetters } from 'vuex'
import { nextTick } from '@renderer/utils/vueTools'
export default {
props: {
show: {
@@ -31,7 +30,12 @@ export default {
type: Boolean,
default: false,
},
teleport: {
type: String,
default: '#root',
},
},
emits: ['after-enter', 'after-leave', 'close'],
data() {
return {
animateIn: [
@@ -81,6 +85,8 @@ export default {
inClass: 'animated jackInTheBox',
outClass: 'animated flipOutX',
unwatchFn: null,
showModal: false,
showContent: false,
}
},
computed: {
@@ -90,17 +96,41 @@ export default {
'setting.randomAnimate'(n) {
n ? this.createWatch() : this.removeWatch()
},
show: {
handler(val) {
if (val) {
this.showModal = true
nextTick(() => {
this.showContent = true
})
} else {
this.showContent = false
}
},
immediate: true,
},
},
mounted() {
if (this.setting.randomAnimate) this.createWatch()
},
beforeDestroy() {
beforeUnmount() {
this.removeWatch()
},
methods: {
check(isShow) {
if (isShow) {
this.showModal = true
nextTick(() => {
this.showContent = true
})
} else {
this.showContent = false
}
},
createWatch() {
this.removeWatch()
this.unwatchFn = this.$watch('show', function(n) {
if (!n) return
this.inClass = 'animated ' + this.animateIn[getRandom(0, this.animateIn.length)]
this.outClass = 'animated ' + this.animateOut[getRandom(0, this.animateOut.length)]
})
@@ -115,29 +145,39 @@ export default {
close() {
this.$emit('close')
},
handleAfterLeave(event) {
this.$emit('after-leave', event)
this.showModal = false
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
@import '@renderer/assets/styles/layout.less';
.modal {
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99;
}
.modal {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .3);
display: grid;
align-items: center;
justify-items: center;
z-index: 99;
// will-change: transform;
}
.content {
position: relative;
border-radius: 5px;
box-shadow: 0 0 3px rgba(0, 0, 0, .3);
overflow: hidden;
@@ -147,6 +187,7 @@ export default {
position: relative;
display: flex;
flex-flow: column nowrap;
z-index: 100;
> * {
background-color: @color-theme_2-background_2;
@@ -185,7 +226,7 @@ export default {
}
each(@themes, {
:global(#container.@{value}) {
:global(#root.@{value}) {
.modal {
background-color: rgba(0, 0, 0, .3);
}
@@ -0,0 +1,337 @@
<template lang="pug">
div(:class="$style.songList")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(:class="$style.list")
div.thead(:class="$style.thead")
table
thead
tr
th.nobreak.center(:class="$style.th" :style="{ width: rowWidth.r1 }") #
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r2 }") {{$t('music_name')}}
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r3 }") {{$t('music_singer')}}
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r4 }") {{$t('music_album')}}
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r5 }") {{$t('music_time')}}
th.nobreak(:class="$style.th" :style="{ width: rowWidth.r6 }") {{$t('action')}}
div(:class="$style.content")
div(v-show="list.length" :class="$style.content" ref="dom_listContent")
base-virtualized-list(:list="list" key-name="songmid" ref="listRef" :item-height="listItemHeight"
containerClass="scroll" contentClass="list" @contextmenu.capture="handleListRightClick")
template(#default="{ item, index }")
div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
:class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]")
div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :tips="item.name + ((item._types.ape || item._types.flac || item._types.wav) ? ` - ${$t('tag__lossless')}` : item._types['320k'] ? ` - ${$t('tag__high_quality')}` : '')")
span.select {{item.name}}
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('tag__lossless')}}
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('tag__high_quality')}}
div.list-item-cell(:style="{ width: rowWidth.r3 }" :tips="item.singer")
span.select {{item.singer}}
div.list-item-cell(:style="{ width: rowWidth.r4 }" :tips="item.albumName")
span.select {{item.albumName}}
div.list-item-cell(:style="{ width: rowWidth.r5 }")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
div.list-item-cell(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :class="$style.btns"
:remove-btn="false" @btn-click="handleListBtnClick"
:download-btn="assertApiSupport(item.source)")
template(#footer)
div(:class="$style.pagination")
material-pagination(:count="total" :limit="limit" :page="page" @btn-click="$emit('togglePage', $event)")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(v-show="!list.length" :class="$style.noitem")
p(v-html="noItem")
//- material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
common-download-modal(v-model:show="isShowDownload" :musicInfo="selectedDownloadMusicInfo" teleport="#view")
common-download-multiple-modal(v-model:show="isShowDownloadMultiple" :list="selectedList" @confirm="removeAllSelect" teleport="#view")
common-list-add-modal(v-model:show="isShowListAdd" :musicInfo="selectedAddMusicInfo" teleport="#view")
common-list-add-multiple-modal(v-model:show="isShowListAddMultiple" :musicList="selectedList" @confirm="removeAllSelect" teleport="#view")
base-menu(:menus="menus" :location="menuLocation" item-name="name" :isShow="isShowItemMenu" @menu-click="handleMenuClick")
</template>
<script>
import { clipboardWriteText, assertApiSupport } from '@renderer/utils'
import { ref, useCssModule } from '@renderer/utils/vueTools'
import useList from './useList'
import useMenu from './useMenu'
import usePlay from './usePlay'
import useMusicDownload from './useMusicDownload'
import useMusicAdd from './useMusicAdd'
import useMusicActions from './useMusicActions'
export default {
name: 'MaterialOnlineList',
props: {
list: {
type: Array,
default() {
return []
},
},
page: {
type: Number,
required: true,
},
limit: {
type: Number,
required: true,
},
total: {
type: Number,
required: true,
},
rowWidth: {
type: Object,
default() {
return {
r1: '5%',
r2: 'auto',
r3: '22%',
r4: '22%',
r5: '8%',
r6: '13%',
}
},
},
noItem: {
type: String,
},
},
emits: ['show-menu', 'togglePage'],
setup(props, { emit }) {
const rightClickSelectedIndex = ref(-1)
const dom_listContent = ref(null)
const listRef = ref(null)
const styles = useCssModule()
const {
selectedList,
listItemHeight,
handleSelectData,
removeAllSelect,
} = useList({ props, emit })
const {
handlePlayMusic,
handlePlayMusicLater,
doubleClickPlay,
} = usePlay({ selectedList, props, removeAllSelect })
const {
isShowListAdd,
isShowListAddMultiple,
selectedAddMusicInfo,
handleShowMusicAddModal,
} = useMusicAdd({ selectedList, props })
const {
isShowDownload,
isShowDownloadMultiple,
selectedDownloadMusicInfo,
handleShowDownloadModal,
} = useMusicDownload({ selectedList, props })
const {
handleSearch,
handleOpenMusicDetail,
} = useMusicActions({ props })
const {
menus,
menuLocation,
isShowItemMenu,
showMenu,
hideMenu,
menuClick,
} = useMenu({
listRef,
assertApiSupport,
emit,
handleShowDownloadModal,
handlePlayMusic,
handlePlayMusicLater,
handleSearch,
handleShowMusicAddModal,
handleOpenMusicDetail,
})
const handleListItemClick = (event, index) => {
if (rightClickSelectedIndex.value > -1) return
handleSelectData(index)
doubleClickPlay(index)
}
const handleListItemRightClick = (event, index) => {
rightClickSelectedIndex.value = index
showMenu(event, props.list[index], index)
}
const handleMenuClick = (action) => {
let index = rightClickSelectedIndex.value
rightClickSelectedIndex.value = -1
menuClick(action, index)
}
const handleListRightClick = (event) => {
if (!event.target.classList.contains('select')) return
event.stopImmediatePropagation()
let classList = dom_listContent.value.classList
classList.add(styles.copying)
window.requestAnimationFrame(() => {
let str = window.getSelection().toString()
classList.remove(styles.copying)
str = str.split(/\n\n/).map(s => s.replace(/\n/g, ' ')).join('\n').trim()
if (!str.length) return
clipboardWriteText(str)
})
}
const handleListBtnClick = ({ action, index }) => {
switch (action) {
case 'download':
handleShowDownloadModal(index, true)
break
case 'play':
handlePlayMusic(index, true)
break
case 'search':
handleSearch(index)
break
case 'listAdd':
handleShowMusicAddModal(index, true)
break
}
}
const scrollToTop = () => {
listRef.value.scrollTo(0, true)
}
return {
listItemHeight,
handleListItemClick,
selectedList,
handleListItemRightClick,
removeAllSelect,
handleListBtnClick,
rightClickSelectedIndex,
dom_listContent,
listRef,
menus,
isShowItemMenu,
menuLocation,
handleMenuClick,
hideMenu,
handleListRightClick,
assertApiSupport,
isShowListAdd,
isShowListAddMultiple,
selectedAddMusicInfo,
isShowDownload,
isShowDownloadMultiple,
selectedDownloadMusicInfo,
scrollToTop,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.song-list {
overflow: hidden;
height: 100%;
display: flex;
flex-flow: column nowrap;
position: relative;
}
.list {
position: relative;
font-size: 14px;
overflow: hidden;
display: flex;
flex-flow: column nowrap;
height: 100%;
}
.thead {
flex: none;
}
.th:first-child {
color: @color-theme_2-font-label;
// padding-left: 10px;
}
.content {
flex: auto;
min-height: 0;
position: relative;
height: 100%;
&.copying {
.no-select {
display: none;
}
}
}
:global(.list) {
height: 100%;
overflow-y: auto;
:global(.list-item-cell) {
font-size: 12px !important;
:global(.badge) {
margin-left: 3px;
}
&:first-child {
// padding-left: 10px;
font-size: 11px !important;
color: @color-theme_2-font-label !important;
}
}
:global(.badge) {
opacity: .85;
}
}
.pagination {
text-align: center;
padding: 15px 0;
// left: 50%;
// transform: translateX(-50%);
}
.noitem {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 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(#root.@{value}) {
.th:first-child {
color: ~'@{color-@{value}-theme_2-font-label}';
}
:global(.list) {
:global(.list-item-cell) {
&:first-child {
color: ~'@{color-@{value}-theme_2-font-label}' !important;
}
}
}
.noitem {
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
})
</style>
@@ -0,0 +1,105 @@
import { computed, useRefGetter, watch, ref, onBeforeUnmount } from '@renderer/utils/vueTools'
import { windowSizeList } from '@common/config'
const useKeyEvent = ({ handleSelectAllData }) => {
const keyEvent = {
isShiftDown: false,
isModDown: false,
}
const handle_key_shift_down = () => {
if (!keyEvent.isShiftDown) keyEvent.isShiftDown = true
}
const handle_key_shift_up = () => {
if (keyEvent.isShiftDown) keyEvent.isShiftDown = false
}
const handle_key_mod_down = () => {
if (!keyEvent.isModDown) keyEvent.isModDown = true
}
const handle_key_mod_up = () => {
if (keyEvent.isModDown) keyEvent.isModDown = false
}
const handle_key_mod_a_down = ({ event }) => {
if (event.target.tagName == 'INPUT') return
event.preventDefault()
if (event.repeat) return
keyEvent.isModDown = false
handleSelectAllData()
}
onBeforeUnmount(() => {
window.eventHub.off('key_shift_down', handle_key_shift_down)
window.eventHub.off('key_shift_up', handle_key_shift_up)
window.eventHub.off('key_mod_down', handle_key_mod_down)
window.eventHub.off('key_mod_up', handle_key_mod_up)
window.eventHub.off('key_mod+a_down', handle_key_mod_a_down)
})
window.eventHub.on('key_shift_down', handle_key_shift_down)
window.eventHub.on('key_shift_up', handle_key_shift_up)
window.eventHub.on('key_mod_down', handle_key_mod_down)
window.eventHub.on('key_mod_up', handle_key_mod_up)
window.eventHub.on('key_mod+a_down', handle_key_mod_a_down)
return keyEvent
}
export default ({ props }) => {
const selectedList = ref([])
const setting = useRefGetter('setting')
let lastSelectIndex = -1
const listItemHeight = computed(() => {
return parseInt(windowSizeList.find(item => item.id == setting.value.windowSizeId).fontSize) / 16 * 37
})
const removeAllSelect = () => {
selectedList.value = []
}
const handleSelectAllData = () => {
removeAllSelect()
selectedList.value = [...props.list]
}
const keyEvent = useKeyEvent({ handleSelectAllData })
const handleSelectData = clickIndex => {
if (keyEvent.isShiftDown) {
if (selectedList.value.length) {
removeAllSelect()
if (lastSelectIndex != clickIndex) {
let isNeedReverse = false
if (clickIndex < lastSelectIndex) {
let temp = lastSelectIndex
lastSelectIndex = clickIndex
clickIndex = temp
isNeedReverse = true
}
selectedList.value = props.list.slice(lastSelectIndex, clickIndex + 1)
if (isNeedReverse) selectedList.value.reverse()
}
} else {
selectedList.value.push(props.list[clickIndex])
lastSelectIndex = clickIndex
}
} else if (keyEvent.isModDown) {
lastSelectIndex = clickIndex
let item = props.list[clickIndex]
let index = selectedList.value.indexOf(item)
if (index < 0) {
selectedList.value.push(item)
} else {
selectedList.value.splice(index, 1)
}
} else if (selectedList.value.length) {
removeAllSelect()
}
}
watch(() => props.list, removeAllSelect)
return {
selectedList,
listItemHeight,
removeAllSelect,
handleSelectData,
}
}
@@ -0,0 +1,124 @@
import { computed, ref, useI18n, useCssModule, nextTick } from '@renderer/utils/vueTools'
import musicSdk from '@renderer/utils/music'
export default ({
listRef,
assertApiSupport,
emit,
handleShowDownloadModal,
handlePlayMusic,
handlePlayMusicLater,
handleSearch,
handleShowMusicAddModal,
handleOpenMusicDetail,
}) => {
const itemMenuControl = ref({
play: true,
addTo: true,
playLater: true,
download: true,
search: true,
sourceDetail: true,
})
const { t } = useI18n()
const styles = useCssModule()
const menuLocation = ref({ x: 0, y: 0 })
const isShowItemMenu = ref(false)
const menus = computed(() => {
return [
{
name: t('list__play'),
action: 'play',
disabled: !itemMenuControl.value.play,
},
{
name: t('list__download'),
action: 'download',
disabled: !itemMenuControl.value.download,
},
{
name: t('list__play_later'),
action: 'playLater',
disabled: !itemMenuControl.value.playLater,
},
{
name: t('list__search'),
action: 'search',
disabled: !itemMenuControl.value.search,
},
{
name: t('list__add_to'),
action: 'addTo',
disabled: !itemMenuControl.value.addTo,
},
{
name: t('list__source_detail'),
action: 'sourceDetail',
disabled: !itemMenuControl.value.sourceDetail,
},
]
})
const showMenu = (event, musicInfo) => {
itemMenuControl.value.sourceDetail = !!musicSdk[musicInfo.source].getMusicDetailPageUrl
// this.listMenu.itemMenuControl.play =
// this.listMenu.itemMenuControl.playLater =
itemMenuControl.download = assertApiSupport(musicInfo.source)
let dom_container = event.target.closest('.' + styles.songList)
const getOffsetValue = (target, x = 0, y = 0) => {
if (target === dom_container) return { x, y }
if (!target) return { x: 0, y: 0 }
x += target.offsetLeft
y += target.offsetTop
return getOffsetValue(target.offsetParent, x, y)
}
let { x, y } = getOffsetValue(event.target)
menuLocation.value.x = x + event.offsetX
menuLocation.value.y = y + event.offsetY - listRef.value.getScrollTop()
emit('show-menu')
nextTick(() => {
isShowItemMenu.value = true
})
}
const hideMenu = () => {
isShowItemMenu.value = false
}
const menuClick = (action, index) => {
// console.log(action)
hideMenu()
if (!action) return
switch (action.action) {
case 'download':
handleShowDownloadModal(index)
break
case 'play':
handlePlayMusic(index)
break
case 'playLater':
handlePlayMusicLater(index)
break
case 'search':
handleSearch(index)
break
case 'addTo':
handleShowMusicAddModal(index)
break
case 'sourceDetail':
handleOpenMusicDetail(index)
}
}
return {
menus,
menuLocation,
isShowItemMenu,
showMenu,
menuClick,
hideMenu,
}
}
@@ -0,0 +1,30 @@
import { useRouter } from '@renderer/utils/vueTools'
import musicSdk from '@renderer/utils/music'
import { openUrl } from '@renderer/utils'
export default ({ props }) => {
const router = useRouter()
const handleSearch = index => {
const info = props.list[index]
router.push({
path: 'search',
query: {
text: `${info.name} ${info.singer}`,
},
})
}
const handleOpenMusicDetail = index => {
const minfo = props.list[index]
const url = musicSdk[minfo.source].getMusicDetailPageUrl(minfo)
if (!url) return
openUrl(url)
}
return {
handleSearch,
handleOpenMusicDetail,
}
}
@@ -0,0 +1,25 @@
import { ref, nextTick } from '@renderer/utils/vueTools'
export default ({ selectedList, props }) => {
const isShowListAdd = ref(false)
const isShowListAddMultiple = ref(false)
const selectedAddMusicInfo = ref(null)
const handleShowMusicAddModal = (index, single) => {
if (selectedList.value.length && !single) {
isShowListAddMultiple.value = true
} else {
selectedAddMusicInfo.value = props.list[index]
nextTick(() => {
isShowListAdd.value = true
})
}
}
return {
isShowListAdd,
isShowListAddMultiple,
selectedAddMusicInfo,
handleShowMusicAddModal,
}
}

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