mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
update
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { SongAuthorInfo, SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
||||
import { QueryPostAPI } from '@/api/query'
|
||||
import { SongAuthorInfo, SongFrom, SongLanguage, SongsInfo, UserInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { SONG_API_URL } from '@/data/constants'
|
||||
import { refDebounced, useDebounceFn } from '@vueuse/core'
|
||||
import { List } from 'linqts'
|
||||
import {
|
||||
DataTableBaseColumn,
|
||||
DataTableColumns,
|
||||
FormInst,
|
||||
FormRules,
|
||||
@@ -16,48 +18,54 @@ import {
|
||||
NEllipsis,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NIcon,
|
||||
NInput,
|
||||
NList,
|
||||
NListItem,
|
||||
NModal,
|
||||
NPopconfirm,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NTag,
|
||||
NText,
|
||||
NTooltip,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { FilterOptionValue } from 'naive-ui/es/data-table/src/interface'
|
||||
import { nextTick } from 'process'
|
||||
import { onMounted, h, ref, watch, computed } from 'vue'
|
||||
import { onMounted, h, ref, watch, computed, reactive } from 'vue'
|
||||
import APlayer from 'vue3-aplayer'
|
||||
import { NotepadEdit20Filled, Delete24Filled, Play24Filled, SquareArrowForward24Filled } from '@vicons/fluent'
|
||||
import NeteaseIcon from '@/svgs/netease.svg'
|
||||
import FiveSingIcon from '@/svgs/fivesing.svg'
|
||||
|
||||
const props = defineProps<{
|
||||
songs: SongsInfo[]
|
||||
canEdit?: boolean
|
||||
isSelf: boolean
|
||||
}>()
|
||||
watch(
|
||||
() => props.songs,
|
||||
(newV) => {
|
||||
let map = new Map()
|
||||
newV.forEach((s) => {
|
||||
s.tags?.forEach((t) => map.set(t, t))
|
||||
})
|
||||
map.forEach((tag) => {
|
||||
tagsSelectOption.value.push({
|
||||
label: tag,
|
||||
value: tag,
|
||||
})
|
||||
})
|
||||
songsInternal.value = newV
|
||||
setTimeout(() => {
|
||||
columns.value = createColumns()
|
||||
}, 1)
|
||||
}
|
||||
)
|
||||
const songsInternal = ref(props.songs)
|
||||
const songsComputed = computed(() => {
|
||||
if (debouncedInput.value) {
|
||||
//忽略大小写比较
|
||||
return songsInternal.value.filter((s) => s.name.toLowerCase().includes(debouncedInput.value.toLowerCase()))
|
||||
}
|
||||
return songsInternal.value
|
||||
})
|
||||
const message = useMessage()
|
||||
|
||||
const showModal = ref(false)
|
||||
const updateSongModel = ref<SongsInfo>({} as SongsInfo)
|
||||
const searchMusicKeyword = ref()
|
||||
const debouncedInput = refDebounced(searchMusicKeyword, 500)
|
||||
|
||||
const columns = ref<DataTableColumns<SongsInfo>>()
|
||||
const aplayerMusic = ref<{
|
||||
title: string
|
||||
artist: string
|
||||
@@ -106,19 +114,46 @@ const songSelectOption = [
|
||||
value: SongLanguage.Other,
|
||||
},
|
||||
]
|
||||
const tagsSelectOption = ref<{ label: string; value: string }[]>([])
|
||||
const tags = ref<string[]>([])
|
||||
const tagsOptions = computed(() => {
|
||||
return tags.value.map((t) => ({
|
||||
label: t,
|
||||
value: t,
|
||||
}))
|
||||
const tagsSelectOption = computed(() => {
|
||||
return new List(songsInternal.value)
|
||||
.SelectMany((s) => new List(s?.tags))
|
||||
.Distinct()
|
||||
.ToArray()
|
||||
.map((t) => ({
|
||||
label: t,
|
||||
value: t,
|
||||
}))
|
||||
})
|
||||
const authorsOptions = computed(() => {
|
||||
return new List(songsInternal.value)
|
||||
.SelectMany((s) => new List(s?.author))
|
||||
.Distinct()
|
||||
.ToArray()
|
||||
.map((t) => ({
|
||||
label: t,
|
||||
value: t,
|
||||
}))
|
||||
})
|
||||
|
||||
const columns = ref<DataTableColumns<SongsInfo>>()
|
||||
const authorColumn = ref<DataTableBaseColumn<SongsInfo>>({
|
||||
title: '作者',
|
||||
key: 'artist',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
filter(value, row) {
|
||||
return (row.author?.findIndex((t) => t == value.toString()) ?? -1) > -1
|
||||
},
|
||||
filterOptions: authorsOptions.value,
|
||||
render(data) {
|
||||
return h(NSpace, { size: 5 }, () => data.author.map((a) => h(NButton, { size: 'tiny', type: 'info', secondary: true, onClick: () => (authorColumn.value.filterOptionValue = a) }, () => a)))
|
||||
},
|
||||
})
|
||||
|
||||
function createColumns(): DataTableColumns<SongsInfo> {
|
||||
authorColumn.value.filterOptions = authorsOptions.value
|
||||
return [
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
resizable: true,
|
||||
minWidth: 100,
|
||||
@@ -127,21 +162,18 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
||||
render(data) {
|
||||
return h(NSpace, { size: 5 }, () => [h(NText, () => data.name), h(NText, { depth: '3' }, () => data.translateName)])
|
||||
},
|
||||
title: '曲名',
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
key: 'artist',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
render(data) {
|
||||
return h(NSpace, { size: 5 }, () => data.author.map((a) => h(NTag, { bordered: false, size: 'small', type: 'info' }, () => a)))
|
||||
},
|
||||
},
|
||||
authorColumn.value,
|
||||
{
|
||||
title: '语言',
|
||||
key: 'language',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
filterOptions: songSelectOption,
|
||||
filter(value, row) {
|
||||
return (row.language?.findIndex((t) => t == (value.toString() as unknown as SongLanguage)) ?? -1) > -1
|
||||
},
|
||||
render(data) {
|
||||
return (data.language?.length ?? 0) > 0
|
||||
? h(NSpace, { size: 5 }, () => data.language?.map((a) => h(NTag, { bordered: false, size: 'small' }, () => songSelectOption.find((s) => s.value == a)?.label)))
|
||||
@@ -163,7 +195,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
||||
filter(value, row) {
|
||||
return (row.tags?.findIndex((t) => t == value.toString()) ?? -1) > -1
|
||||
},
|
||||
filterOptions: tagsOptions.value,
|
||||
filterOptions: tagsSelectOption.value,
|
||||
render(data) {
|
||||
return (data.tags?.length ?? 0) > 0 ? h(NSpace, { size: 5 }, () => data.tags?.map((a) => h(NTag, { bordered: false, size: 'small' }, () => a))) : null
|
||||
},
|
||||
@@ -172,64 +204,89 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
||||
title: '操作',
|
||||
key: 'manage',
|
||||
disabled: () => !props.canEdit,
|
||||
width: 200,
|
||||
width: props.isSelf ? 170 : 100,
|
||||
render(data) {
|
||||
return h(
|
||||
NSpace,
|
||||
{
|
||||
justify: 'space-around',
|
||||
justify: 'end',
|
||||
size: 10
|
||||
},
|
||||
() => [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
updateSongModel.value = JSON.parse(JSON.stringify(data))
|
||||
showModal.value = true
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => '修改',
|
||||
}
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
aplayerMusic.value = {
|
||||
title: data.name,
|
||||
artist: data.author.join('/') ?? '',
|
||||
src: data.url,
|
||||
pic: '',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => '播放',
|
||||
}
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: 'error',
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
aplayerMusic.value = {
|
||||
title: data.name,
|
||||
artist: data.author.join('/') ?? '',
|
||||
src: data.url,
|
||||
pic: '',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => '删除',
|
||||
}
|
||||
),
|
||||
GetPlayButton(data),
|
||||
data.url
|
||||
? h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: 'primary',
|
||||
size: 'small',
|
||||
circle: true,
|
||||
onClick: () => {
|
||||
aplayerMusic.value = {
|
||||
title: data.name,
|
||||
artist: data.author.join('/') ?? '',
|
||||
src: data.url,
|
||||
pic: '',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: Play24Filled }),
|
||||
}
|
||||
),
|
||||
default: () => '试听',
|
||||
})
|
||||
: null,
|
||||
props.isSelf
|
||||
? [
|
||||
h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
circle: true,
|
||||
secondary: true,
|
||||
onClick: () => {
|
||||
updateSongModel.value = JSON.parse(JSON.stringify(data))
|
||||
showModal.value = true
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: NotepadEdit20Filled }),
|
||||
}
|
||||
),
|
||||
default: () => '修改',
|
||||
}),
|
||||
h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => delSong(data),
|
||||
},
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: 'error',
|
||||
size: 'small',
|
||||
circle: true,
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: Delete24Filled }),
|
||||
}
|
||||
),
|
||||
default: () => '确认删除该歌曲?',
|
||||
}
|
||||
),
|
||||
default: () => '删除',
|
||||
}),
|
||||
]
|
||||
: null,
|
||||
]
|
||||
)
|
||||
},
|
||||
@@ -239,34 +296,67 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
||||
function GetPlayButton(song: SongsInfo) {
|
||||
switch (song.from) {
|
||||
case SongFrom.FiveSing: {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
color: '#00BBB3',
|
||||
onClick: () => {
|
||||
window.open(`http://5sing.kugou.com/bz/${song.id}.html`)
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => '在5sing打开',
|
||||
}
|
||||
)
|
||||
return h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
color: '#00BBB3',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
window.open(`http://5sing.kugou.com/bz/${song.id}.html`)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(FiveSingIcon, { class: 'svg-icon fivesing' }),
|
||||
}
|
||||
)
|
||||
),
|
||||
default: () => '在5sing打开',
|
||||
})
|
||||
}
|
||||
case SongFrom.Netease:
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
color: '#C20C0C',
|
||||
onClick: () => {
|
||||
window.open(`https://music.163.com/#/song?id=${song.id}`)
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => '在网易云打开',
|
||||
}
|
||||
)
|
||||
return h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
color: '#C20C0C',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
window.open(`https://music.163.com/#/song?id=${song.id}`)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(NeteaseIcon, { class: 'svg-icon netease' }),
|
||||
}
|
||||
),
|
||||
default: () => '在网易云打开',
|
||||
})
|
||||
case SongFrom.Custom:
|
||||
return song.url
|
||||
? h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
color: '#6b95bd',
|
||||
ghost: true,
|
||||
onClick: () => {
|
||||
window.open(song.url)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: SquareArrowForward24Filled }),
|
||||
}
|
||||
),
|
||||
default: () => '打开链接',
|
||||
})
|
||||
: null
|
||||
}
|
||||
}
|
||||
function renderCell(value: string | number) {
|
||||
@@ -290,13 +380,21 @@ async function updateSong() {
|
||||
}
|
||||
})
|
||||
}
|
||||
async function delSong(song: SongsInfo) {
|
||||
await QueryGetAPI<SongsInfo>(SONG_API_URL + 'del', {
|
||||
key: song.key,
|
||||
}).then((data) => {
|
||||
if (data.code == 200) {
|
||||
songsInternal.value = songsInternal.value.filter((s) => s.key != song.key)
|
||||
message.success('已删除歌曲')
|
||||
} else {
|
||||
message.error('未能删除歌曲: ' + data.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
songsInternal.value = props.songs
|
||||
tags.value = new List(songsInternal.value)
|
||||
.SelectMany((s) => new List(s?.tags))
|
||||
.Distinct()
|
||||
.ToArray()
|
||||
setTimeout(() => {
|
||||
columns.value = createColumns()
|
||||
}, 1)
|
||||
@@ -304,14 +402,22 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
歌单 {{ songsInternal.length }}
|
||||
<NCard embedded>
|
||||
<NButton> </NButton>
|
||||
<NCard embedded size="small">
|
||||
<NInput placeholder="搜索歌曲" v-model:value="searchMusicKeyword" size="small" style="max-width: 150px" />
|
||||
</NCard>
|
||||
<NDivider style="margin: 5px 0 5px 0"> 共 {{ songsInternal.length }} 首 </NDivider>
|
||||
<Transition>
|
||||
<APlayer v-if="aplayerMusic" :music="aplayerMusic" autoplay />
|
||||
<div v-if="aplayerMusic">
|
||||
<APlayer :music="aplayerMusic" autoplay />
|
||||
<NDivider style="margin: 15px 0 15px 0" />
|
||||
</div>
|
||||
</Transition>
|
||||
<NDataTable size="small" :columns="columns" :data="songsInternal"> </NDataTable>
|
||||
<NDataTable
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data="songsComputed"
|
||||
:pagination="{ itemCount: songsInternal.length, defaultPageSize: 25, pageSizes: [25, 50, 200], size: 'small', showSizePicker: true }"
|
||||
/>
|
||||
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
||||
<template #header> 修改信息 </template>
|
||||
<NForm ref="formRef" :rules="updateSongRules" :model="updateSongModel" :render-cell="renderCell">
|
||||
@@ -338,3 +444,12 @@ onMounted(() => {
|
||||
<NButton @click="updateSong" type="success"> 更新 </NButton>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.netease path:nth-child(2) {
|
||||
fill: #c20c0c;
|
||||
}
|
||||
.fivesing:first-child {
|
||||
fill: #00bbb3;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user