完善歌单功能

pull/96/head
lyswhut 2019-09-03 02:49:48 +08:00
parent 507d495f3f
commit 9e9053c0eb
11 changed files with 373 additions and 102 deletions

View File

@ -18,7 +18,7 @@ div(:class="$style.songList")
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)")
td.nobreak.center(style="width: 37px;" @click.stop)
material-checkbox(:id="index.toString()" v-model="selectdList" :value="item")
material-checkbox(:id="index.toString()" v-model="selectdList" @change="handleChangeSelect" :value="item")
td.break(style="width: 25%;")
| {{item.name}}
span.badge.badge-info(v-if="item._types['320k']") 高品质
@ -34,7 +34,7 @@ div(:class="$style.songList")
div(:class="$style.pagination")
material-pagination(:count="total" :limit="limit" :page="page" @btn-click="handleTogglePage")
div(v-else :class="$style.noitem")
p(v-html="noitem")
p(v-html="noItem")
material-flow-btn(:show="isShowEditBtn && (source == 'kw' || !isAPITemp)" :remove-btn="false" @btn-click="handleFlowBtnClick")
</template>
@ -73,9 +73,9 @@ export default {
source: {
type: String,
},
noitem: {
noItem: {
type: String,
default: '搜我所想~~😉',
default: '列表加载中...',
},
},
computed: {
@ -95,7 +95,19 @@ export default {
this.isSelectAll = false
this.isShowEditBtn = false
}
this.$emit('input', [...n])
},
selectdData(n) {
const len = n.length
if (len) {
this.isSelectAll = true
this.isIndeterminate = len !== this.list.length
this.isShowEditBtn = true
this.selectdList = [...n]
} else {
this.isSelectAll = false
this.isShowEditBtn = false
this.resetSelect()
}
},
list(n) {
this.resetSelect()
@ -145,6 +157,9 @@ export default {
emitEvent(action, data) {
this.$emit('action', { action, data })
},
handleChangeSelect() {
this.$emit('input', [...this.selectdList])
},
},
}
</script>

View File

@ -0,0 +1,194 @@
<template lang="pug">
div(:class="$style.tagList")
div(:class="$style.label" ref="dom_btn" @click="handleShow") {{value.name}}
div.scroll(:class="$style.list" @click.stop ref="dom_list" :style="listStyle")
div(:class="$style.tag" @click="handleClick(null)") 全部
dl(:class="$style.type" v-for="type in list")
dt(:class="$style.type") {{type.name}}
dd(:class="$style.tag" v-for="tag in type.list" @click="handleClick(tag)") {{tag.name}}
</template>
<script>
// import { isChildren } from '../../utils'
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
value: {
type: Object,
},
},
data() {
return {
show: false,
listStyle: {
height: 0,
opacity: 0,
},
}
},
watch: {
show(n) {
this.$nextTick(() => {
if (n) {
let sh = this.$refs.dom_list.scrollHeight
this.listStyle.height = (sh > 250 ? 250 : sh) + 'px'
this.listStyle.opacity = 1
this.listStyle.overflow = 'auto'
} else {
this.listStyle.height = 0
this.listStyle.opacity = 0
}
})
},
list() {
this.$refs.dom_list.scrollTop = 0
},
},
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeDestroy() {
document.removeEventListener('click', this.handleHide)
},
methods: {
handleHide(e) {
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && e.target == this.$refs.dom_btn) return
setTimeout(() => {
this.show = false
}, 50)
},
handleClick(item) {
if (!item) {
item = {
name: '全部',
id: null,
}
}
if (item.id === this.value.id) return this.handleShow()
this.$emit('input', item)
this.$emit('change', item)
this.handleShow()
},
handleShow() {
this.show = !this.show
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.tag-list {
font-size: 12px;
position: relative;
}
.label {
padding: 8px 15px;
// background-color: @color-btn-background;
transition: background-color @transition-theme;
border-top: 2px solid @color-tab-border-bottom;
// border-left: 2px solid @color-tab-border-bottom;
box-sizing: border-box;
text-align: center;
// border-top-left-radius: 3px;
color: @color-btn;
cursor: pointer;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
.list {
position: absolute;
top: 100%;
width: 644px;
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-right: 2px solid @color-tab-border-bottom;
border-bottom-right-radius: 5px;
background-color: @color-theme_2;
overflow: hidden;
opacity: 0;
transition: .25s ease;
transition-property: height, opacity;
z-index: 10;
li {
cursor: pointer;
padding: 8px 15px;
// color: @color-btn;
text-align: center;
outline: none;
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
}
.tag {
display: inline-block;
margin: 5px;
background-color: @color-btn-background;
padding: 5px;
cursor: pointer;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
}
each(@themes, {
:global(#container.@{value}) {
.label {
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
// border-left-color: ~'@{color-@{value}-tab-border-bottom}';
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
// border-left-color: ~'@{color-@{value}-tab-border-bottom}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
}
})
</style>

View File

@ -25,12 +25,19 @@ const state = {
limit: 30,
key: null,
},
isVisibleListDetail: false,
}
sources.forEach(source => {
state.tags[source.id] = null
})
// getters
const getters = {
sourceInfo: () => ({ sources, sortList }),
tags: state => state.tags,
isVisibleListDetail: state => state.isVisibleListDetail,
listData(state) {
return state.list
},
@ -47,17 +54,19 @@ const actions = {
},
getList({ state, rootState, commit }, page) {
let source = rootState.setting.songList.source
let tabId = rootState.setting.songList.tagId
let tabId = rootState.setting.songList.tagInfo.id
let sortId = rootState.setting.songList.sortId
console.log(sortId)
let key = `${source}${sortId}${tabId}${page}`
if (state.list.list.length && state.list.key == key) return true
return music[source].songList.getList(sortId, tabId, page).then(result => commit('setList', { result, key }))
return music[source].songList.getList(sortId, tabId, page).then(result => commit('setList', { result, key, page }))
},
getListDetail({ state, rootState, commit }, { id, page }) {
let source = rootState.setting.songList.source
let key = `${source}${id}${page}}`
let key = `${source}${id}${page}`
if (state.listDetail.list.length && state.listDetail.key == key) return true
return music[source].songList.getListDetail(id, page).then(result => commit('setListDetail', { result, key }))
console.log(id, page)
return music[source].songList.getListDetail(id, page).then(result => commit('setListDetail', { result, key, page }))
},
}
@ -66,20 +75,23 @@ const mutations = {
setTags(state, { tags, source }) {
state.tags[source] = tags
},
setList(state, { result, key }) {
setList(state, { result, key, page }) {
state.list.list = result.list
state.list.total = result.total
state.list.limit = result.limit
state.list.page = result.page
state.list.page = page
state.list.key = key
},
setListDetail(state, { result, key }) {
setListDetail(state, { result, key, page }) {
state.listDetail.list = result.list
state.listDetail.total = result.total
state.listDetail.limit = result.limit
state.listDetail.page = result.page
state.listDetail.page = page
state.listDetail.key = key
},
setVisibleListDetail(state, bool) {
state.isVisibleListDetail = bool
},
}
export default {

View File

@ -12,8 +12,8 @@ export default {
if (tabId != null) state.setting.leaderboard.tabId = tabId
if (source != null) state.setting.leaderboard.source = source
},
setSongList(state, { sortId, tagId, source }) {
if (tagId != null) state.setting.songList.tagId = tagId
setSongList(state, { sortId, tagInfo, source }) {
if (tagInfo != null) state.setting.songList.tagInfo = tagInfo
if (sortId != null) state.setting.songList.sortId = sortId
if (source != null) state.setting.songList.source = source
},

View File

@ -186,7 +186,10 @@ export const updateSetting = setting => {
songList: {
source: 'kw',
sortId: 'hot',
tagId: null,
tagInfo: {
name: '全部',
id: null,
},
},
themeId: 0,
sourceId: 'kw',

View File

@ -127,7 +127,7 @@ export default {
},
filterTagInfo(rawList) {
return rawList.map(type => ({
type: type.first,
name: type.first,
list: type.second.map(item => ({
parent_id: type.first,
parent_name: type.first,

View File

@ -5,7 +5,7 @@ export default {
list: [
{
id: 'kgtop500',
name: 'TOP500',
name: '酷狗TOP500',
bangid: '8888',
},
{
@ -45,7 +45,7 @@ export default {
},
{
id: 'kgdyrgb',
name: '电音热歌榜',
name: '电音榜',
bangid: '33160',
},
{

View File

@ -41,8 +41,9 @@ export default {
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
: `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&`
},
getSongListUrl(sortId, tagId = '', page) {
return `http://www2.kugou.kugou.com/yueku/v9/special/index/getData/getData.html&cdn=cdn&t=${sortId}&c=${tagId}?is_ajax=1&p=${page}`
getSongListUrl(sortId, tagId, page) {
if (tagId == null) tagId = ''
return `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_ajax=1&cdn=cdn&t=${sortId}&c=${tagId}&p=${page}`
},
getSongListDetailUrl(id) {
return `http://www2.kugou.kugou.com/yueku/v9/special/single/${id}-5-9999.html`
@ -71,10 +72,10 @@ export default {
},
filterTagInfo(rawData) {
const result = []
for (const type of Object.keys(rawData)) {
for (const name of Object.keys(rawData)) {
result.push({
type,
list: rawData[type].data.map(tag => ({
name,
list: rawData[name].data.map(tag => ({
parent_id: tag.parent_id,
parent_name: tag.pname,
id: tag.id,
@ -196,7 +197,7 @@ export default {
interval: formatPlayTime(item.duration / 1000),
img: null,
lrc: null,
hash: item.HASH,
hash: item.hash,
types,
_types,
typeUrl: {},
@ -232,7 +233,6 @@ export default {
)
if (!tagId) tasks.push(this.getSongListRecommend()) // 如果是所有类别,则顺便获取推荐列表
return Promise.all(tasks).then(([list, info, recommendList]) => {
console.log(recommendList)
if (recommendList) list.unshift(...recommendList)
return {
list,

View File

@ -58,7 +58,7 @@ export default {
},
filterTagInfo(rawList) {
return rawList.map(type => ({
type: type.name,
name: type.name,
list: type.data.map(item => ({
parent_id: type.id,
parent_name: type.name,
@ -115,7 +115,7 @@ export default {
return this._requestObj_listDetail.promise.then(({ body }) => {
if (body.result !== 'ok') return this.getListDetail(id, page)
return {
list: this.filterListDetail(body.data.musiclist),
list: this.filterListDetail(body.musiclist),
page,
limit: body.rn,
total: body.total,

View File

@ -1,9 +1,9 @@
<template lang="pug">
div(:class="$style.leaderboard")
div(:class="$style.header")
material-tab(:class="$style.tab" :list="types" item-key="id" item-name="name" v-model="tabId")
material-tab(:class="$style.tab" :list="types" align="left" item-key="id" item-name="name" v-model="tabId")
material-select(:class="$style.select" :list="sourceInfo.sources" item-key="id" item-name="name" v-model="source")
material-song-list(v-model="selectdData" noItem="列表加载中..." @action="handleSongListAction" :source="source" :page="page" :limit="info.limit" :total="info.total" :list="list")
material-song-list(v-model="selectdData" @action="handleSongListAction" :source="source" :page="page" :limit="info.limit" :total="info.total" :list="list")
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectdData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
</template>
@ -81,6 +81,7 @@ export default {
if (index == null) {
targetSong = this.selectdData[0]
this.defaultListAddMultiple(this.selectdData)
this.resetSelect()
} else {
targetSong = this.list[index]
this.defaultListAdd(targetSong)
@ -122,6 +123,7 @@ export default {
}
this.createDownloadMultiple({ list: [...this.selectdData], type })
this.isShowDownloadMultiple = false
this.resetSelect()
},
handleFlowBtnClick(action) {
switch (action) {
@ -133,6 +135,7 @@ export default {
break
case 'add':
this.defaultListAddMultiple(this.selectdData)
this.resetSelect()
break
}
},
@ -150,6 +153,9 @@ export default {
return this.handleSearch(data)
}
},
resetSelect() {
this.selectdData = []
},
},
}
</script>

View File

@ -1,8 +1,22 @@
<template lang="pug">
div(:class="$style.leaderboard")
div(:class="$style.header")
material-tag-list(:class="$style.tagList" :list="tagList" v-model="tagInfo")
material-tab(:class="$style.tab" :list="sorts" item-key="id" item-name="name" v-model="sortId")
material-select(:class="$style.select" :list="sourceInfo.sources" item-key="id" item-name="name" v-model="source")
div(:class="$style.container")
div(:class="$style.materialSongList" v-show="isVisibleListDetail")
material-song-list(v-model="selectdData" @action="handleSongListAction" :source="source" :page="listDetail.page" :limit="listDetail.limit" :total="listDetail.total" :list="listDetail.list")
div.scroll(:class="$style.content" ref="dom_scrollContent" v-show="!isVisibleListDetail")
ul
li(:class="$style.item" v-for="(item, index) in listData.list" @click="handleItemClick(index)")
div(:class="$style.left")
img(:src="item.img")
div(:class="$style.right" :src="item.img")
h4(:title="item.name") {{item.name}}
p(:title="item.desc") {{item.desc}}
div(:class="$style.pagination")
material-pagination(:count="listData.total" :limit="listData.limit" :page="listData.page" @btn-click="handleTogglePage")
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectdData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
</template>
@ -15,11 +29,12 @@ export default {
name: 'SongList',
data() {
return {
tagId: null,
tagInfo: {
name: '全部',
id: null,
},
sortId: undefined,
source: null,
listPage: 1,
songListPage: 1,
isShowDownload: false,
musicInfo: null,
selectdData: [],
@ -29,7 +44,7 @@ export default {
},
computed: {
...mapGetters(['setting']),
...mapGetters('songList', ['sourceInfo', 'tags', 'listData', 'listDetail']),
...mapGetters('songList', ['sourceInfo', 'tags', 'listData', 'isVisibleListDetail', 'listDetail']),
...mapGetters('list', ['defaultList']),
sorts() {
return this.source ? this.sourceInfo.sortList[this.source] : []
@ -37,24 +52,31 @@ export default {
isAPITemp() {
return this.setting.apiSource == 'temp'
},
tagList() {
return this.tags[this.source] ? this.tags[this.source].tags : []
},
},
watch: {
sortId(n, o) {
this.setSongList({ sortId: n })
if (o === undefined && this.listPage !== 1) return
if (o === undefined && this.listData.page !== 1) return
this.getList(1).then(() => {
this.listPage = this.listData.listPage
this.$nextTick(() => {
scrollTo(this.$refs.dom_scrollContent, 0)
})
})
},
tagId(n, o) {
this.setSongList({ tagId: n })
if (!o && this.listPage !== 1) return
tagInfo(n, o) {
this.setSongList({ tagInfo: n })
if (!o && this.listData.page !== 1) return
if (this.isToggleSource) {
this.isToggleSource = false
return
}
this.getList(1).then(() => {
this.listPage = this.listData.listPage
this.$nextTick(() => {
scrollTo(this.$refs.dom_scrollContent, 0)
})
})
},
source(n, o) {
@ -62,41 +84,31 @@ export default {
if (!this.tags[n]) this.getTags()
if (o) {
this.isToggleSource = true
this.tagId = null
this.tagInfo = {
name: '全部',
id: null,
}
this.sortId = this.sorts[0] && this.sorts[0].id
}
},
},
mounted() {
this.source = this.setting.songList.source
this.tagId = this.setting.songList.tagId
this.listPage = this.listData.page
this.songListPage = this.listDetail.page
this.isToggleSource = true
this.tagInfo = this.setting.songList.tagInfo
this.sortId = this.setting.songList.sortId
},
methods: {
...mapMutations(['setSongList']),
...mapActions('songList', ['getTags', 'getList', 'getListDetail']),
...mapMutations('songList', ['setVisibleListDetail']),
...mapActions('download', ['createDownload', 'createDownloadMultiple']),
...mapMutations('list', ['defaultListAdd', 'defaultListAddMultiple']),
...mapMutations('player', ['setList']),
handleDoubleClick(index) {
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
(this.source == 'kw' || (!this.isAPITemp && this.list[index].source != 'tx')) ? this.testPlay(index) : this.handleSearch(index)
this.clickTime = 0
this.clickIndex = -1
},
handleListBtnClick(info) {
switch (info.action) {
case 'download':
this.musicInfo = this.list[info.index]
this.musicInfo = this.listDetail.list[info.index]
this.$nextTick(() => {
this.isShowDownload = true
})
@ -116,8 +128,9 @@ export default {
if (index == null) {
targetSong = this.selectdData[0]
this.defaultListAddMultiple(this.selectdData)
this.resetSelect()
} else {
targetSong = this.list[index]
targetSong = this.listDetail.list[index]
this.defaultListAdd(targetSong)
}
let targetIndex = this.defaultList.list.findIndex(
@ -132,7 +145,7 @@ export default {
}
},
handleSearch(index) {
const info = this.list[index]
const info = this.listDetail.list[index]
this.$router.push({
path: 'search',
query: {
@ -142,19 +155,11 @@ export default {
},
handleTogglePage(page) {
this.getList(page).then(() => {
this.page = this.info.page
this.$nextTick(() => {
scrollTo(this.$refs.dom_scrollContent, 0)
})
})
},
handleSelectAllData(isSelect) {
this.selectdData = isSelect ? [...this.list] : []
},
resetSelect() {
this.isSelectAll = false
this.selectdData = []
},
handleAddDownload(type) {
this.createDownload({ musicInfo: this.musicInfo, type })
this.isShowDownload = false
@ -169,6 +174,10 @@ export default {
this.resetSelect()
this.isShowDownloadMultiple = false
},
handleItemClick(index) {
this.setVisibleListDetail(true)
this.getListDetail({ id: this.listData.list[index].id, page: 1 })
},
handleFlowBtnClick(action) {
switch (action) {
case 'download':
@ -176,7 +185,6 @@ export default {
break
case 'play':
this.testPlay()
this.resetSelect()
break
case 'add':
this.defaultListAddMultiple(this.selectdData)
@ -184,6 +192,23 @@ export default {
break
}
},
handleSongListAction({ action, data }) {
switch (action) {
case 'listBtnClick':
return this.handleListBtnClick(data)
case 'togglePage':
return this.handleTogglePage(data)
case 'flowBtnClick':
return this.handleFlowBtnClick(data)
case 'testPlay':
return this.testPlay(data)
case 'search':
return this.handleSearch(data)
}
},
resetSelect() {
this.selectdData = []
},
},
}
</script>
@ -211,46 +236,62 @@ export default {
flex: none;
width: 80px;
}
.content {
.container {
flex: auto;
display: flex;
overflow: hidden;
flex-flow: column nowrap;
}
.list {
position: relative;
.materialSongList {
position: absolute;
width: 100%;
height: 100%;
font-size: 14px;
display: flex;
flex-flow: column nowrap;
// table {
// position: relative;
// thead {
// position: fixed;
// width: 100%;
// th {
// width: 100%;
// }
// }
// }
top: 0;
left: 0;
}
.thead {
flex: none;
}
.tbody {
flex: auto;
.content {
height: 100%;
overflow-y: auto;
td {
padding: 0 15px;
ul {
display: flex;
flex-flow: row wrap;
}
}
.item {
width: 100% / 3;
box-sizing: border-box;
display: flex;
padding-top: 15px;
}
.left {
flex: none;
width: 30%;
display: flex;
img {
max-width: 100%;
}
}
.right {
flex: auto;
padding: 5px 15px 5px 7px;
overflow: hidden;
h4 {
font-size: 14px;
text-align: justify;
line-height: 1.2;
.mixin-ellipsis-1;
}
p {
margin-top: 12px;
font-size: 12px;
:global(.badge) {
margin-right: 3px;
&:first-child {
margin-left: 3px;
}
&:last-child {
margin-right: 0;
}
}
.mixin-ellipsis-2;
text-align: justify;
line-height: 1.2;
// text-indent: 24px;
color: #888;
}
}
.pagination {