我的列表右键菜单新增列表排序功能

pull/392/head
lyswhut 2020-12-19 15:16:22 +08:00
parent ba9fd8d1f1
commit bc69a98df1
16 changed files with 639 additions and 16 deletions

View File

@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
"version": "1.5.0",
"version": "1.6.0",
"description": "一个免费的音乐下载助手",
"main": "./dist/electron/main.js",
"productName": "lx-music-desktop",

View File

@ -1,14 +1,4 @@
### 新增
- 直接从歌单详情收藏的列表新增同步功能。注意:这将会覆盖本地的目标列表,歌曲将被替换成最新的在线列表
- 我的列表右键菜单新增列表排序功能,可调整单曲、多选后的歌曲的顺序
### 优化
- 优化软件启动时恢复上一次播放的歌曲进度功能
### 修复
- 修复MAC平台上下载歌曲封面嵌入无法显示的问题
- 修复MAC平台首次运行软件最小化、关闭控制按钮默认在右边的问题
- 修复酷狗源的某些歌曲没有专辑字段导致的列表加载失败问题
- 修复某些酷狗源歌单链接无法打开的问题

View File

@ -1,6 +1,6 @@
<template lang="pug">
input(:class="$style.input" :type="type" :placeholder="placeholder" :value="value" :disabled="disabled"
@focus="$emit('focus', $event)" @blur="$emit('blur', $event)" @input="$emit('input', $event.target.value.trim())" @change="$emit('change', $event.target.value.trim())"
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>
@ -24,6 +24,16 @@ export default {
default: 'text',
},
},
methods: {
handleInput(event) {
let value = event.target.value.trim()
event.target.value = value
this.$emit('input', value)
},
focus() {
this.$refs.dom_input.focus()
},
},
}
</script>
@ -41,6 +51,13 @@ export default {
transition: background-color 0.2s ease;
background-color: @color-btn-background;
font-size: 13.3px;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
&[disabled] {
opacity: .4;
}

View File

@ -0,0 +1,201 @@
<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" @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>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
},
bgClose: {
type: Boolean,
default: true,
},
excludeListId: {
type: Array,
default() {
return []
},
},
listName: {
type: String,
default: '',
},
fromListId: {
type: String,
default: null,
},
isMove: {
type: Boolean,
default: false,
},
},
data() {
return {
isEditing: false,
newListName: '',
}
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => l.id != this.excludeListId.includes(l.id))
},
spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
},
},
methods: {
...mapMutations('list', ['listAdd', 'listMove', 'createUserList']),
handleClick(index) {
this.isMove
? this.listMove({ fromId: this.fromListId, toId: this.lists[index].id, musicInfo: this.musicInfo })
: this.listAdd({ id: this.lists[index].id, musicInfo: this.musicInfo })
this.$nextTick(() => {
this.handleClose()
})
},
handleClose() {
this.$emit('close')
},
handleEditing(event) {
if (this.isEditing) return
if (!this.newListName) this.newListName = this.listName
this.isEditing = true
this.$nextTick(() => event.currentTarget.querySelector('.' + this.$style.newListInput).focus())
},
handleSaveList(event) {
let name = event.target.value
this.newListName = event.target.value = ''
this.isEditing = false
if (!name) return
this.createUserList({ name })
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
// padding: 15px 0;
max-width: 530px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
min-height: 0;
// max-height: 100%;
// overflow: hidden;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
padding: 15px;
}
}
.name {
color: @color-theme;
}
.btn-content {
flex: auto;
max-height: 100%;
padding-right: 15px;
display: flex;
flex-flow: row wrap;
justify-content: space-evenly;
}
.btn {
box-sizing: border-box;
margin-left: 15px;
margin-bottom: 15px;
height: 36px;
line-height: 36px;
padding: 0 10px !important;
width: 150px;
.mixin-ellipsis-1;
}
.newList {
border: 1px dashed @color-theme-hover;
background-color: @color-theme_2-background_2;
color: @color-theme-hover;
opacity: .7;
svg {
height: 18px;
margin-top: 9px;
}
&.editing {
opacity: 1;
svg {
display: none;
}
.newListInput {
display: block;
}
}
}
.newListInput {
width: 100%;
height: 34px;
border: none;
padding: 0;
line-height: 34px;
background: none;
outline: none;
font-size: 14px;
text-align: center;
font-family: inherit;
display: none;
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.name {
color: ~'@{color-@{value}-theme}';
}
.newList {
border-color: ~'@{color-@{value}-theme-hover}';
color: ~'@{color-@{value}-theme-hover}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
}
}
})
</style>

View File

@ -0,0 +1,195 @@
<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>
<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicList: {
type: Array,
default() {
return []
},
},
bgClose: {
type: Boolean,
default: true,
},
excludeListId: {
type: Array,
default() {
return []
},
},
listName: {
type: String,
default: '',
},
fromListId: {
type: String,
default: null,
},
isMove: {
type: Boolean,
default: false,
},
},
data() {
return {
isEditing: false,
newListName: '',
}
},
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => l.id != this.excludeListId.includes(l.id))
},
spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
},
},
methods: {
...mapMutations('list', ['listAddMultiple', 'listMoveMultiple', 'createUserList']),
handleClick(index) {
this.isMove
? 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)
})
},
handleClose(isSelect = false) {
this.$emit('close', isSelect)
},
handleEditing(event) {
if (this.isEditing) return
if (!this.newListName) this.newListName = this.listName
this.isEditing = true
this.$nextTick(() => event.currentTarget.querySelector('.' + this.$style.newListInput).focus())
},
handleSaveList(event) {
let name = event.target.value
this.newListName = event.target.value = ''
this.isEditing = false
if (!name) return
this.createUserList({ name })
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
// padding: 15px 0;
max-width: 530px;
min-width: 200px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
min-height: 0;
// max-height: 100%;
// overflow: hidden;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
padding: 15px;
}
}
.btn-content {
flex: auto;
max-height: 100%;
padding-right: 15px;
display: flex;
flex-flow: row wrap;
justify-content: space-evenly;
}
.btn {
box-sizing: border-box;
margin-left: 15px;
margin-bottom: 15px;
height: 36px;
line-height: 36px;
padding: 0 10px !important;
width: 150px;
.mixin-ellipsis-1;
}
.newList {
border: 1px dashed @color-theme-hover;
background-color: @color-theme_2-background_2;
color: @color-theme-hover;
opacity: .7;
svg {
height: 18px;
margin-top: 9px;
}
&.editing {
opacity: 1;
svg {
display: none;
}
.newListInput {
display: block;
}
}
}
.newListInput {
width: 100%;
height: 34px;
border: none;
padding: 0;
line-height: 34px;
background: none;
outline: none;
font-size: 14px;
text-align: center;
font-family: inherit;
display: none;
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.newList {
border-color: ~'@{color-@{value}-theme-hover}';
color: ~'@{color-@{value}-theme-hover}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
}
}
})
</style>

View File

@ -0,0 +1,109 @@
<template lang="pug">
material-modal(:show="show" @close="handleClose")
main(:class="$style.main")
h2 {{selectedNum > 0 ? $t('material.list_sort_modal.title_multiple', { num: selectedNum }) : $t('material.list_sort_modal.title', { name: musicInfo ? musicInfo.name : '' })}}
material-input(:class="$style.input" type="number" v-model="sortNum" ref="input" :placeholder="$t('material.list_sort_modal.input_tip')" @keydown.native.enter="handleSubmit")
material-btn(:class="$style.btn" @click="handleSubmit") {{$t('material.list_sort_modal.btn_confirm')}}
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
default() {
return {}
},
},
selectedNum: {
type: Number,
default: 0,
},
},
data() {
return {
sortNum: '',
}
},
watch: {
show(n) {
if (n) {
this.sortNum = ''
this.$nextTick(() => {
this.$refs.input.focus()
})
}
},
},
computed: {
},
methods: {
handleClose() {
this.$emit('close')
},
handleSubmit() {
let num = /^[1-9]\d*/.exec(this.sortNum)
num = num ? parseInt(num[0]) : ''
this.sortNum = num.toString()
if (this.sortNum == '') return
this.$emit('confirm', num)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 0 15px;
max-width: 530px;
min-width: 320px;
display: flex;
flex-flow: column nowrap;
min-height: 0;
// max-height: 100%;
// overflow: hidden;
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
// text-align: center;
padding: 15px 0 8px;
}
}
.input {
// width: 100%;
// height: 26px;
padding: 8px 8px;
}
.btn {
margin: 20px auto 15px;
// box-sizing: border-box;
// margin-left: 15px;
// margin-bottom: 15px;
// height: 36px;
// line-height: 36px;
// padding: 0 10px !important;
min-width: 100px;
// .mixin-ellipsis-1;
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>

View File

@ -138,10 +138,10 @@ export default {
// will-change: transform;
li {
cursor: pointer;
min-width: 80px;
min-width: 90px;
line-height: 34px;
// color: @color-btn;
padding: 0 5px;
padding: 0 10px;
text-align: center;
outline: none;
transition: @transition-theme;

View File

@ -0,0 +1,50 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main.ignore-to-rem(:class="$style.main")
h2 {{$t('material.xm_verify_modal.title')}}
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
bgClose: {
type: Boolean,
default: true,
},
},
methods: {
handleClose() {
this.$emit('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
background: #fff !important;
&:global(.ignore-to-rem) {
padding: 15px;
width: 360px;
height: 330px;
h2 {
font-size: 16px;
}
}
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,6 @@
{
"title": "Adjust the position of {name} to:",
"title_multiple": "Adjust the position of the selected {num} songs to:",
"input_tip": "Please input which position you want to adjust to",
"btn_confirm": "Confirm"
}

View File

@ -10,6 +10,7 @@
"list_copy_name": "Copy name",
"list_add_to": "Add to ...",
"list_move_to": "Move to ...",
"list_sort": "Adjust position",
"list_download": "Download",
"list_remove": "Remove",
"list_source_detail": "Song Page",

View File

@ -0,0 +1,6 @@
{
"title": "将 {name} 的位置调整到:",
"title_multiple": "将已选的 {num} 首歌曲的位置调整到:",
"input_tip": "请输入要调整到第几个位置",
"btn_confirm": "确定"
}

View File

@ -11,6 +11,7 @@
"list_source_detail": "歌曲详情页",
"list_add_to": "添加到...",
"list_move_to": "移动到...",
"list_sort": "调整位置",
"list_download": "下载",
"list_remove": "删除",
"default_list": "试听列表",

View File

@ -0,0 +1,6 @@
{
"title": "將 {name} 的位置調整到:",
"title_multiple": "將已選的 {num} 首歌曲的位置調整到:",
"input_tip": "請輸入要調整到第幾個位置",
"btn_confirm": "確定"
}

View File

@ -10,6 +10,7 @@
"list_copy_name": "複製歌曲名",
"list_add_to": "添加到...",
"list_move_to": "移動到...",
"list_sort": "調整位置",
"list_download": "下載",
"list_remove": "刪除",
"list_source_detail": "歌曲詳情頁",

View File

@ -185,6 +185,12 @@ const mutations = {
setListScroll(state, { id, location }) {
if (allList[id]) allList[id].location = location
},
sortList(state, { id, sortNum, musicInfos }) {
let targetList = allList[id]
this.commit('list/listRemoveMultiple', { id, list: musicInfos })
targetList.list.splice(sortNum - 1, 0, ...musicInfos)
},
}
export default {

View File

@ -69,6 +69,7 @@
material-menu(:menus="listsItemMenu" :location="listsData.menuLocation" item-name="name" :isShow="listsData.isShowItemMenu" @menu-click="handleListsItemMenuClick")
material-menu(:menus="listItemMenu" :location="listMenu.menuLocation" item-name="name" :isShow="listMenu.isShowItemMenu" @menu-click="handleListItemMenuClick")
material-search-list(:list="list" @action="handleMusicSearchAction" :visible="isVisibleMusicSearch")
material-list-sort-modal(:show="isShowListSortModal" :music-info="musicInfo" :selected-num="selectdListDetailData.length" @close="isShowListSortModal = false" @confirm="handleSortMusicInfo")
</template>
<script>
@ -91,6 +92,7 @@ export default {
delayShow: false,
isShowListAdd: false,
isShowListAddMultiple: false,
isShowListSortModal: false,
delayTimeout: null,
isToggleList: true,
focusTarget: 'listDetail',
@ -123,6 +125,7 @@ export default {
copyName: true,
addTo: true,
moveTo: true,
sort: true,
download: true,
remove: true,
sourceDetail: true,
@ -229,6 +232,11 @@ export default {
action: 'sourceDetail',
disabled: !this.listMenu.itemMenuControl.sourceDetail,
},
{
name: this.$t('view.list.list_sort'),
action: 'sort',
disabled: !this.listMenu.itemMenuControl.sort,
},
{
name: this.$t('view.list.list_add_to'),
action: 'addTo',
@ -329,6 +337,7 @@ export default {
'removeUserList',
'setListScroll',
'setList',
'sortList',
]),
...mapActions('songList', ['getListDetailAll']),
...mapActions('download', ['createDownload', 'createDownloadMultiple']),
@ -809,6 +818,20 @@ export default {
})
}
break
case 'sort':
this.isShowListSortModal = true
this.musicInfo = this.list[index]
// if (this.selectdListDetailData.length) {
// this.isShowDownloadMultiple = true
// } else {
// minfo = this.list[index]
// if (!this.assertApiSupport(minfo.source)) return
// this.musicInfo = minfo
// this.$nextTick(() => {
// this.isShowDownload = true
// })
// }
break
case 'remove':
if (this.selectdListDetailData.length) {
this.listRemoveMultiple({ id: this.listId, list: this.selectdListDetailData })
@ -854,11 +877,22 @@ export default {
const targetListInfo = this.userList[index]
const list = await this.fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
// console.log(targetListInfo.list.length, list.length)
this.removeAllSelectListDetail()
this.setList({
...targetListInfo,
list,
})
},
handleSortMusicInfo(num) {
num = Math.min(num, this.list.length)
this.sortList({
id: this.listId,
sortNum: num,
musicInfos: this.selectdListDetailData.length ? [...this.selectdListDetailData] : [this.musicInfo],
})
this.removeAllSelectListDetail()
this.isShowListSortModal = false
},
},
}
</script>