v2.0.0
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<SongList ref="list_ref" :list-info="listInfo" @toggle-page="togglePage" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, ref, nextTick } from '@common/utils/vueTools'
|
||||
import { listInfo } from '@renderer/store/songList/state'
|
||||
import { getAndSetList } from '@renderer/store/songList/action'
|
||||
import { useRouter, useRoute, onBeforeRouteLeave } from '@common/utils/vueRouter'
|
||||
import LX from '@renderer/types/lx'
|
||||
import SongList from './components/SongList.vue'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
source: LX.OnlineSource
|
||||
tagId: string
|
||||
sortId?: string
|
||||
page: number
|
||||
}>()
|
||||
|
||||
|
||||
const list_ref = ref<any>(null)
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const getListData = async(source: LX.OnlineSource, tabId: string, sortId: string, page: number) => {
|
||||
// console.log(source, tabId, sortId, page)
|
||||
await getAndSetList(source, tabId, sortId, page).then(() => {
|
||||
if (listInfo.key == window.lx.songListInfo.songlistKey && window.lx.songListInfo.songlistPosition) {
|
||||
nextTick(() => {
|
||||
list_ref.value?.scrollTo(window.lx.songListInfo.songlistPosition)
|
||||
})
|
||||
} else if (list_ref.value) {
|
||||
window.lx.songListInfo.songlistKey = null
|
||||
nextTick(() => {
|
||||
list_ref.value.scrollTo(0)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const togglePage = (page: number) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
sortId: props.sortId ?? '',
|
||||
page,
|
||||
},
|
||||
})
|
||||
// getListData(props.source, props.tagId, props.sortId ?? '', page)
|
||||
}
|
||||
|
||||
watch(() => [props.source, props.tagId, props.sortId, props.page], ([source, tagId, sortId, page]) => {
|
||||
// const source = (await getLeaderboardSetting()).source as LX.OnlineSource
|
||||
// console.log(source, tagId, sortId)
|
||||
if (!source || !sortId) return
|
||||
// console.log(source, tagId, sortId, page)
|
||||
getListData(source as LX.OnlineSource, tagId as string, sortId as string, page as number)
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
window.lx.songListInfo.songlistKey = listInfo.key
|
||||
if (list_ref.value) window.lx.songListInfo.songlistPosition = list_ref.value.getScrollTop()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.container {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.listContent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 15px;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
width: 32%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
margin-top: 15px;
|
||||
cursor: pointer;
|
||||
transition: opacity @transition-normal;
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
.left {
|
||||
flex: none;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
display: flex;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
opacity: .9;
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
}
|
||||
// box-shadow: 0 0 2px 0 rgba(0,0,0,.2);
|
||||
}
|
||||
.right {
|
||||
flex: auto;
|
||||
padding: 3px 15px 5px 7px;
|
||||
overflow: hidden;
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
height: 2.6em;
|
||||
text-align: justify;
|
||||
line-height: 1.3;
|
||||
.mixin-ellipsis-2;
|
||||
}
|
||||
}
|
||||
.songlist_info {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
gap: 15px;
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
.mixin-ellipsis-1;
|
||||
text-align: justify;
|
||||
line-height: 1.2;
|
||||
// text-indent: 24px;
|
||||
color: var(--color-font-label);
|
||||
svg {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
.author {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
.mixin-ellipsis-1;
|
||||
text-align: justify;
|
||||
line-height: 1.2;
|
||||
// text-indent: 24px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
.pagination {
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
// left: 50%;
|
||||
// transform: translateX(-50%);
|
||||
}
|
||||
.noitem {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// background-color: var(--color-000);
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<material-modal :show="props.modelValue" teleport="#view" width="60%" @close="emit('update:modelValue', $event)" @after-enter="$refs.input.focus()">
|
||||
<main :class="$style.main">
|
||||
<h2>{{ $t('songlist__import_input_title') }}</h2>
|
||||
<div :class="$style.inputContent">
|
||||
<base-selection v-model="source" :class="$style.select" :list="props.sourceList" item-key="id" item-name="name" />
|
||||
<base-input
|
||||
ref="input"
|
||||
v-model.trim="text"
|
||||
:class="$style.input"
|
||||
:placeholder="$t('songlist__import_input_tip')"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.footer">
|
||||
<div :class="$style.tips">
|
||||
<ul>
|
||||
<li>{{ $t('songlist__import_input_tip_1') }}</li>
|
||||
<li>{{ $t('songlist__import_input_tip_2') }}</li>
|
||||
<li>{{ $t('songlist__import_input_tip_3') }}</li>
|
||||
<li>
|
||||
{{ $t('songlist__import_input_tip_4') }}
|
||||
<span
|
||||
class="hover underline"
|
||||
aria-label="https://lyswhut.github.io/lx-music-doc/desktop/faq/cannot-open-songlist"
|
||||
@click="openUrl('https://lyswhut.github.io/lx-music-doc/desktop/faq/cannot-open-songlist')"
|
||||
>FAQ</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<base-btn :class="$style.btn" @click="handleSubmit">{{ $t('songlist__import_input_btn_confirm') }}</base-btn>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { openSongListInputInfo } from '@renderer/store/songList/state'
|
||||
import { setOpenSongListInputInfo } from '@renderer/store/songList/action'
|
||||
import { ref, watch } from '@common/utils/vueTools'
|
||||
import { useRoute, useRouter } from '@common/utils/vueRouter'
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
sourceList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const text = ref('')
|
||||
const source = ref('')
|
||||
|
||||
watch(() => props.modelValue, (visible) => {
|
||||
if (!visible) return
|
||||
source.value = openSongListInputInfo.source || route.query.source
|
||||
// text.value = openSongListInputInfo.text
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!text.value.length) return
|
||||
setOpenSongListInputInfo(text.value, source.value)
|
||||
router.push({
|
||||
path: '/songList/detail',
|
||||
query: {
|
||||
source: source.value,
|
||||
id: text.value,
|
||||
refresh: 'true',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 0 15px;
|
||||
// max-width: 530px;
|
||||
// min-width: 300px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
// text-align: center;
|
||||
padding: 15px 0 8px;
|
||||
}
|
||||
}
|
||||
.inputContent {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.select {
|
||||
width: auto;
|
||||
:global {
|
||||
.label-content {
|
||||
height: 100%;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.selection-list {
|
||||
li {
|
||||
// background-color: var(--color-main-background);
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
font-size: 13px;
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.input {
|
||||
flex: auto;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
// width: 100%;
|
||||
// height: 26px;
|
||||
padding: 8px 8px;
|
||||
color: var(--color-font);
|
||||
}
|
||||
.footer {
|
||||
margin: 50px 0 15px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.tips {
|
||||
flex: auto;
|
||||
font-size: 12px;
|
||||
color: var(--color-font);
|
||||
line-height: 1.5;
|
||||
ul {
|
||||
list-style: decimal;
|
||||
padding-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
// box-sizing: border-box;
|
||||
// margin-left: 15px;
|
||||
// margin-bottom: 15px;
|
||||
// height: 36px;
|
||||
// line-height: 36px;
|
||||
// padding: 0 10px !important;
|
||||
min-width: 80px;
|
||||
// .mixin-ellipsis-1;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<div v-show="!props.listInfo.noItemLabel" ref="dom_list_ref" :class="$style.listContent" class="scroll">
|
||||
<ul>
|
||||
<li v-for="item in props.listInfo.list" :key="item.id" :class="$style.item" @click="toDetail(item)">
|
||||
<div :class="$style.image">
|
||||
<img :class="$style.img" loading="lazy" decoding="async" :src="item.img">
|
||||
</div>
|
||||
<div :class="$style.desc">
|
||||
<h4>{{ item.name }}</h4>
|
||||
<div>
|
||||
<div :class="$style.songlist_info">
|
||||
<span v-if="item.total != null"><svg-icon name="music" />{{ item.total }}</span>
|
||||
<span v-if="item.play_count != null"><svg-icon name="headphones" />{{ item.play_count }}</span>
|
||||
</div>
|
||||
<p :class="$style.author">{{ item.author }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li v-for="(i, index) in 6" :key="index" :class="$style.item" style="margin-bottom: 0;height: 0;" />
|
||||
</ul>
|
||||
<div :class="$style.pagination">
|
||||
<material-pagination :count="props.listInfo.total" :limit="props.listInfo.limit" :page="props.listInfo.page" @btn-click="togglePage" />
|
||||
</div>
|
||||
</div>
|
||||
<transition enter-active-class="animated fadeIn" leave-active-class="animated fadeOut">
|
||||
<div v-show="props.listInfo.noItemLabel" :class="$style.noitem">
|
||||
<p v-text="props.listInfo.noItemLabel" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineEmits, defineExpose } from '@common/utils/vueTools'
|
||||
import { ListInfo, ListInfoItem } from '@renderer/store/songList/state'
|
||||
// import LX from '@renderer/types/lx'
|
||||
import { useRoute, useRouter } from '@common/utils/vueRouter'
|
||||
|
||||
|
||||
const props = defineProps<{
|
||||
listInfo: ListInfo
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const dom_list_ref = ref<HTMLElement | null>(null)
|
||||
|
||||
const emit = defineEmits(['toggle-page'])
|
||||
|
||||
|
||||
const togglePage = (page: number) => {
|
||||
emit('toggle-page', page)
|
||||
}
|
||||
|
||||
const toDetail = (info: ListInfoItem) => {
|
||||
router.push({
|
||||
path: '/songList/detail',
|
||||
query: {
|
||||
source: info.source,
|
||||
id: info.id,
|
||||
picUrl: info.img,
|
||||
fromName: route.name as string,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
scrollTo(top: number) {
|
||||
dom_list_ref.value?.scrollTo({
|
||||
top,
|
||||
// behavior: 'smooth',
|
||||
})
|
||||
},
|
||||
getScrollTop() {
|
||||
return dom_list_ref.value?.scrollTop ?? 0
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
.container {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.listContent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
padding: 15px 15px 0;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.item {
|
||||
max-width: 360px;
|
||||
width: 32%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
// flex-flow: column nowrap;
|
||||
// padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
transition: opacity @transition-normal;
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
.image {
|
||||
flex: none;
|
||||
width: 40%;
|
||||
display: flex;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
opacity: .9;
|
||||
aspect-ratio: 1 / 1;
|
||||
|
||||
box-shadow: 0 0 2px 0 rgba(0,0,0,.2);
|
||||
}
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.desc {
|
||||
flex: auto;
|
||||
padding: 2px 15px 2px 7px;
|
||||
overflow: hidden;
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
// height: 2.6em;
|
||||
text-align: justify;
|
||||
line-height: 1.3;
|
||||
.mixin-ellipsis-2;
|
||||
}
|
||||
}
|
||||
.songlist_info {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
gap: 15px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
.mixin-ellipsis-1;
|
||||
text-align: justify;
|
||||
line-height: 1.2;
|
||||
// text-indent: 24px;
|
||||
color: var(--color-font-label);
|
||||
svg {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
.author {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
.mixin-ellipsis-1;
|
||||
text-align: justify;
|
||||
line-height: 1.2;
|
||||
// text-indent: 24px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
.pagination {
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
// left: 50%;
|
||||
// transform: translateX(-50%);
|
||||
}
|
||||
.noitem {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// background-color: var(--color-000);
|
||||
|
||||
p {
|
||||
font-size: 24px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<base-tab :model-value="sortId" :class="$style.tab" :list="list" item-label="name" @change="handleToggle" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watch, shallowReactive } from '@common/utils/vueTools'
|
||||
import { sortList } from '@renderer/store/songList/state'
|
||||
import { useRouter, useRoute } from '@common/utils/vueRouter'
|
||||
|
||||
const props = defineProps({
|
||||
source: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
tagId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sortId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const list = shallowReactive([])
|
||||
|
||||
|
||||
const handleToggle = (id) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
source: props.source,
|
||||
tagId: props.tagId,
|
||||
sortId: id,
|
||||
},
|
||||
})
|
||||
}
|
||||
watch(() => props.source, async(source) => {
|
||||
// const source = (await getLeaderboardSetting()).source as LX.OnlineSource
|
||||
if (!source) return
|
||||
let _list = sortList[source] ?? []
|
||||
list.splice(0, list.length, ..._list)
|
||||
if (!props.sortId && list.length) handleToggle(list[0].id)
|
||||
// console.log(list)
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.tag-list {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
.label {
|
||||
.icon {
|
||||
svg{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.list {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 8px 15px;
|
||||
// background-color: var(--color-button-background);
|
||||
transition: background-color @transition-normal;
|
||||
// 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: var(--color-button-font);
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
flex: auto;
|
||||
}
|
||||
.icon {
|
||||
flex: none;
|
||||
margin-left: 7px;
|
||||
line-height: 0;
|
||||
svg {
|
||||
width: .9em;
|
||||
transition: transform .2s ease;
|
||||
transform: rotate(0);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 645px;
|
||||
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: var(--color-main-background);
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
overflow-y: auto;
|
||||
transform-origin: 0 0 0;
|
||||
max-height: 250px;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
z-index: 10;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 8px 15px;
|
||||
// color: var(--color-button-font);
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: background-color @transition-normal;
|
||||
background-color: var(--color-button-background);
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 3px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
background-color: var(--color-button-background);
|
||||
padding: 8px 10px;
|
||||
border-radius: @radius-progress-border;
|
||||
transition: background-color @transition-normal;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div :class="[$style.tagList, {[$style.active]: popupVisible}]">
|
||||
<div ref="dom_btn" :class="$style.label" @click="handleShow">
|
||||
<span>{{ tagName }}</span>
|
||||
<div :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" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.popup" :style="popupStyle" :aria-hidden="!popupVisible" @click.stop>
|
||||
<div :class="$style.list" class="scroll">
|
||||
<div :class="$style.tag" @click="handleToggleTag('')">{{ $t('default') }}</div>
|
||||
<dl v-for="tagInfo in list" :key="tagInfo.name">
|
||||
<dt :class="$style.type">{{ tagInfo.name }}</dt>
|
||||
<dd v-for="tag in tagInfo.list" :key="tag.id" :class="$style.tag" @click="handleToggleTag(tag.id)">{{ tag.name }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, shallowReactive, ref, onMounted, onBeforeUnmount, computed, reactive } from '@common/utils/vueTools'
|
||||
import { setTags, getTags } from '@renderer/store/songList/action'
|
||||
import { tags, TagInfoTypeItem } from '@renderer/store/songList/state'
|
||||
import LX from '@renderer/types/lx'
|
||||
import { useRouter, useRoute } from '@common/utils/vueRouter'
|
||||
import { useI18n } from '@renderer/plugins/i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
source: LX.OnlineSource
|
||||
tagId: string
|
||||
sortId?: string
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const t = useI18n()
|
||||
|
||||
const list = shallowReactive<TagInfoTypeItem[]>([])
|
||||
const handleToggleTag = (id: string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
source: props.source,
|
||||
tagId: id,
|
||||
sortId: props.sortId,
|
||||
},
|
||||
})
|
||||
handleHide()
|
||||
}
|
||||
watch(() => props.source, async(source) => {
|
||||
if (!source) return
|
||||
// const source = (await getLeaderboardSetting()).source as LX.OnlineSource
|
||||
let tagInfo = tags[source]
|
||||
// console.log(await getTags(source))
|
||||
if (tagInfo == null) setTags(tagInfo = await getTags(source), source)
|
||||
|
||||
list.splice(0, list.length, ...[{ name: window.i18n.t('songlist__tag_info_hot_tag'), list: [...tagInfo.hotTag] }, ...tagInfo.tags])
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
const tagName = computed(() => {
|
||||
if (!props.tagId) return t('default')
|
||||
for (const tags of list) {
|
||||
const tag = tags.list.find(t => t.id == props.tagId)
|
||||
if (tag) return tag.name
|
||||
}
|
||||
return props.tagId
|
||||
})
|
||||
|
||||
const popupStyle = reactive({
|
||||
width: '645px',
|
||||
maxHeight: '250px',
|
||||
})
|
||||
|
||||
const setTagPopupWidth = () => {
|
||||
window.setTimeout(() => {
|
||||
const dom_view = document.getElementById('view')
|
||||
popupStyle.width = dom_view!.clientWidth * 0.96 + 'px'
|
||||
popupStyle.maxHeight = dom_view!.clientHeight * 0.65 + 'px'
|
||||
}, 50)
|
||||
}
|
||||
|
||||
const dom_btn = ref<HTMLElement | null>(null)
|
||||
const popupVisible = ref(false)
|
||||
const handleShow = () => popupVisible.value = !popupVisible.value
|
||||
const handleHide = (evt?: MouseEvent) => {
|
||||
// if (e && e.target.parentNode != this.$refs.dom_popup && this.show) return this.show = false
|
||||
// console.log(this.$refs)
|
||||
if (evt && (evt.target == dom_btn.value || dom_btn.value?.contains(evt.target as HTMLElement))) return
|
||||
setTimeout(() => {
|
||||
popupVisible.value = false
|
||||
}, 50)
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
setTagPopupWidth()
|
||||
document.addEventListener('click', handleHide)
|
||||
window.addEventListener('resize', setTagPopupWidth)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleHide)
|
||||
window.removeEventListener('resize', setTagPopupWidth)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.tag-list {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
.label {
|
||||
.icon {
|
||||
svg{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
.popup {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 8px 15px;
|
||||
// background-color: var(--color-button-background);
|
||||
transition: color @transition-normal;
|
||||
// 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: var(--color-font);
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
flex: auto;
|
||||
}
|
||||
.icon {
|
||||
flex: none;
|
||||
margin-left: 7px;
|
||||
line-height: 0;
|
||||
svg {
|
||||
width: .8em;
|
||||
transition: transform .2s ease;
|
||||
transform: rotate(0);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary-font-hover);
|
||||
}
|
||||
&:active {
|
||||
color: var(--color-primary-font-active);
|
||||
}
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 645px;
|
||||
left: 8px;
|
||||
margin-top: 12px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-content-background);
|
||||
opacity: 0;
|
||||
transform: scale(.95, .8);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
max-height: 250px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, .15));
|
||||
display: flex;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 20px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-bottom: 8px solid var(--color-content-background);
|
||||
}
|
||||
}
|
||||
.list {
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
// box-shadow: 0 0 4px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.type {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 3px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
background-color: var(--color-button-background);
|
||||
padding: 8px 10px;
|
||||
border-radius: @radius-progress-border;
|
||||
transition: background-color @transition-normal;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.header">
|
||||
<div :class="$style.left">
|
||||
<tag-list :source="source" :tag-id="tagId" :sort-id="sortId" />
|
||||
<sort-tab :source="source" :tag-id="tagId" :sort-id="sortId" />
|
||||
</div>
|
||||
<base-btn :class="$style.btn" outline min @click="visibleOpenSongListModal = true">{{ $t('songlist__import_input_show_btn') }}</base-btn>
|
||||
<base-selection :model-value="source" :class="$style.select" :list="sourceList" item-key="id" item-name="name" @update:model-value="handleToggleSource" />
|
||||
</div>
|
||||
<list-view :source="source" :tag-id="tagId" :sort-id="sortId" :page="page" />
|
||||
<open-list-modal v-model="visibleOpenSongListModal" :source-list="sourceList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ref } from '@common/utils/vueTools'
|
||||
import { getSongListSetting, setSongListSetting } from '@renderer/utils/data'
|
||||
import TagList from './components/TagList.vue'
|
||||
import SortTab from './components/SortTab.vue'
|
||||
import OpenListModal from './components/OpenListModal.vue'
|
||||
import ListView from './ListView.vue'
|
||||
import { sources, listInfo, isVisibleListDetail } from '@renderer/store/songList/state'
|
||||
import { sourceNames } from '@renderer/store'
|
||||
import LX from '@renderer/types/lx'
|
||||
import { useRoute, useRouter } from '@common/utils/vueRouter'
|
||||
|
||||
const source = ref<LX.OnlineSource>('kw')
|
||||
const tagId = ref<string>('')
|
||||
const sortId = ref<string>('')
|
||||
const page = ref<number>(1)
|
||||
|
||||
|
||||
interface Query {
|
||||
source?: string
|
||||
tagId?: string
|
||||
sortId?: string
|
||||
page?: string
|
||||
}
|
||||
|
||||
const verifyQueryParams = async function(this: any, to: { query: Query, path: string}, from: any, next: (route?: { path: string; query: Query }) => void) {
|
||||
let _source = to.query.source
|
||||
let _tagId = to.query.tagId
|
||||
let _sortId = to.query.sortId
|
||||
let _page: string | undefined = to.query.page
|
||||
|
||||
if (isVisibleListDetail.value) {
|
||||
next({ path: '/songList/detail', query: {} })
|
||||
return
|
||||
} else if (_source == null) {
|
||||
if (listInfo.key) {
|
||||
_source = listInfo.source
|
||||
_tagId = listInfo.tagId
|
||||
_sortId = listInfo.sortId
|
||||
_page = listInfo.page.toString()
|
||||
} else {
|
||||
const setting = await getSongListSetting()
|
||||
_source = setting.source
|
||||
_tagId = setting.tagId
|
||||
_sortId = setting.sortId
|
||||
_page = '1'
|
||||
}
|
||||
|
||||
next({
|
||||
path: to.path,
|
||||
query: { ...to.query, source: _source, tagId: _tagId, sortId: _sortId, page: _page },
|
||||
})
|
||||
return
|
||||
}
|
||||
next()
|
||||
source.value = _source as LX.OnlineSource
|
||||
tagId.value = _tagId ?? ''
|
||||
sortId.value = _sortId ?? ''
|
||||
page.value = _page ? parseInt(_page) : 1
|
||||
setSongListSetting({ source: _source, tagId: _tagId, sortId: _sortId })
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TagList,
|
||||
SortTab,
|
||||
ListView,
|
||||
OpenListModal,
|
||||
},
|
||||
beforeRouteEnter: verifyQueryParams,
|
||||
beforeRouteUpdate: verifyQueryParams,
|
||||
setup() {
|
||||
const visibleOpenSongListModal = ref(false)
|
||||
|
||||
const sourceList = computed(() => {
|
||||
return sources.map(s => ({ id: s, name: sourceNames.value[s] }))
|
||||
})
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const handleToggleSource = (id: LX.OnlineSource) => {
|
||||
if (id == source.value) return
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
source: id,
|
||||
tagId: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
source,
|
||||
tagId,
|
||||
sortId,
|
||||
page,
|
||||
sourceList,
|
||||
handleToggleSource,
|
||||
visibleOpenSongListModal,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
position: relative;
|
||||
}
|
||||
.header {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
// padding-right: 5px;
|
||||
// box-sizing: border-box;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.left {
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
color: var(--color-font);
|
||||
transition: color @transition-fast;
|
||||
background: none !important;
|
||||
&:hover {
|
||||
color: var(--color-primary-font-hover);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.select {
|
||||
font-size: 12px;
|
||||
width: auto;
|
||||
flex: none;
|
||||
padding: 0 5px;
|
||||
|
||||
&:hover {
|
||||
:global(.icon) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:global {
|
||||
.label-content {
|
||||
background-color: transparent !important;
|
||||
transition: color @transition-fast;
|
||||
color: var(--color-font);
|
||||
// line-height: 38px;
|
||||
// height: 38px;
|
||||
border-radius: 0;
|
||||
&:hover {
|
||||
// background: none !important;
|
||||
color: var(--color-primary-font-hover);
|
||||
.icon {
|
||||
opacity: 1;
|
||||
// color: var(--color-primary-font-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
// .label {
|
||||
// color: var(--color-font) !important;
|
||||
// }
|
||||
.icon {
|
||||
svg {
|
||||
width: .8em;
|
||||
}
|
||||
// opacity: .6;
|
||||
// transition: color @transition-fast;
|
||||
// color: var(--color-font-label);
|
||||
}
|
||||
|
||||
.selection-list {
|
||||
max-height: 500px;
|
||||
box-shadow: 0 1px 4px 0 rgba(0,0,0,.2);
|
||||
li {
|
||||
// background-color: var(--color-main-background);
|
||||
text-align: center;
|
||||
line-height: 38px;
|
||||
font-size: 13px;
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user