From 35ca6db1ee7737e72ec881263dbfc46e8b838d86 Mon Sep 17 00:00:00 2001 From: lyswhut Date: Tue, 26 Oct 2021 14:44:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=88=91=E7=9A=84=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E3=80=81=E4=B8=8B=E8=BD=BD=E3=80=81=E6=AD=8C=E5=8D=95?= =?UTF-8?q?=E3=80=81=E6=8E=92=E8=A1=8C=E6=A6=9C=E5=88=97=E8=A1=A8=E6=80=A7?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- publish/changeLog.md | 2 + src/common/defaultSetting.js | 1 - src/common/utils.js | 4 +- src/renderer/App.vue | 3 + src/renderer/assets/styles/index.less | 57 ++++ src/renderer/components/core/Aside.vue | 2 +- .../components/material/Selection.vue | 2 +- src/renderer/components/material/SongList.vue | 117 ++++--- .../components/material/VirtualizedList.vue | 241 ++++++++++++++ src/renderer/main.js | 4 +- src/renderer/store/modules/leaderboard.js | 46 +-- src/renderer/store/modules/list.js | 8 +- src/renderer/store/mutations.js | 3 - src/renderer/utils/data.js | 45 +++ src/renderer/views/Download.vue | 101 +++--- src/renderer/views/Leaderboard.vue | 40 ++- src/renderer/views/List.vue | 299 ++++++++---------- src/renderer/views/Setting.vue | 6 +- 18 files changed, 646 insertions(+), 335 deletions(-) create mode 100644 src/renderer/components/material/VirtualizedList.vue create mode 100644 src/renderer/utils/data.js diff --git a/publish/changeLog.md b/publish/changeLog.md index 7aa7a5b4..d671042d 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -6,6 +6,8 @@ - 优化列表同步代码逻辑 - 优化开关评论时的动画性能 +- 优化进入、离开播放详情页的性能 +- 大幅优化我的列表、下载、歌单、排行榜列表性能,现在即使同一列表内的歌曲很多时也不会卡顿了 ### 修复 diff --git a/src/common/defaultSetting.js b/src/common/defaultSetting.js index f261cbf7..4e31bd59 100644 --- a/src/common/defaultSetting.js +++ b/src/common/defaultSetting.js @@ -35,7 +35,6 @@ const defaultSetting = { list: { isShowAlbumName: true, isShowSource: true, - prevSelectListId: 'default', isSaveScrollLocation: true, addMusicLocationType: 'top', }, diff --git a/src/common/utils.js b/src/common/utils.js index be18ffed..08e8dc97 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -153,8 +153,8 @@ exports.initSetting = isShowErrorAlert => { // 迁移列表滚动位置设置 ~0.18.3 if (setting.list.scroll) { let scroll = setting.list.scroll - electronStore_list.set('defaultList.location', scroll.locations.default || 0) - electronStore_list.set('loveList.location', scroll.locations.love || 0) + // electronStore_list.set('defaultList.location', scroll.locations.default || 0) + // electronStore_list.set('loveList.location', scroll.locations.love || 0) electronStore_config.delete('setting.list.scroll') electronStore_config.set('setting.list.isSaveScrollLocation', scroll.enable) delete setting.list.scroll diff --git a/src/renderer/App.vue b/src/renderer/App.vue index d1c05a27..64645594 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -29,6 +29,7 @@ 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 @@ -338,6 +339,8 @@ export default { return Promise.all([ this.initMyList(), // 初始化播放列表 this.initSearchHistoryList(), // 初始化搜索历史列表 + initListPosition(), // 列表位置记录 + initListPrevSelectId(), // 上次选中的列表记录 ]) // this.initDownloadList() // 初始化下载列表 }, diff --git a/src/renderer/assets/styles/index.less b/src/renderer/assets/styles/index.less index db861e80..b045c5a3 100644 --- a/src/renderer/assets/styles/index.less +++ b/src/renderer/assets/styles/index.less @@ -1,5 +1,7 @@ @import './reset.less'; @import './animate.less'; +@import './layout.less'; + *, *::after, *::before { -webkit-user-drag: none; } @@ -72,6 +74,45 @@ table { } } +.list { + width: 100%; + overflow: hidden; + color: @color-theme_2-font; + .list-item { + height: 100%; + display: flex; + flex-flow: row nowrap; + align-items: center; + // border-top: 1px solid rgba(0, 0, 0, 0.12); + transition: background-color 0.2s ease; + border-bottom: 1px solid @color-theme_2-line; + box-sizing: border-box; + &:hover { + background-color: @color-theme_2-hover; + } + &.active { + background-color: @color-theme_2-active; + } + &.selected { + background-color: @color-theme_2-hover; + } + .list-item-cell { + flex: none; + padding: 0 6px; + position: relative; + transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + font-size: 13px; + line-height: 16px; + vertical-align: middle; + box-sizing: border-box; + .mixin-ellipsis-1; + + &.auto { + flex: auto; + } + } + } +} .badge { display: inline-block; @@ -240,6 +281,22 @@ each(@themes, { } } + .list { + color: ~'@{color-@{value}-theme_2-font}'; + .list-item { + border-bottom-color: ~'@{color-@{value}-theme_2-line}'; + &:hover { + background-color: ~'@{color-@{value}-theme_2-hover}'; + } + &.active { + background-color: ~'@{color-@{value}-theme_2-active}'; + } + &.selected { + background-color: ~'@{color-@{value}-theme_2-hover}'; + } + } + } + input, textarea { &::placeholder { color: ~'@{color-@{value}-theme_2-font-label}'; diff --git a/src/renderer/components/core/Aside.vue b/src/renderer/components/core/Aside.vue index 4bef41a1..f3210d98 100644 --- a/src/renderer/components/core/Aside.vue +++ b/src/renderer/components/core/Aside.vue @@ -35,7 +35,7 @@ div(:class="$style.aside") dl //- dt {{$t('core.aside.my_music')}} dd - router-link(:active-class="$style.active" :tips="$t('core.aside.my_list')" :to="`list?id=${setting.list.prevSelectListId || defaultList.id}`") + 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') diff --git a/src/renderer/components/material/Selection.vue b/src/renderer/components/material/Selection.vue index 4a56513a..5a82c17b 100644 --- a/src/renderer/components/material/Selection.vue +++ b/src/renderer/components/material/Selection.vue @@ -5,7 +5,7 @@ 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.list.scroll(:class="$style.list" :style="listStyles" ref="dom_list") + 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}} diff --git a/src/renderer/components/material/SongList.vue b/src/renderer/components/material/SongList.vue index 73189531..a65c866d 100644 --- a/src/renderer/components/material/SongList.vue +++ b/src/renderer/components/material/SongList.vue @@ -13,7 +13,31 @@ div(:class="$style.songList") th.nobreak(:style="{ width: rowWidth.r5 }") {{$t('material.song_list.time')}} th.nobreak(:style="{ width: rowWidth.r6 }") {{$t('material.song_list.action')}} div(:class="$style.content") - div.scroll(v-show="list.length" :class="$style.tbody" ref="dom_scrollContent") + div(v-if="list.length" :class="$style.content" ref="dom_listContent") + material-virtualized-list(:list="list" key-name="songmid" ref="list" :item-height="37" + containerClass="scroll" contentClass="list" @contextmenu.native.capture="handleContextMenu") + template(#default="{ item, index }") + div.list-item(@click="handleDoubleClick($event, index)" @contextmenu="handleListItemRigthClick($event, index)" + :class="[{ selected: selectedIndex == index }, { active: selectdList.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('material.song_list.lossless')}` : item._types['320k'] ? ` - ${$t('material.song_list.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('material.song_list.lossless')}} + span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}} + div.list-item-cell(:style="{ width: rowWidth.r3 }") + span.select {{item.singer}} + div.list-item-cell(:style="{ width: rowWidth.r4 }") + 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="handleTogglePage") + //- div.scroll(v-show="list.length" :class="$style.tbody" ref="dom_scrollContent") table tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody") tr(v-for='(item, index) in list' :key='item.songmid' @contextmenu="handleListItemRigthClick($event, index)" @click="handleDoubleClick($event, index)") @@ -175,7 +199,9 @@ export default { isModDown: false, }, lastSelectIndex: 0, + selectedIndex: -1, listMenu: { + rightClickItemIndex: -1, isShowItemMenu: false, itemMenuControl: { play: true, @@ -233,7 +259,7 @@ export default { this.handleSelectAllData() }, handleDoubleClick(event, index) { - if (event.target.classList.contains('select')) return + if (this.listMenu.rightClickItemIndex > -1) return this.handleSelectData(event, index) @@ -264,14 +290,8 @@ export default { } this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1) if (isNeedReverse) this.selectdList.reverse() - let nodes = this.$refs.dom_tbody.childNodes - do { - nodes[lastSelectIndex].classList.add('active') - lastSelectIndex++ - } while (lastSelectIndex <= clickIndex) } } else { - event.currentTarget.classList.add('active') this.selectdList.push(this.list[clickIndex]) this.lastSelectIndex = clickIndex } @@ -281,10 +301,8 @@ export default { let index = this.selectdList.indexOf(item) if (index < 0) { this.selectdList.push(item) - event.currentTarget.classList.add('active') } else { this.selectdList.splice(index, 1) - event.currentTarget.classList.remove('active') } } else if (this.selectdList.length) { this.removeAllSelect() @@ -293,12 +311,6 @@ export default { }, removeAllSelect() { this.selectdList = [] - let dom_tbody = this.$refs.dom_tbody - if (!dom_tbody) return - let nodes = dom_tbody.querySelectorAll('.active') - for (const node of nodes) { - if (node.parentNode == dom_tbody) node.classList.remove('active') - } }, handleListBtnClick(info) { this.emitEvent('listBtnClick', info) @@ -306,10 +318,6 @@ export default { handleSelectAllData() { this.removeAllSelect() this.selectdList = [...this.list] - let nodes = this.$refs.dom_tbody.childNodes - for (const node of nodes) { - node.classList.add('active') - } this.$emit('input', [...this.selectdList]) }, handleTogglePage(page) { @@ -327,12 +335,12 @@ export default { handleContextMenu(event) { if (!event.target.classList.contains('select')) return event.stopImmediatePropagation() - let classList = this.$refs.dom_scrollContent.classList + let classList = this.$refs.dom_listContent.classList classList.add(this.$style.copying) window.requestAnimationFrame(() => { let str = window.getSelection().toString() classList.remove(this.$style.copying) - str = str.trim() + str = str.split(/\n\n/).map(s => s.replace(/\n/g, ' ')).join('\n').trim() if (!str.length) return clipboardWriteText(str) }) @@ -344,23 +352,27 @@ export default { this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl // this.listMenu.itemMenuControl.play = // this.listMenu.itemMenuControl.playLater = - this.listMenu.itemMenuControl.download = - this.assertApiSupport(this.list[index].source) - let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected') - if (dom_selected) dom_selected.classList.remove('selected') - this.$refs.dom_tbody.querySelectorAll('tr')[index].classList.add('selected') - let dom_td = event.target.closest('td') + this.listMenu.itemMenuControl.download = this.assertApiSupport(this.list[index].source) + let dom_container = event.target.closest('.' + this.$style.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) + } this.listMenu.rightClickItemIndex = index - this.listMenu.menuLocation.x = dom_td.offsetLeft + event.offsetX - this.listMenu.menuLocation.y = dom_td.offsetParent.offsetTop + dom_td.offsetTop + event.offsetY - this.$refs.dom_scrollContent.scrollTop + this.selectedIndex = index + let { x, y } = getOffsetValue(event.target) + this.listMenu.menuLocation.x = x + event.offsetX + this.listMenu.menuLocation.y = y + event.offsetY - this.$refs.list.getScrollTop() this.hideListsMenu() this.$nextTick(() => { this.listMenu.isShowItemMenu = true }) }, hideListMenu() { - let dom_selected = this.$refs.dom_tbody && this.$refs.dom_tbody.querySelector('tr.selected') - if (dom_selected) dom_selected.classList.remove('selected') + this.selectedIndex = -1 this.listMenu.isShowItemMenu = false this.listMenu.rightClickItemIndex = -1 }, @@ -406,24 +418,7 @@ export default { flex: auto; min-height: 0; position: relative; -} -.tbody { height: 100%; - overflow-y: auto; - td { - font-size: 12px; - :global(.badge) { - margin-left: 3px; - } - &:first-child { - // padding-left: 10px; - font-size: 11px; - color: @color-theme_2-font-label; - } - } - :global(.badge) { - opacity: .85; - } &.copying { .no-select { @@ -431,6 +426,24 @@ export default { } } } +: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; @@ -456,10 +469,10 @@ export default { each(@themes, { :global(#container.@{value}) { - .tbody { - td { + :global(.list) { + :global(.list-item-cell) { &:first-child { - color: ~'@{color-@{value}-theme_2-font-label}'; + color: ~'@{color-@{value}-theme_2-font-label}' !important; } } } diff --git a/src/renderer/components/material/VirtualizedList.vue b/src/renderer/components/material/VirtualizedList.vue new file mode 100644 index 00000000..54148621 --- /dev/null +++ b/src/renderer/components/material/VirtualizedList.vue @@ -0,0 +1,241 @@ + + + diff --git a/src/renderer/main.js b/src/renderer/main.js index 2ad40edb..8ef83b07 100644 --- a/src/renderer/main.js +++ b/src/renderer/main.js @@ -1,5 +1,5 @@ import Vue from 'vue' -import { sync } from 'vuex-router-sync' +// import { sync } from 'vuex-router-sync' import './event' @@ -20,7 +20,7 @@ import { getSetting } from './utils' import languageList from '@renderer/lang/languages.json' import { rendererSend, NAMES } from '../common/ipc' -sync(store, router) +// sync(store, router) Vue.config.productionTip = false Vue.config.devtools = process.env.NODE_ENV === 'development' diff --git a/src/renderer/store/modules/leaderboard.js b/src/renderer/store/modules/leaderboard.js index d5985c29..1d4bc698 100644 --- a/src/renderer/store/modules/leaderboard.js +++ b/src/renderer/store/modules/leaderboard.js @@ -9,9 +9,7 @@ for (const source of music.sources) { sources.push(source) } -// state -const state = { - boards: sourceList, +const listInfo = { list: [], total: 0, page: 1, @@ -19,6 +17,11 @@ const state = { key: null, } +// state +const state = { + boards: sourceList, +} + // getters const getters = { sources(state, getters, rootState, { sourceNames }) { @@ -27,16 +30,6 @@ const getters = { boards(state) { return state.boards }, - list(state) { - return state.list - }, - info(state) { - return { - total: state.total, - limit: state.limit, - page: state.page, - } - }, } // actions @@ -47,7 +40,6 @@ const actions = { // let tabId = rootState.setting.leaderboard.tabId // let key = `${source}${tabId}${page}` // if (state.list.length && state.key == key) return true - // commit('clearList') if (state.boards[source].length) return return music[source].leaderboard.getBoards().then(result => commit('setBoardsList', { boards: result, source })) }, @@ -56,14 +48,22 @@ const actions = { let tabId = rootState.setting.leaderboard.tabId let [source, bangId] = tabId.split('__') let key = `${source}${tabId}${page}` - if (state.list.length && state.key == key) return Promise.resolve() - commit('clearList') + if (listInfo.list.length && listInfo.key == key) return Promise.resolve(listInfo) + // commit('clearList') // return ( // cache.has(key) // ? Promise.resolve(cache.get(key)) // : music[source].leaderboard.getList(bangId, page) // ).then(result => commit('setList', { result, key })) - return music[source].leaderboard.getList(bangId, page).then(result => commit('setList', { result, key })) + return music[source].leaderboard.getList(bangId, page).then(result => { + cache.set(key, result) + listInfo.list = result.list + listInfo.total = result.total + listInfo.limit = result.limit + listInfo.page = result.page + listInfo.key = key + return listInfo + }) }, getListAll({ state, rootState }, id) { // console.log(source, id) @@ -96,18 +96,6 @@ const mutations = { setBoardsList(state, { boards, source }) { state.boards[source] = boards.list }, - setList(state, { result, key }) { - state.list = result.list - state.total = result.total - state.limit = result.limit - state.page = result.page - state.key = key - cache.set(key, result) - }, - clearList(state) { - state.list = [] - state.total = 0 - }, } export default { diff --git a/src/renderer/store/modules/list.js b/src/renderer/store/modules/list.js index 12c124f2..0e570b76 100644 --- a/src/renderer/store/modules/list.js +++ b/src/renderer/store/modules/list.js @@ -1,6 +1,7 @@ import musicSdk from '../../utils/music' import { clearLyric, clearMusicUrl } from '../../utils' import { sync as eventSyncName } from '@renderer/event/names' +import { removeListPosition, setListPrevSelectId } from '@renderer/utils/data' let allList = {} window.allList = allList @@ -340,6 +341,7 @@ const mutations = { if (index < 0) return let list = state.userList.splice(index, 1)[0] allListRemove(list) + removeListPosition(id) }, setUserListName(state, { id, name, isSync }) { if (!isSync) { @@ -380,9 +382,6 @@ const mutations = { state.userList.splice(index, 1) state.userList.splice(index + 1, 0, targetList) }, - setListScroll(state, { id, location }) { - if (allList[id]) allList[id].location = location - }, setMusicPosition(state, { id, position, list, isSync }) { if (!isSync) { window.eventHub.$emit(eventSyncName.send_action_list, { @@ -418,6 +417,9 @@ const mutations = { setOtherSource(state, { musicInfo, otherSource }) { musicInfo.otherSource = otherSource }, + setPrevSelectListId(state, val) { + setListPrevSelectId(val) + }, } export default { diff --git a/src/renderer/store/mutations.js b/src/renderer/store/mutations.js index 5cd60e33..9b02b7f7 100644 --- a/src/renderer/store/mutations.js +++ b/src/renderer/store/mutations.js @@ -61,9 +61,6 @@ export default { setMediaDeviceId(state, val) { state.setting.player.mediaDeviceId = val }, - setPrevSelectListId(state, val) { - state.setting.list.prevSelectListId = val - }, setDesktopLyricConfig(state, config) { state.setting.desktopLyric = Object.assign(state.setting.desktopLyric, config) }, diff --git a/src/renderer/utils/data.js b/src/renderer/utils/data.js new file mode 100644 index 00000000..8637ca7a --- /dev/null +++ b/src/renderer/utils/data.js @@ -0,0 +1,45 @@ +import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc' +import { throttle } from './index' + +let listPosition = {} +let listPrevSelectId + +const saveListPosition = throttle(() => { + rendererSend(NAMES.mainWindow.save_data, { + path: 'listPosition', + data: listPosition, + }) +}, 1000) + +export const initListPosition = () => { + return rendererInvoke(NAMES.mainWindow.get_data, 'listPosition').then(data => { + if (!data) data = {} + listPosition = data + }) +} +export const getListPosition = id => listPosition[id] || 0 +export const setListPosition = (id, position) => { + listPosition[id] = position || 0 + saveListPosition() +} +export const removeListPosition = id => { + delete listPosition[id] + saveListPosition() +} + +const saveListPrevSelectId = throttle(() => { + rendererSend(NAMES.mainWindow.save_data, { + path: 'listPrevSelectId', + data: listPrevSelectId, + }) +}, 200) +export const initListPrevSelectId = () => { + return rendererInvoke(NAMES.mainWindow.get_data, 'listPrevSelectId').then(id => { + listPrevSelectId = id + }) +} +export const getListPrevSelectId = () => listPrevSelectId +export const setListPrevSelectId = id => { + listPrevSelectId = id + saveListPrevSelectId() +} diff --git a/src/renderer/views/Download.vue b/src/renderer/views/Download.vue index d530c150..bbff13d4 100644 --- a/src/renderer/views/Download.vue +++ b/src/renderer/views/Download.vue @@ -14,21 +14,22 @@ div(:class="$style.download") th.nobreak(style="width: 22%;") {{$t('view.download.status')}} th.nobreak(style="width: 10%;") {{$t('view.download.quality')}} th.nobreak(style="width: 13%;") {{$t('view.download.action')}} - div.scroll(v-if="list.length" :class="$style.tbody" ref="dom_scrollContent") - table - tbody(ref="dom_tbody") - tr(v-for='(item, index) in showList' :key='item.key' @contextmenu="handleListItemRigthClick($event, index)" @click="handleDoubleClick($event, index)" :class="playListIndex === index ? $style.active : ''") - td.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" @click.stop) {{index + 1}} - td.break - span.select {{item.musicInfo.name}} - {{item.musicInfo.singer}} - td.break(style="width: 20%;") {{item.progress.progress}}% - td.break(style="width: 22%;") {{item.statusText}} - td.break(style="width: 10%;") {{item.type && item.type.toUpperCase()}} - td(style="width: 13%; padding-left: 0; padding-right: 0;") - material-list-buttons(:index="index" :download-btn="false" :file-btn="item.status != downloadStatus.ERROR" remove-btn - :start-btn="!item.isComplate && item.status != downloadStatus.WAITING && (item.status != downloadStatus.RUN)" - :pause-btn="!item.isComplate && (item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING)" :list-add-btn="false" - :play-btn="item.status == downloadStatus.COMPLETED" :search-btn="item.status == downloadStatus.ERROR" @btn-click="handleListBtnClick") + div(v-if="list.length" :class="$style.content" ref="dom_listContent") + material-virtualized-list(:list="showList" key-name="key" ref="list" :item-height="37" #default="{ item, index }" + containerClass="scroll" contentClass="list") + div.list-item(@click="handleDoubleClick($event, index)" @contextmenu="handleListItemRigthClick($event, index)" + :class="[{[$style.active]: playListIndex == index }, { selected: selectedIndex == index }, { active: selectedData.includes(item) }]") + div.list-item-cell.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" @click.stop) {{index + 1}} + div.list-item-cell.auto + span.select {{item.musicInfo.name}} - {{item.musicInfo.singer}} + div.list-item-cell(style="width: 20%;") {{item.progress.progress}}% + div.list-item-cell(style="width: 22%;") {{item.statusText}} + div.list-item-cell(style="width: 10%;") {{item.type && item.type.toUpperCase()}} + div.list-item-cell(style="width: 13%; padding-left: 0; padding-right: 0;") + material-list-buttons(:index="index" :download-btn="false" :file-btn="item.status != downloadStatus.ERROR" remove-btn + :start-btn="!item.isComplate && item.status != downloadStatus.WAITING && (item.status != downloadStatus.RUN)" + :pause-btn="!item.isComplate && (item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING)" :list-add-btn="false" + :play-btn="item.status == downloadStatus.COMPLETED" :search-btn="item.status == downloadStatus.ERROR" @btn-click="handleListBtnClick") material-menu(:menus="listItemMenu" :location="listMenu.menuLocation" item-name="name" :isShow="listMenu.isShowItemMenu" @menu-click="handleListItemMenuClick") div(:class="$style.noItem" v-else) @@ -52,8 +53,10 @@ export default { isShiftDown: false, isModDown: false, }, + selectedIndex: -1, lastSelectIndex: 0, listMenu: { + rightClickItemIndex: -1, isShowItemMenu: false, itemMenuControl: { play: true, @@ -217,7 +220,7 @@ export default { this.handleSelectAllData() }, handleDoubleClick(event, index) { - if (event.target.classList.contains('select')) return + if (this.listMenu.rightClickItemIndex > -1) return this.handleSelectData(event, index) @@ -248,14 +251,8 @@ export default { } this.selectedData = this.showList.slice(lastSelectIndex, clickIndex + 1) if (isNeedReverse) this.selectedData.reverse() - let nodes = this.$refs.dom_tbody.childNodes - do { - nodes[lastSelectIndex].classList.add('active') - lastSelectIndex++ - } while (lastSelectIndex <= clickIndex) } } else { - event.currentTarget.classList.add('active') this.selectedData.push(this.showList[clickIndex]) this.lastSelectIndex = clickIndex } @@ -265,21 +262,13 @@ export default { let index = this.selectedData.indexOf(item) if (index < 0) { this.selectedData.push(item) - event.currentTarget.classList.add('active') } else { this.selectedData.splice(index, 1) - event.currentTarget.classList.remove('active') } } else if (this.selectedData.length) this.removeAllSelect() }, removeAllSelect() { this.selectedData = [] - let dom_tbody = this.$refs.dom_tbody - if (!dom_tbody) return - let nodes = dom_tbody.querySelectorAll('.active') - for (const node of nodes) { - if (node.parentNode == dom_tbody) node.classList.remove('active') - } }, handleClick(index) { const key = this.showList[index].key @@ -323,11 +312,6 @@ export default { handleSelectAllData() { this.removeAllSelect() this.selectedData = [...this.showList] - - let nodes = this.$refs.dom_tbody.childNodes - for (const node of nodes) { - node.classList.add('active') - } }, // async handleFlowBtnClick(action) { // let selectedData = [...this.selectedData] @@ -363,13 +347,19 @@ export default { }, handleListItemRigthClick(event, index) { this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.showList[index].musicInfo.source].getMusicDetailPageUrl - let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected') - if (dom_selected) dom_selected.classList.remove('selected') - this.$refs.dom_tbody.querySelectorAll('tr')[index].classList.add('selected') - let dom_td = event.target.closest('td') + let dom_container = event.target.closest('.' + this.$style.download) + 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) + } this.listMenu.rightClickItemIndex = index - this.listMenu.menuLocation.x = dom_td.offsetLeft + event.offsetX - this.listMenu.menuLocation.y = dom_td.offsetTop + event.offsetY - this.$refs.dom_scrollContent.scrollTop + this.selectedIndex = index + let { x, y } = getOffsetValue(event.target) + this.listMenu.menuLocation.x = x + event.offsetX + this.listMenu.menuLocation.y = y + event.offsetY - this.$refs.list.getScrollTop() let item = this.showList[index] if (item.isComplate) { @@ -397,8 +387,7 @@ export default { }) }, hideListMenu() { - let dom_selected = this.$refs.dom_tbody && this.$refs.dom_tbody.querySelector('tr.selected') - if (dom_selected) dom_selected.classList.remove('selected') + this.selectedIndex = -1 this.listMenu.isShowItemMenu = false this.listMenu.rightClickItemIndex = -1 }, @@ -524,18 +513,18 @@ export default { // padding-left: 10px; } } -.tbody { +:global(.list) { flex: auto; overflow-y: auto; - td { - font-size: 12px; + :global(.list-item-cell) { + font-size: 12px !important; &:first-child { // padding-left: 10px; - font-size: 11px; - color: @color-theme_2-font-label; + font-size: 11px !important; + color: @color-theme_2-font-label !important; } } - tr { + :global(.list-item) { &.active { color: @color-btn; } @@ -544,17 +533,17 @@ export default { each(@themes, { :global(#container.@{value}) { - .tbody { - tr { - &.active { - color: ~'@{color-@{value}-btn}'; - } - } - td { + :global(.list) { + :global(.list-item-cell) { &:first-child { color: ~'@{color-@{value}-theme_2-font-label}'; } } + :global(.list-item) { + &.active { + color: ~'@{color-@{value}-btn}' !important; + } + } } } }) diff --git a/src/renderer/views/Leaderboard.vue b/src/renderer/views/Leaderboard.vue index 425a4345..6b5350dd 100644 --- a/src/renderer/views/Leaderboard.vue +++ b/src/renderer/views/Leaderboard.vue @@ -16,7 +16,7 @@ @contextmenu="handleListsItemRigthClick($event, index)") span(:class="$style.listsLabel") {{item.name}} div(:class="$style.list") - material-song-list(v-model="selectedData" ref="songList" :hideListsMenu="hideListsMenu" :rowWidth="{r1: '5%', r2: 'auto', r3: '22%', r4: '22%', r5: '9%', r6: '15%'}" @action="handleSongListAction" :source="source" :page="page" :limit="info.limit" :total="info.total" :noItem="$t('material.song_list.loding_list')" :list="list") + material-song-list(v-model="selectedData" ref="songList" :hideListsMenu="hideListsMenu" :rowWidth="{r1: '5%', r2: 'auto', r3: '22%', r4: '22%', r5: '9%', r6: '15%'}" @action="handleSongListAction" :source="source" :page="page" :limit="listInfo.limit" :total="listInfo.total" :noItem="$t('material.song_list.loding_list')" :list="list") material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false") material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectedData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false") material-list-add-modal(:show="isShowListAdd" :musicInfo="musicInfo" @close="isShowListAdd = false") @@ -56,11 +56,18 @@ export default { y: 0, }, }, + listInfo: { + list: [], + total: 0, + page: 1, + limit: 30, + key: null, + }, } }, computed: { ...mapGetters(['setting']), - ...mapGetters('leaderboard', ['sources', 'boards', 'list', 'info']), + ...mapGetters('leaderboard', ['sources', 'boards', 'info']), ...mapGetters('list', ['defaultList']), boardList() { return this.source && this.boards[this.source] ? this.boards[this.source] : [] @@ -79,13 +86,22 @@ export default { }, ] }, + list() { + return this.listInfo.list + }, }, watch: { tabId(n, o) { this.setLeaderboard({ tabId: n }) if (!n || (!o && this.page !== 1)) return - this.getList(1).then(() => { - this.page = this.info.page + this.listInfo.list = [] + this.getList(1).then(listInfo => { + this.listInfo.list = listInfo.list + this.listInfo.total = listInfo.total + this.listInfo.limit = listInfo.limit + this.listInfo.page = listInfo.page + this.listInfo.key = listInfo.key + this.page = listInfo.page }) }, source(n, o) { @@ -102,7 +118,7 @@ export default { mounted() { this.source = this.setting.leaderboard.source this.tabId = this.setting.leaderboard.tabId - this.page = this.info.page + this.page = this.listInfo.page }, methods: { ...mapMutations(['setLeaderboard']), @@ -206,8 +222,14 @@ export default { }) }, handleTogglePage(page) { - this.getList(page).then(() => { - this.page = this.info.page + this.listInfo.list = [] + this.getList(page).then(listInfo => { + this.listInfo.list = listInfo.list + this.listInfo.total = listInfo.total + this.listInfo.limit = listInfo.limit + this.listInfo.page = listInfo.page + this.listInfo.key = listInfo.key + this.page = listInfo.page }) }, handleAddDownload(type) { @@ -381,7 +403,7 @@ export default { transition: opacity .3s ease; } - :global(.list) { + :global(.selection-list) { max-height: 500px; box-shadow: 0 1px 8px 0 rgba(0,0,0,.2); li { @@ -465,7 +487,7 @@ each(@themes, { :global(.label) { color: ~'@{color-@{value}-theme_2-font}' !important; } - :global(.list) { + :global(.selection-list) { li { background-color: ~'@{color-@{value}-theme_2-background_2}'; &:hover { diff --git a/src/renderer/views/List.vue b/src/renderer/views/List.vue index 25ab684e..dc07029f 100644 --- a/src/renderer/views/List.vue +++ b/src/renderer/views/List.vue @@ -36,35 +36,26 @@ th.nobreak(style="width: 22%;") {{$t('view.list.album')}} th.nobreak(style="width: 9%;") {{$t('view.list.time')}} th.nobreak(style="width: 15%;") {{$t('view.list.action')}} - div(v-if="delayShow && list.length" :class="$style.content") - div.scroll(:class="$style.tbody" @scroll="handleScroll" ref="dom_scrollContent") - table - tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody") - tr(v-for='(item, index) in list' :key='item.songmid' :id="'mid_' + item.songmid" @contextmenu="handleListItemRigthClick($event, index)" - @click="handleDoubleClick($event, index)" :class="[isPlayList && playInfo.playIndex === index ? $style.active : '', assertApiSupport(item.source) ? null : $style.disabled]") - td.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}} - td.break - span.select {{item.name}} - span(:class="[$style.labelSource, $style.noSelect]" v-if="isShowSource") {{item.source}} - //- span.badge.badge-light(v-if="item._types['128k']") 128K - //- span.badge.badge-light(v-if="item._types['192k']") 192K - //- span.badge.badge-secondary(v-if="item._types['320k']") 320K - //- span.badge.badge-theme-info(v-if="item._types.ape") APE - //- span.badge.badge-theme-success(v-if="item._types.flac") FLAC - td.break(style="width: 22%;") - span.select {{item.singer}} - td.break(style="width: 22%;") - span.select {{item.albumName}} - td(style="width: 9%;") - span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}} - td(style="width: 15%; padding-left: 0; padding-right: 0;") - material-list-buttons(:index="index" @btn-click="handleListBtnClick" :download-btn="assertApiSupport(item.source)") - //- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载 - //- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听 - //- button.btn-secondary(type='button' @click.stop='handleRemove(index)') 删除 - //- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)') + + div(v-if="list.length" :class="$style.content" ref="dom_listContent") + material-virtualized-list(:list="list" key-name="songmid" ref="list" #default="{ item, index }" :item-height="37" + @scroll="handleScroll" containerClass="scroll" contentClass="list" @contextmenu.native.capture="handleContextMenu") + div.list-item(@click="handleDoubleClick($event, index)" + :class="[{ [$style.active]: isPlayList && playInfo.playIndex === index }, { selected: selectedIndex == index }, { active: selectdListDetailData.includes(item) }, { [$style.disabled]: !assertApiSupport(item.source) }]" + @contextmenu="handleListItemRigthClick($event, index)") + div.list-item-cell.nobreak.center(style="flex: 0 0 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}} + div.list-item-cell.auto.break(:tips="item.name") + span.select {{item.name}} + span(:class="[$style.labelSource, $style.noSelect]" v-if="isShowSource") {{item.source}} + div.list-item-cell.break(style="flex: 0 0 22%;") + span.select {{item.singer}} + div.list-item-cell.break(style="flex: 0 0 22%;") + span.select {{item.albumName}} + div.list-item-cell(style="flex: 0 0 9%;") + span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}} + div.list-item-cell(style="flex: 0 0 15%; padding-left: 0; padding-right: 0;") + material-list-buttons(:index="index" @btn-click="handleListBtnClick" :download-btn="assertApiSupport(item.source)") div(:class="$style.noItem" v-else) - p(v-text="list.length ? $t('view.list.loding_list') : $t('view.list.no_item')") + p(v-text="$t('view.list.no_item')") material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false") material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectdListDetailData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false") //- material-flow-btn(:show="isShowEditBtn" :play-btn="false" @btn-click="handleFlowBtnClick") @@ -79,8 +70,11 @@