新增根据歌曲名、歌手名等字段对列表自动排序的功能

This commit is contained in:
lyswhut
2021-12-06 15:33:36 +08:00
parent a96eea5e44
commit ec3a0e08c3
28 changed files with 464 additions and 166 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div id="my-list" :class="$style.container" @click="handleContainerClick" v-if="isInitedList">
<MyLists :list-id="listId" @show-menu="$refs.musicList.hideMenu()" ref="lists" />
<MyLists :list-id="listId" @show-menu="$refs.musicList.handleMenuClick()" ref="lists" />
<MusicList :list-id="listId" @show-menu="$refs.lists.hideListsMenu()" ref="musicList" />
</div>
@@ -1,5 +1,5 @@
<template>
<material-modal :show="visible" @close="$emit('update:visible', false)" bg-close="bg-close">
<material-modal :show="visible" @close="$emit('update:visible', false)" bg-close teleport="#view">
<div :class="$style.header">
<h2>{{listInfo.name}}</h2>
</div>
@@ -121,6 +121,9 @@ export default {
flex: none;
padding: 15px;
text-align: center;
h2 {
word-break: break-all;
}
}
.main {
min-height: 200px;
@@ -1,68 +1,166 @@
<template>
<material-modal :show="show" @close="handleClose" @after-enter="$refs.input.focus()">
<material-modal :show="visible" @close="closeModal" teleport="#view" bg-close @after-leave="handleAfterLeave">
<main :class="$style.main">
<h2>{{selectedNum > 0 ? $t('music_sort__title_multiple', { num: selectedNum }) : $t('music_sort__title', { name: musicInfo ? musicInfo.name : '' })}}</h2>
<base-input :class="$style.input"
type="number"
v-model="sortNum"
ref="input"
@submit="handleSubmit"
@blur="verify" :placeholder="$t('music_sort__input_tip')" />
<div :class="$style.header">
<h2>{{listInfo.name}}</h2>
</div>
<section>
<h3 :class="$style.title">{{$t('list_sort_modal_by_field')}}</h3>
<ul :class="$style.list">
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_field_name" name="list_sort_modal_field" need="need"
v-model="sortField" value="name" :label="$t('list_sort_modal_by_name')" />
</li>
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_field_singer" name="list_sort_modal_field" need="need"
v-model="sortField" value="singer" :label="$t('list_sort_modal_by_singer')" />
</li>
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_field_album" name="list_sort_modal_field" need="need"
v-model="sortField" value="album" :label="$t('list_sort_modal_by_album')" />
</li>
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_field_time" name="list_sort_modal_field" need="need"
v-model="sortField" value="time" :label="$t('list_sort_modal_by_time')" />
</li>
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_field_source" name="list_sort_modal_field" need="need"
v-model="sortField" value="source" :label="$t('list_sort_modal_by_source')" />
</li>
</ul>
</section>
<section>
<h3 :class="$style.title">{{$t('list_sort_modal_by_type')}}</h3>
<ul :class="$style.list">
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_type_down" name="list_sort_modal_type" need="need"
v-model="sortType" value="down" :label="$t('list_sort_modal_by_down')" />
</li>
<li :class="$style.listItem">
<base-checkbox id="list_sort_modal_type_up" name="list_sort_modal_type" need="need"
v-model="sortType" value="up" :label="$t('list_sort_modal_by_up')" />
</li>
</ul>
</section>
<div :class="$style.footer">
<base-btn :class="$style.btn" @click="handleSubmit">{{$t('music_sort__btn_confirm')}}</base-btn>
<base-btn :class="$style.btn" @click="closeModal">{{$t('music_sort__btn_cancel')}}</base-btn>
<base-btn :class="$style.btn" @click="handleSort">{{$t('music_sort__btn_confirm')}}</base-btn>
</div>
</main>
</material-modal>
</template>
<script>
import { ref, useCommit } from '@renderer/utils/vueTools'
// import { dialog } from '@renderer/plugins/Dialog'
import { getList } from '@renderer/core/share/utils'
const getIntv = (musicInfo) => {
if (!musicInfo.interval) return 0
if (musicInfo._interval) return musicInfo._interval
let intvArr = musicInfo.interval.split(':')
let intv = 0
let unit = 1
while (intvArr.length) {
intv += (intvArr.pop()) * unit
unit *= 60
}
return parseInt(intv)
}
export default {
props: {
show: {
visible: {
type: Boolean,
default: false,
},
musicInfo: {
listInfo: {
type: Object,
default() {
return {}
},
},
selectedNum: {
type: Number,
default: 0,
required: true,
},
},
emits: ['update:show', 'confirm'],
data() {
return {
sortNum: '',
emits: ['update:visible'],
setup(props, { emit }) {
// const { t } = useI18n()
const sortField = ref(null)
const sortType = ref(null)
const setList = useCommit('list', 'setList')
const closeModal = () => {
emit('update:visible', false)
}
},
watch: {
show(n) {
if (n) {
this.sortNum = ''
const handleAfterLeave = () => {
sortField.value = null
sortType.value = null
}
const verify = () => {
return !!sortField.value && !!sortType.value
}
const handleSort = async() => {
if (!verify()) return
// if (!await dialog.confirm({
// message: t('list_sort_modal_tip_confirm'),
// cancelButtonText: t('cancel_button_text'),
// confirmButtonText: t('confirm_button_text'),
// })) return
const list = [...getList(props.listInfo.id)]
let fieldName
switch (sortField.value) {
case 'name':
fieldName = 'name'
break
case 'singer':
fieldName = 'singer'
break
case 'album':
fieldName = 'albumName'
break
case 'time':
fieldName = 'interval'
break
case 'source':
fieldName = 'source'
break
}
},
},
methods: {
handleClose() {
this.$emit('update:show', false)
},
verify() {
let num = /^[1-9]\d*/.exec(this.sortNum)
num = num ? parseInt(num[0]) : ''
this.sortNum = num.toString()
return num
},
handleSubmit() {
console.log('object')
let num = this.verify()
if (this.sortNum == '') return
this.handleClose()
this.$emit('confirm', num)
},
console.log(sortType.value, sortField.value)
if (sortType.value == 'down') {
if (fieldName == 'interval') {
list.sort((a, b) => {
if (a.interval == null) {
return b.interval == null ? 0 : 1
} else return b.interval == null ? -1 : getIntv(b) - getIntv(a)
})
} else {
list.sort((a, b) => {
if (a[fieldName] == null) {
return b[fieldName] == null ? 0 : -1
} else return b[fieldName] == null ? 1 : a[fieldName].localeCompare(b[fieldName])
})
}
} else {
if (fieldName == 'interval') {
list.sort((a, b) => {
if (a.interval == null) {
return b.interval == null ? 0 : -1
} else return b.interval == null ? 1 : getIntv(a) - getIntv(b)
})
} else {
list.sort((a, b) => {
if (a[fieldName] == null) {
return b[fieldName] == null ? 0 : 1
} else return b[fieldName] == null ? -1 : b[fieldName].localeCompare(a[fieldName])
})
}
}
closeModal()
setList({ ...props.listInfo, list })
}
return {
sortField,
sortType,
closeModal,
handleSort,
handleAfterLeave,
}
},
}
</script>
@@ -71,29 +169,40 @@ export default {
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.header {
flex: none;
padding: 15px;
text-align: center;
h2 {
color: @color-theme_2-font;
word-break: break-all;
}
}
.main {
padding: 0 15px;
max-width: 530px;
min-width: 280px;
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;
word-break: break-all;
// text-align: center;
padding: 15px 0 8px;
}
}
.input {
// width: 100%;
// height: 26px;
padding: 8px 8px;
.title {
font-size: 14px;
color: @color-theme_2-font-label;
padding: 10px 0 8px;
}
.list {
display: flex;
flex-flow: row wrap;
font-size: 14px;
}
.listItem {
width: (100% / 3);
padding-left: 10px;
margin-bottom: 8px;
box-sizing: border-box;
}
.footer {
margin: 20px 0 15px auto;
@@ -115,11 +224,14 @@ export default {
each(@themes, {
:global(#root.@{value}) {
.main {
.header {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.title {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
})
@@ -32,13 +32,12 @@ div(:class="$style.list")
div(:class="$style.noItem" v-show="!list.length")
p(v-text="$t('no_item')")
//- material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
teleport(to="#view")
common-download-modal(v-model:show="isShowDownload" :musicInfo="selectedDownloadMusicInfo")
common-download-multiple-modal(v-model:show="isShowDownloadMultiple" :list="selectedList" @confirm="removeAllSelect")
common-list-add-modal(v-model:show="isShowListAdd" :is-move="isMove" :musicInfo="selectedAddMusicInfo" :exclude-list-id="excludeListIds")
common-list-add-multiple-modal(v-model:show="isShowListAddMultiple" :is-move="isMoveMultiple" :musicList="selectedList" @confirm="removeAllSelect" :exclude-list-id="excludeListIds")
search-list(:list="list" @action="handleMusicSearchAction" :visible="isShowSearchBar")
list-sort-modal(v-model:show="isShowListSortModal" :music-info="selectedSortMusicInfo" :selected-num="selectedNum" @confirm="sortMusic")
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" :is-move="isMove" :musicInfo="selectedAddMusicInfo" :exclude-list-id="excludeListIds" teleport="#view")
common-list-add-multiple-modal(v-model:show="isShowListAddMultiple" :is-move="isMoveMultiple" :musicList="selectedList" @confirm="removeAllSelect" :exclude-list-id="excludeListIds" teleport="#view")
search-list(:list="list" @action="handleMusicSearchAction" :visible="isShowSearchBar")
music-sort-modal(v-model:show="isShowMusicSortModal" :music-info="selectedSortMusicInfo" :selected-num="selectedNum" @confirm="sortMusic")
base-menu(:menus="menus" :location="menuLocation" item-name="name" :isShow="isShowItemMenu" @menu-click="handleMenuClick")
</template>
@@ -46,7 +45,7 @@ div(:class="$style.list")
import { clipboardWriteText, assertApiSupport } from '@renderer/utils'
import { useCssModule } from '@renderer/utils/vueTools'
import SearchList from '../SearchList'
import ListSortModal from '../ListSortModal'
import MusicSortModal from '../MusicSortModal'
import useListInfo from './useListInfo'
import useList from './useList'
import useMenu from './useMenu'
@@ -68,7 +67,7 @@ export default {
emits: ['show-menu'],
components: {
SearchList,
ListSortModal,
MusicSortModal,
},
setup(props, { emit }) {
const styles = useCssModule()
@@ -117,7 +116,7 @@ export default {
} = useMusicDownload({ selectedList, list })
const {
isShowListSortModal,
isShowMusicSortModal,
selectedNum,
selectedSortMusicInfo,
handleShowSortModal,
@@ -136,7 +135,6 @@ export default {
menuLocation,
isShowItemMenu,
showMenu,
hideMenu,
menuClick,
} = useMenu({
listRef,
@@ -231,7 +229,6 @@ export default {
isShowItemMenu,
menuLocation,
handleMenuClick,
hideMenu,
handleListRightClick,
assertApiSupport,
@@ -242,7 +239,7 @@ export default {
isMoveMultiple,
selectedAddMusicInfo,
isShowListSortModal,
isShowMusicSortModal,
selectedNum,
selectedSortMusicInfo,
sortMusic,
@@ -158,6 +158,5 @@ export default ({
isShowItemMenu,
showMenu,
menuClick,
hideMenu,
}
}
@@ -1,7 +1,7 @@
import { ref, nextTick, useCommit } from '@renderer/utils/vueTools'
export default ({ props, list, selectedList, removeAllSelect }) => {
const isShowListSortModal = ref(false)
const isShowMusicSortModal = ref(false)
const selectedNum = ref(0)
const musicInfo = ref(null)
@@ -14,7 +14,7 @@ export default ({ props, list, selectedList, removeAllSelect }) => {
selectedNum.value = 0
musicInfo.value = list.value[index]
nextTick(() => {
isShowListSortModal.value = true
isShowMusicSortModal.value = true
})
}
}
@@ -27,11 +27,11 @@ export default ({ props, list, selectedList, removeAllSelect }) => {
list: selectedNum.value ? [...selectedList.value] : [musicInfo.value],
})
removeAllSelect()
isShowListSortModal.value = false
isShowMusicSortModal.value = false
}
return {
isShowListSortModal,
isShowMusicSortModal,
selectedNum,
selectedSortMusicInfo: musicInfo,
handleShowSortModal,
@@ -0,0 +1,125 @@
<template>
<material-modal :show="show" @close="handleClose" @after-enter="$refs.input.focus()" teleport="#view">
<main :class="$style.main">
<h2>{{selectedNum > 0 ? $t('music_sort__title_multiple', { num: selectedNum }) : $t('music_sort__title', { name: musicInfo ? musicInfo.name : '' })}}</h2>
<base-input :class="$style.input"
type="number"
v-model="sortNum"
ref="input"
@submit="handleSubmit"
@blur="verify" :placeholder="$t('music_sort__input_tip')" />
<div :class="$style.footer">
<base-btn :class="$style.btn" @click="handleSubmit">{{$t('music_sort__btn_confirm')}}</base-btn>
</div>
</main>
</material-modal>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
musicInfo: {
type: Object,
default() {
return {}
},
},
selectedNum: {
type: Number,
default: 0,
},
},
emits: ['update:show', 'confirm'],
data() {
return {
sortNum: '',
}
},
watch: {
show(n) {
if (n) {
this.sortNum = ''
}
},
},
methods: {
handleClose() {
this.$emit('update:show', false)
},
verify() {
let num = /^[1-9]\d*/.exec(this.sortNum)
num = num ? parseInt(num[0]) : ''
this.sortNum = num.toString()
return num
},
handleSubmit() {
let num = this.verify()
if (this.sortNum == '') return
this.handleClose()
this.$emit('confirm', num)
},
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.main {
padding: 0 15px;
max-width: 530px;
min-width: 280px;
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;
word-break: break-all;
// text-align: center;
padding: 15px 0 8px;
}
}
.input {
// width: 100%;
// height: 26px;
padding: 8px 8px;
}
.footer {
margin: 20px 0 15px auto;
}
.btn {
// box-sizing: border-box;
// margin-left: 15px;
// margin-bottom: 15px;
// height: 36px;
// line-height: 36px;
// padding: 0 10px !important;
min-width: 70px;
// .mixin-ellipsis-1;
+.btn {
margin-left: 10px;
}
}
each(@themes, {
:global(#root.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
}
})
</style>
+19 -5
View File
@@ -34,7 +34,8 @@
</transition>
</ul>
<base-menu :menus="listsItemMenu" :location="listsData.menuLocation" item-name="name" :isShow="listsData.isShowItemMenu" @menu-click="handleListsItemMenuClick" />
<DuplicateMusicModal v-model:visible="isShowDuplicateMusicModal" :list-info="selectedListInfo" />
<DuplicateMusicModal v-model:visible="isShowDuplicateMusicModal" :list-info="selectedDuplicateListInfo" />
<ListSortModal v-model:visible="isShowListSortModal" :list-info="selectedSortListInfo" />
</div>
</template>
@@ -43,6 +44,7 @@ import { mapMutations, mapActions } from 'vuex'
import { openSaveDir, saveLxConfigFile, selectDir, readLxConfigFile, filterFileName } from '@renderer/utils'
import musicSdk from '@renderer/utils/music'
import DuplicateMusicModal from './DuplicateMusicModal'
import ListSortModal from './ListSortModal'
import { defaultList, loveList, userLists } from '@renderer/core/share/list'
import { computed } from '@renderer/utils/vueTools'
import { getList } from '@renderer/core/share/utils'
@@ -57,6 +59,7 @@ export default {
},
components: {
DuplicateMusicModal,
ListSortModal,
},
setup() {
const lists = computed(() => [defaultList, loveList, ...userLists])
@@ -70,13 +73,14 @@ export default {
emits: ['show-menu'],
data() {
return {
isShowListSortModal: false,
isShowDuplicateMusicModal: false,
isShowListSortModal: false,
listsData: {
isShowItemMenu: false,
itemMenuControl: {
rename: true,
duplicate: true,
sort: true,
import: true,
export: true,
sync: false,
@@ -93,7 +97,8 @@ export default {
isNewLeave: false,
},
fetchingListStatus: {},
selectedListInfo: {},
selectedDuplicateListInfo: {},
selectedSortListInfo: {},
}
},
computed: {
@@ -114,6 +119,11 @@ export default {
action: 'duplicate',
disabled: !this.listsData.itemMenuControl.duplicate,
},
{
name: this.$t('lists__sort_list'),
action: 'sort',
disabled: !this.listsData.itemMenuControl.sort,
},
{
name: this.$t('lists__import'),
action: 'import',
@@ -145,7 +155,6 @@ export default {
watch: {
listId(id) {
this.setPrevSelectListId(id)
this.isShowListSortModal = false
},
lists(lists) {
if (lists.some(l => l.id == this.listId)) return
@@ -245,6 +254,7 @@ export default {
this.listsData.itemMenuControl.movedown = index < userLists.length - 1
break
}
this.listsData.itemMenuControl.sort = !!getList(this.getTargetListInfo(index)?.id).length
this.listsData.rightClickItemIndex = index
this.listsData.menuLocation.x = event.currentTarget.offsetLeft + event.offsetX
this.listsData.menuLocation.y = event.currentTarget.offsetTop + event.offsetY - this.$refs.dom_lists_list.scrollTop
@@ -272,9 +282,13 @@ export default {
})
break
case 'duplicate':
this.selectedListInfo = this.getTargetListInfo(index)
this.selectedDuplicateListInfo = this.getTargetListInfo(index)
this.isShowDuplicateMusicModal = true
break
case 'sort':
this.selectedSortListInfo = this.getTargetListInfo(index)
this.isShowListSortModal = true
break
case 'import':
this.handleImportList(index)
break
@@ -1,27 +1,28 @@
<template lang="pug">
div(:class="$style.container" ref="dom_container" v-show="isShow")
transition(enter-active-class="animated-fast zoomIn" leave-active-class="animated zoomOut" @after-leave="handleAnimated")
div(:class="$style.search" v-show="visible")
div(:class="$style.form")
input.key-bind.ignore-esc(:placeholder="placeholder" v-model.trim="text" ref="dom_input"
@input="handleDelaySearch"
@keyup.enter="handleTemplistClick(selectIndex)"
@keyup.arrow-down.prevent="handleKeyDown"
@keyup.arrow-up.prevent="handleKeyUp"
@keyup.escape.prevent="handleKeyEsc"
@contextmenu="handleContextMenu")
button(type="button" @click="handleHide")
slot
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')
div.scroll(v-if="resultList" :class="$style.list" :style="listStyle" ref="dom_scrollContainer")
ul(ref="dom_list")
li(v-for="(item, index) in resultList" :key="item.songmid" :class="selectIndex === index ? $style.select : null" @mouseenter="selectIndex = index" @click="handleTemplistClick(index)")
div(:class="$style.img")
div(:class="$style.text")
h3(:class="$style.text") {{item.name}} - {{item.singer}}
h3(v-if="item.albumName" :class="[$style.text, $style.albumName]") {{item.albumName}}
div(:class="$style.source") {{item.source}}
teleport(to="#view")
div(:class="$style.container" ref="dom_container" v-show="isShow")
transition(enter-active-class="animated-fast zoomIn" leave-active-class="animated zoomOut" @after-leave="handleAnimated")
div(:class="$style.search" v-show="visible")
div(:class="$style.form")
input.key-bind.ignore-esc(:placeholder="placeholder" v-model.trim="text" ref="dom_input"
@input="handleDelaySearch"
@keyup.enter="handleTemplistClick(selectIndex)"
@keyup.arrow-down.prevent="handleKeyDown"
@keyup.arrow-up.prevent="handleKeyUp"
@keyup.escape.prevent="handleKeyEsc"
@contextmenu="handleContextMenu")
button(type="button" @click="handleHide")
slot
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')
div.scroll(v-if="resultList" :class="$style.list" :style="listStyle" ref="dom_scrollContainer")
ul(ref="dom_list")
li(v-for="(item, index) in resultList" :key="item.songmid" :class="selectIndex === index ? $style.select : null" @mouseenter="selectIndex = index" @click="handleTemplistClick(index)")
div(:class="$style.img")
div(:class="$style.text")
h3(:class="$style.text") {{item.name}} - {{item.singer}}
h3(v-if="item.albumName" :class="[$style.text, $style.albumName]") {{item.albumName}}
div(:class="$style.source") {{item.source}}
</template>
<script>
@@ -107,6 +108,7 @@ export default {
default: false,
},
},
emits: ['action'],
data() {
return {
text: '',