diff --git a/postcss.config.js b/postcss.config.js
index d44c46e1..df5bbc3b 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -15,7 +15,7 @@ module.exports = {
'*-height', '*-width',
'flex', '::-webkit-scrollbar',
'top', 'left', 'bottom', 'right',
- 'border-radius',
+ 'border-radius', 'gap',
],
selectorBlackList: ['html', 'ignore-to-rem'],
replace: true,
diff --git a/publish/changeLog.md b/publish/changeLog.md
index 3c98e5b4..80f8988d 100644
--- a/publish/changeLog.md
+++ b/publish/changeLog.md
@@ -1,6 +1,7 @@
### 新增
- 新增歌词简体中文转繁体中文,当软件语言被设置为繁体中文后,播放歌曲的歌词也将自动转成繁体中文显示
+- 为方便分享歌曲列表,新增单个列表导入/导出功能,可在右击“我的列表”里的列表名后弹出的菜单中使用
### 修复
diff --git a/src/renderer/components/material/Modal.vue b/src/renderer/components/material/Modal.vue
index 6b6de777..26b172d7 100644
--- a/src/renderer/components/material/Modal.vue
+++ b/src/renderer/components/material/Modal.vue
@@ -3,7 +3,9 @@ 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")
+ :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")
@@ -141,6 +143,7 @@ export default {
overflow: hidden;
max-height: 80%;
max-width: 76%;
+ min-width: 220px;
position: relative;
display: flex;
flex-flow: column nowrap;
diff --git a/src/renderer/lang/en-us/base.json b/src/renderer/lang/en-us/base.json
index 09199968..b40621d8 100644
--- a/src/renderer/lang/en-us/base.json
+++ b/src/renderer/lang/en-us/base.json
@@ -1,5 +1,7 @@
{
- "date_format_second": "{num} seconds ago",
+ "cancel_button_text": "Cancel",
+ "confirm_button_text": "OK",
+ "date_format_hour": "{num} hours ago",
"date_format_minute": "{num} minutes ago",
- "date_format_hour": "{num} hours ago"
+ "date_format_second": "{num} seconds ago"
}
diff --git a/src/renderer/lang/en-us/view/list.json b/src/renderer/lang/en-us/view/list.json
index 6866910a..29c4a70d 100644
--- a/src/renderer/lang/en-us/view/list.json
+++ b/src/renderer/lang/en-us/view/list.json
@@ -1,28 +1,35 @@
{
- "lists_new_list_btn": "Create list",
- "lists_new_list_input": "New list...",
- "lists_rename": "Rename",
- "lists_moveup": "Move Up",
- "lists_movedown": "Move Down",
- "lists_sync": "Update",
- "lists_remove": "Remove",
+ "action": "Manage",
+ "album": "Album",
+ "default_list": "Recently Played",
+ "list_add_to": "Add to ...",
+ "list_copy_name": "Copy name",
+ "list_download": "Download",
+ "list_move_to": "Move to ...",
"list_play": "Play",
"list_play_later": "Play later",
- "list_copy_name": "Copy name",
- "list_add_to": "Add to ...",
- "list_move_to": "Move to ...",
- "list_sort": "Adjust position",
- "list_download": "Download",
- "list_search": "Search",
"list_remove": "Remove",
+ "list_search": "Search",
+ "list_sort": "Adjust position",
"list_source_detail": "Song Page",
- "default_list": "Recently Played",
+ "lists_export": "Export",
+ "lists_export_part_desc": "Choose where to save the list file",
+ "lists_import": "Import",
+ "lists_import_part_button_cancel": "No",
+ "lists_import_part_button_confirm": "Overwrite",
+ "lists_import_part_confirm": "The imported list ({importName}) has the same ID as the local list ({localName}). Do you overwrite the local list?",
+ "lists_import_part_desc": "Select list file",
+ "lists_movedown": "Move Down",
+ "lists_moveup": "Move Up",
+ "lists_new_list_btn": "Create list",
+ "lists_new_list_input": "New list...",
+ "lists_remove": "Remove",
+ "lists_rename": "Rename",
+ "lists_sync": "Update",
+ "loding_list": "Loading...",
"love_list": "Favorites",
"name": "Name",
+ "no_item": "Nothing's here...",
"singer": "Artist",
- "album": "Album",
- "action": "Manage",
- "time": "Length",
- "loding_list": "Loading...",
- "no_item": "Nothing's here..."
+ "time": "Length"
}
diff --git a/src/renderer/lang/zh-cn/base.json b/src/renderer/lang/zh-cn/base.json
index 6a12f4bf..2df59ecb 100644
--- a/src/renderer/lang/zh-cn/base.json
+++ b/src/renderer/lang/zh-cn/base.json
@@ -1,5 +1,7 @@
{
- "date_format_second": "{num}秒前",
+ "cancel_button_text": "我不",
+ "confirm_button_text": "好的",
+ "date_format_hour": "{num}小时前",
"date_format_minute": "{num}分钟前",
- "date_format_hour": "{num}小时前"
+ "date_format_second": "{num}秒前"
}
diff --git a/src/renderer/lang/zh-cn/view/list.json b/src/renderer/lang/zh-cn/view/list.json
index 9bc45797..835e51b4 100644
--- a/src/renderer/lang/zh-cn/view/list.json
+++ b/src/renderer/lang/zh-cn/view/list.json
@@ -1,28 +1,35 @@
{
- "lists_new_list_btn": "新建列表",
- "lists_new_list_input": "新列表...",
- "lists_rename": "重命名",
- "lists_moveup": "上移",
- "lists_movedown": "下移",
- "lists_sync": "更新",
- "lists_remove": "删除",
+ "action": "操作",
+ "album": "专辑",
+ "default_list": "试听列表",
+ "list_add_to": "添加到...",
+ "list_copy_name": "复制歌曲名",
+ "list_download": "下载",
+ "list_move_to": "移动到...",
"list_play": "播放",
"list_play_later": "稍后播放",
- "list_copy_name": "复制歌曲名",
- "list_source_detail": "歌曲详情页",
- "list_add_to": "添加到...",
- "list_move_to": "移动到...",
- "list_sort": "调整位置",
- "list_download": "下载",
- "list_search": "搜索",
"list_remove": "删除",
- "default_list": "试听列表",
+ "list_search": "搜索",
+ "list_sort": "调整位置",
+ "list_source_detail": "歌曲详情页",
+ "lists_export": "导出",
+ "lists_export_part_desc": "选择列表文件保存位置",
+ "lists_import": "导入",
+ "lists_import_part_button_cancel": "不要啊",
+ "lists_import_part_button_confirm": "覆盖掉",
+ "lists_import_part_confirm": "导入的列表({importName})与本地列表({localName})的ID相同,是否覆盖本地列表?",
+ "lists_import_part_desc": "选择列表文件",
+ "lists_movedown": "下移",
+ "lists_moveup": "上移",
+ "lists_new_list_btn": "新建列表",
+ "lists_new_list_input": "新列表...",
+ "lists_remove": "删除",
+ "lists_rename": "重命名",
+ "lists_sync": "更新",
+ "loding_list": "加载中...",
"love_list": "收藏",
"name": "歌曲名",
+ "no_item": "列表竟然是空的...",
"singer": "歌手",
- "album": "专辑",
- "action": "操作",
- "time": "时长",
- "loding_list": "加载中...",
- "no_item": "列表竟然是空的..."
+ "time": "时长"
}
diff --git a/src/renderer/lang/zh-tw/base.json b/src/renderer/lang/zh-tw/base.json
index 8c323e89..60631394 100644
--- a/src/renderer/lang/zh-tw/base.json
+++ b/src/renderer/lang/zh-tw/base.json
@@ -1,5 +1,7 @@
{
- "date_format_second": "{num}秒前",
+ "cancel_button_text": "取消",
+ "confirm_button_text": "好的",
+ "date_format_hour": "{num}小時前",
"date_format_minute": "{num}分鐘前",
- "date_format_hour": "{num}小時前"
+ "date_format_second": "{num}秒前"
}
diff --git a/src/renderer/lang/zh-tw/view/list.json b/src/renderer/lang/zh-tw/view/list.json
index 80de4fda..55cb5b36 100644
--- a/src/renderer/lang/zh-tw/view/list.json
+++ b/src/renderer/lang/zh-tw/view/list.json
@@ -1,28 +1,35 @@
{
- "lists_new_list_btn": "新建列表",
- "lists_new_list_input": "新列表...",
- "lists_rename": "重命名",
- "lists_moveup": "上移",
- "lists_movedown": "下移",
- "lists_sync": "更新",
- "lists_remove": "刪除",
+ "action": "操作",
+ "album": "專輯",
+ "default_list": "試聽列表",
+ "list_add_to": "添加到...",
+ "list_copy_name": "複製歌曲名",
+ "list_download": "下載",
+ "list_move_to": "移動到...",
"list_play": "播放",
"list_play_later": "稍後播放",
- "list_copy_name": "複製歌曲名",
- "list_add_to": "添加到...",
- "list_move_to": "移動到...",
- "list_sort": "調整位置",
- "list_download": "下載",
- "list_search": "搜索",
"list_remove": "刪除",
+ "list_search": "搜索",
+ "list_sort": "調整位置",
"list_source_detail": "歌曲詳情頁",
- "default_list": "試聽列表",
+ "lists_export": "導出",
+ "lists_export_part_desc": "選擇列表文件保存位置",
+ "lists_import": "導入",
+ "lists_import_part_button_cancel": "不要啊",
+ "lists_import_part_button_confirm": "覆蓋掉",
+ "lists_import_part_confirm": "導入的列表({importName})與本地列表({localName})的ID相同,是否覆蓋本地列表?",
+ "lists_import_part_desc": "選擇列表文件",
+ "lists_movedown": "下移",
+ "lists_moveup": "上移",
+ "lists_new_list_btn": "新建列表",
+ "lists_new_list_input": "新列表...",
+ "lists_remove": "刪除",
+ "lists_rename": "重命名",
+ "lists_sync": "更新",
+ "loding_list": "加載中...",
"love_list": "收藏列表",
"name": "歌曲名",
+ "no_item": "列表竟然是空的...",
"singer": "歌手",
- "album": "專輯",
- "action": "操作",
- "time": "時長",
- "loding_list": "加載中...",
- "no_item": "列表竟然是空的..."
+ "time": "時長"
}
diff --git a/src/renderer/plugins/Dialog/Dialog.vue b/src/renderer/plugins/Dialog/Dialog.vue
new file mode 100644
index 00000000..561b9e7e
--- /dev/null
+++ b/src/renderer/plugins/Dialog/Dialog.vue
@@ -0,0 +1,73 @@
+
+
+ {{message}}
+
+
+
+
+
+
+
diff --git a/src/renderer/plugins/Dialog/index.js b/src/renderer/plugins/Dialog/index.js
new file mode 100644
index 00000000..d470596a
--- /dev/null
+++ b/src/renderer/plugins/Dialog/index.js
@@ -0,0 +1,50 @@
+import Dialog from './Dialog'
+import i18n from '../i18n'
+import store from '@renderer/store'
+import Vue from 'vue'
+
+const defaultOptions = {
+ message: '',
+ showCancel: false,
+ cancelButtonText: '',
+ confirmButtonText: '',
+}
+
+const dialog = {
+ install(Vue, options) {
+ const DialogConstructor = Vue.extend(Dialog)
+
+ const dialog = function Dialog(options) {
+ const { message, showCancel, cancelButtonText, confirmButtonText } =
+ Object.assign({}, defaultOptions, typeof options == 'string' ? { message: options } : options || {})
+ return new Promise((resolve, reject) => {
+ let instance = new DialogConstructor({ i18n, store }).$mount(document.createElement('div'))
+
+ // 属性设置
+ instance.visible = true
+ instance.message = message
+ instance.showCancel = showCancel
+ instance.cancelButtonText = cancelButtonText
+ instance.confirmButtonText = confirmButtonText
+
+ // 挂载
+ document.getElementById('container').appendChild(instance.$el)
+
+ instance.handleCancel = () => {
+ instance.visible = false
+ resolve(false)
+ }
+
+ instance.handleComfirm = () => {
+ instance.visible = false
+ resolve(true)
+ }
+ })
+ }
+ dialog.confirm = options => dialog({ ...options, showCancel: true })
+
+ Vue.prototype.$dialog = dialog
+ },
+}
+
+Vue.use(dialog)
diff --git a/src/renderer/plugins/index.js b/src/renderer/plugins/index.js
index e94f9bdc..c62c44ae 100644
--- a/src/renderer/plugins/index.js
+++ b/src/renderer/plugins/index.js
@@ -1,2 +1,3 @@
// import './axios'
+import './Dialog'
import './Tips'
diff --git a/src/renderer/store/modules/download.js b/src/renderer/store/modules/download.js
index 3108b81f..2ebe539d 100644
--- a/src/renderer/store/modules/download.js
+++ b/src/renderer/store/modules/download.js
@@ -11,6 +11,7 @@ import {
getMusicUrl as getMusicUrlFormStorage,
setMusicUrl,
assertApiSupport,
+ filterFileName,
} from '../../utils'
import { NAMES, rendererInvoke } from '@common/ipc'
@@ -33,7 +34,6 @@ const dls = {}
const tryNum = {}
let isRuningActionTask = false
-const filterFileName = /[\\/:*?#"<>|]/g
// getters
const getters = {
@@ -378,9 +378,9 @@ const actions = {
statusText: '待下载',
url: null,
// songmid: musicInfo.songmid,
- fileName: `${rootState.setting.download.fileName
+ fileName: filterFileName(`${rootState.setting.download.fileName
.replace('歌名', musicInfo.name)
- .replace('歌手', musicInfo.singer)}.${ext}`.replace(filterFileName, ''),
+ .replace('歌手', musicInfo.singer)}.${ext}`),
progress: {
downloaded: 0,
total: 0,
diff --git a/src/renderer/store/modules/list.js b/src/renderer/store/modules/list.js
index 82f712c9..8fd2c3fe 100644
--- a/src/renderer/store/modules/list.js
+++ b/src/renderer/store/modules/list.js
@@ -293,11 +293,11 @@ const mutations = {
const targetMusicInfo = targetList.list.find(item => item.songmid == id)
if (targetMusicInfo) Object.assign(targetMusicInfo, data)
},
- createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, isSync }) {
+ createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId, position, isSync }) {
if (!isSync) {
window.eventHub.$emit(eventSyncName.send_action_list, {
action: 'create_user_list',
- data: { name, id, list, source, sourceListId },
+ data: { name, id, list, source, sourceListId, position },
})
}
@@ -311,7 +311,11 @@ const mutations = {
source,
sourceListId,
}
- state.userList.push(newList)
+ if (position == null) {
+ state.userList.push(newList)
+ } else {
+ state.userList.splice(position + 1, 0, newList)
+ }
allListUpdate(newList)
}
this.commit('list/listAddMultiple', { id, list, isSync: true })
diff --git a/src/renderer/utils/index.js b/src/renderer/utils/index.js
index fd507f7a..374603b4 100644
--- a/src/renderer/utils/index.js
+++ b/src/renderer/utils/index.js
@@ -4,6 +4,7 @@ import { shell, clipboard } from 'electron'
import crypto from 'crypto'
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
import iconv from 'iconv-lite'
+import { gzip, gunzip } from 'zlib'
/**
* 获取两个数之间的随机整数,大于等于min,小于max
@@ -433,3 +434,39 @@ export const setMusicUrl = (musicInfo, type, url) => rendererSend(NAMES.mainWind
url,
})
export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url)
+
+export const gzipData = str => {
+ return new Promise((resolve, reject) => {
+ gzip(str, (err, result) => {
+ if (err) return reject(err)
+ resolve(result)
+ })
+ })
+}
+export const gunzipData = buf => {
+ return new Promise((resolve, reject) => {
+ gunzip(buf, (err, result) => {
+ if (err) return reject(err)
+ resolve(result.toString())
+ })
+ })
+}
+
+export const saveLxConfigFile = async(path, data) => {
+ if (!path.endsWith('.lxmc')) path += '.lxmc'
+ fs.writeFile(path, await gzipData(JSON.stringify(data)), 'binary', err => {
+ console.log(err)
+ })
+}
+
+export const readLxConfigFile = async path => {
+ let isJSON = path.endsWith('.json')
+ let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
+ if (!data || isJSON) return data
+ data = await gunzipData(Buffer.from(data, 'binary'))
+ return data.toString('utf8')
+}
+
+
+const fileNameRxp = /[\\/:*?#"<>|]/g
+export const filterFileName = name => name.replace(fileNameRxp, '')
diff --git a/src/renderer/views/List.vue b/src/renderer/views/List.vue
index e9022860..bd5df148 100644
--- a/src/renderer/views/List.vue
+++ b/src/renderer/views/List.vue
@@ -7,9 +7,13 @@
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='70%' viewBox='0 0 24 24' space='preserve')
use(xlink:href='#icon-list-add')
ul.scroll(:class="$style.listsContent" ref="dom_lists_list")
- li(:class="[$style.listsItem, defaultList.id == listId ? $style.active : null]" :tips="defaultList.name" @click="handleListToggle(defaultList.id)")
+ li(:class="[$style.listsItem, defaultList.id == listId ? $style.active : null]" :tips="defaultList.name"
+ @contextmenu="handleListsItemRigthClick($event, -2)"
+ @click="handleListToggle(defaultList.id)")
span(:class="$style.listsLabel") {{defaultList.name}}
- li(:class="[$style.listsItem, loveList.id == listId ? $style.active : null]" :tips="loveList.name" @click="handleListToggle(loveList.id)")
+ li(:class="[$style.listsItem, loveList.id == listId ? $style.active : null]" :tips="loveList.name"
+ @contextmenu="handleListsItemRigthClick($event, -1)"
+ @click="handleListToggle(loveList.id)")
span(:class="$style.listsLabel") {{loveList.name}}
li.user-list(
:class="[$style.listsItem, item.id == listId ? $style.active : null, listsData.rightClickItemIndex == index ? $style.clicked : null, fetchingListStatus[item.id] ? $style.fetching : null]"
@@ -74,7 +78,7 @@
diff --git a/src/renderer/views/Setting.vue b/src/renderer/views/Setting.vue
index da70527a..9d5cc8a7 100644
--- a/src/renderer/views/Setting.vue
+++ b/src/renderer/views/Setting.vue
@@ -329,16 +329,16 @@ import {
setWindowSize,
getSetting,
saveSetting,
+ saveLxConfigFile,
+ readLxConfigFile,
} from '../utils'
import { rendererSend, rendererInvoke, rendererOn, NAMES, rendererOff } from '@common/ipc'
import { mergeSetting, isMac } from '../../common/utils'
import apiSourceInfo from '../utils/music/api-source-info'
-import fs from 'fs'
import languageList from '@renderer/lang/languages.json'
import { base as eventBaseName } from '../event/names'
import * as hotKeys from '../../common/hotKey'
import { mainWindow as eventsNameMainWindow, winLyric as eventsNameWinLyric } from '../../main/events/_name'
-import { gzip, gunzip } from 'zlib'
import music from '../utils/music'
let hotKeyTargetInput
@@ -815,7 +815,7 @@ export default {
async importSetting(path) {
let settingData
try {
- settingData = JSON.parse(await this.handleReadFile(path))
+ settingData = JSON.parse(await readLxConfigFile(path))
} catch (error) {
return
}
@@ -830,12 +830,12 @@ export default {
type: 'setting',
data: Object.assign({ version: this.settingVersion }, this.setting),
}
- this.handleSaveFile(path, JSON.stringify(data))
+ saveLxConfigFile(path, JSON.stringify(data))
},
async importPlayList(path) {
let listData
try {
- listData = JSON.parse(await this.handleReadFile(path))
+ listData = JSON.parse(await readLxConfigFile(path))
} catch (error) {
return
}
@@ -867,12 +867,12 @@ export default {
if (item.otherSource) delete item.otherSource
}
}
- this.handleSaveFile(path, JSON.stringify(data))
+ saveLxConfigFile(path, JSON.stringify(data))
},
async importAllData(path) {
let allData
try {
- allData = JSON.parse(await this.handleReadFile(path))
+ allData = JSON.parse(await readLxConfigFile(path))
} catch (error) {
return
}
@@ -906,7 +906,7 @@ export default {
if (item.otherSource) delete item.otherSource
}
}
- this.handleSaveFile(path, JSON.stringify(allData))
+ saveLxConfigFile(path, JSON.stringify(allData))
},
handleImportAllData() {
selectDir({
@@ -1198,35 +1198,6 @@ export default {
handleTrayShowChange(isShow) {
this.current_setting.tray.isToTray = isShow
},
- async handleSaveFile(path, data) {
- if (!path.endsWith('.lxmc')) path += '.lxmc'
- fs.writeFile(path, await this.gzip(data), 'binary', err => {
- console.log(err)
- })
- },
- async handleReadFile(path) {
- let isJSON = path.endsWith('.json')
- let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
- if (!data || isJSON) return data
- data = await this.gunzip(Buffer.from(data, 'binary'))
- return data.toString('utf8')
- },
- gzip(str) {
- return new Promise((resolve, reject) => {
- gzip(str, (err, result) => {
- if (err) return reject(err)
- resolve(result)
- })
- })
- },
- gunzip(buf) {
- return new Promise((resolve, reject) => {
- gunzip(buf, (err, result) => {
- if (err) return reject(err)
- resolve(result.toString())
- })
- })
- },
getApiStatus() {
let status
if (window.globalObj.userApi.status) status = this.$t('view.setting.basic_source_status_success')