mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
nothing
This commit is contained in:
18
src/App.vue
18
src/App.vue
@@ -1,12 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<NMessageProvider>
|
<NMessageProvider>
|
||||||
<NConfigProvider :theme-overrides="themeOverrides" style="height: 100vh">
|
<NNotificationProvider>
|
||||||
<ViewerLayout v-if="layout == 'viewer'" />
|
<NConfigProvider :theme-overrides="themeOverrides" :locale="zhCN" style="height: 100vh">
|
||||||
<ManageLayout v-else-if="layout == 'manage'" />
|
<ViewerLayout v-if="layout == 'viewer'" />
|
||||||
<template v-else>
|
<ManageLayout v-else-if="layout == 'manage'" />
|
||||||
<RouterView />
|
<template v-else>
|
||||||
</template>
|
<RouterView />
|
||||||
</NConfigProvider>
|
</template>
|
||||||
|
</NConfigProvider>
|
||||||
|
</NNotificationProvider>
|
||||||
</NMessageProvider>
|
</NMessageProvider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
import ViewerLayout from '@/views/ViewerLayout.vue'
|
import ViewerLayout from '@/views/ViewerLayout.vue'
|
||||||
import ManageLayout from '@/views/ManageLayout.vue'
|
import ManageLayout from '@/views/ManageLayout.vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { NConfigProvider, NMessageProvider } from 'naive-ui'
|
import { NConfigProvider, NMessageProvider, NNotificationProvider, zhCN } from 'naive-ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -24,14 +24,31 @@ export interface AccountInfo extends UserInfo {
|
|||||||
biliVerifyCode?: string
|
biliVerifyCode?: string
|
||||||
emailVerifyUrl?: string
|
emailVerifyUrl?: string
|
||||||
}
|
}
|
||||||
export interface SongsInfo {
|
export interface SongAuthorInfo {
|
||||||
id: string
|
|
||||||
name: string
|
name: string
|
||||||
author: string
|
id: number
|
||||||
url: string
|
}
|
||||||
cover: string
|
export enum SongFrom {
|
||||||
from: string
|
Custom,
|
||||||
language: string
|
Netease,
|
||||||
desc: string
|
FiveSing,
|
||||||
tags: string[]
|
}
|
||||||
|
export interface SongsInfo {
|
||||||
|
id: number
|
||||||
|
key: string
|
||||||
|
name: string
|
||||||
|
author: string[]
|
||||||
|
url: string
|
||||||
|
from: SongFrom
|
||||||
|
language: SongLanguage[]
|
||||||
|
description?: string
|
||||||
|
tags?: string[]
|
||||||
|
}
|
||||||
|
export enum SongLanguage {
|
||||||
|
Chinese, // 中文
|
||||||
|
English, // 英文
|
||||||
|
Japanese, // 日文
|
||||||
|
Spanish, // 西班牙文
|
||||||
|
French, // 法文
|
||||||
|
Other, //其他
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,58 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SongsInfo } from '@/api/api-models'
|
import { SongAuthorInfo, SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
||||||
import { DataTableColumns, NAvatar, NButton, NCollapseTransition, NDataTable, NInput, NList, NListItem, NSpace } from 'naive-ui'
|
import { QueryPostAPI } from '@/api/query'
|
||||||
import { onMounted, h, ref } from 'vue'
|
import { SONG_API_URL } from '@/data/constants'
|
||||||
|
import {
|
||||||
|
DataTableColumns,
|
||||||
|
FormInst,
|
||||||
|
FormRules,
|
||||||
|
NAvatar,
|
||||||
|
NButton,
|
||||||
|
NCollapseTransition,
|
||||||
|
NDataTable,
|
||||||
|
NDivider,
|
||||||
|
NEllipsis,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NModal,
|
||||||
|
NSelect,
|
||||||
|
NSpace,
|
||||||
|
NTag,
|
||||||
|
NText,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
|
import { onMounted, h, ref, watch } from 'vue'
|
||||||
import APlayer from 'vue3-aplayer'
|
import APlayer from 'vue3-aplayer'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
songs: SongsInfo[]
|
songs: SongsInfo[]
|
||||||
canEdit?: boolean
|
canEdit?: boolean
|
||||||
}>()
|
}>()
|
||||||
const songsInternal = ref<{ [id: string]: SongsInfo }>({})
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const songsInternal = ref(props.songs)
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const showModal = ref(false)
|
||||||
|
const updateSongModel = ref<SongsInfo>({} as SongsInfo)
|
||||||
|
|
||||||
const columns = ref<DataTableColumns<SongsInfo>>()
|
const columns = ref<DataTableColumns<SongsInfo>>()
|
||||||
const aplayerMusic = ref<{
|
const aplayerMusic = ref<{
|
||||||
title: string
|
title: string
|
||||||
@@ -17,91 +61,115 @@ const aplayerMusic = ref<{
|
|||||||
pic: string
|
pic: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const createColumns = (): DataTableColumns<SongsInfo> => [
|
const formRef = ref<FormInst | null>(null)
|
||||||
{
|
const updateSongRules: FormRules = {
|
||||||
title: '',
|
name: [
|
||||||
key: 'cover',
|
{
|
||||||
resizable: false,
|
required: true,
|
||||||
width: 50,
|
message: '请输入歌曲名称',
|
||||||
render(data) {
|
|
||||||
return h(NAvatar, {
|
|
||||||
src: data.cover,
|
|
||||||
imgProps: {},
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入密码',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const songSelectOption = [
|
||||||
|
{
|
||||||
|
label: '中文',
|
||||||
|
value: SongLanguage.Chinese,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '日语',
|
||||||
|
value: SongLanguage.Japanese,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '英语',
|
||||||
|
value: SongLanguage.English,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '法语',
|
||||||
|
value: SongLanguage.French,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '西语',
|
||||||
|
value: SongLanguage.Spanish,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '其他',
|
||||||
|
value: SongLanguage.Other,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const tagsSelectOption = ref<{ label: string; value: string }[]>([])
|
||||||
|
|
||||||
|
const createColumns = (): DataTableColumns<SongsInfo> => [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
resizable: true,
|
resizable: true,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
|
sorter: 'default',
|
||||||
render(data) {
|
render(data) {
|
||||||
return props.canEdit
|
return h('span', data.name)
|
||||||
? h(NInput, {
|
|
||||||
value: data.name,
|
|
||||||
onUpdateValue(v) {
|
|
||||||
songsInternal.value[data.id].name = v
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: h('span', data.name)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '作者',
|
title: '作者',
|
||||||
key: 'author',
|
key: 'artist',
|
||||||
resizable: true,
|
resizable: true,
|
||||||
render(data) {
|
render(data) {
|
||||||
return props.canEdit
|
return h(NSpace, { size: 5 }, () => data.author.map((a) => h(NTag, { bordered: false, size: 'small', type: 'info' }, () => a)))
|
||||||
? h(NInput, {
|
|
||||||
value: data.author,
|
|
||||||
onUpdateValue(v) {
|
|
||||||
songsInternal.value[data.id].author = v
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: h('span', data.author)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '描述',
|
title: '描述',
|
||||||
key: 'description',
|
key: 'description',
|
||||||
resizable: true,
|
resizable: true,
|
||||||
minWidth: 75,
|
|
||||||
render(data) {
|
render(data) {
|
||||||
return props.canEdit
|
return h(NEllipsis, () => data.description)
|
||||||
? h(NInput, {
|
},
|
||||||
value: data.desc,
|
},
|
||||||
onUpdateValue(v) {
|
{
|
||||||
songsInternal.value[data.id].desc = v
|
title: '标签',
|
||||||
},
|
key: 'tags',
|
||||||
})
|
resizable: true,
|
||||||
: h('span', data.desc)
|
render(data) {
|
||||||
|
return (data.tags?.length ?? 0) > 0 ? h(NSpace, { size: 5 }, () => data.tags?.map((a) => h(NTag, { bordered: false, size: 'small' }, () => a))) : null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'manage',
|
key: 'manage',
|
||||||
minWidth: 75,
|
disabled: () => !props.canEdit,
|
||||||
|
width: 200,
|
||||||
render(data) {
|
render(data) {
|
||||||
return h(NSpace, [
|
return h(NSpace, () => [
|
||||||
h(
|
h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
onClick: () => console.log(1),
|
size:'small',
|
||||||
|
onClick: () => {
|
||||||
|
updateSongModel.value = JSON.parse(JSON.stringify(data))
|
||||||
|
showModal.value = true
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => '保存',
|
default: () => '修改',
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
size:'small',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
aplayerMusic.value = {
|
aplayerMusic.value = {
|
||||||
title: data.name,
|
title: data.name,
|
||||||
artist: data.author,
|
artist: data.author.join('/') ?? '',
|
||||||
src: data.url,
|
src: data.url,
|
||||||
pic: data.cover,
|
pic: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -109,22 +177,85 @@ const createColumns = (): DataTableColumns<SongsInfo> => [
|
|||||||
default: () => '播放',
|
default: () => '播放',
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
size:'small',
|
||||||
|
onClick: () => {
|
||||||
|
aplayerMusic.value = {
|
||||||
|
title: data.name,
|
||||||
|
artist: data.author.join('/') ?? '',
|
||||||
|
src: data.url,
|
||||||
|
pic: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => '删除',
|
||||||
|
}
|
||||||
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
onMounted(() => {
|
function renderCell(value: string | number) {
|
||||||
props.songs.forEach((song) => {
|
if (!value) {
|
||||||
songsInternal.value[song.id] = song
|
return h(NText, { depth: 3 }, { default: () => '未填写' })
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSong() {
|
||||||
|
await QueryPostAPI<SongsInfo>(SONG_API_URL + 'update', {
|
||||||
|
key: updateSongModel.value.key,
|
||||||
|
song: updateSongModel.value,
|
||||||
|
}).then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
const index = songsInternal.value.findIndex((s) => s.key == data.data.key)
|
||||||
|
songsInternal.value.splice(index, 1, data.data)
|
||||||
|
message.success('已更新歌曲信息')
|
||||||
|
} else {
|
||||||
|
message.error('未能更新歌曲信息: ' + data.message)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
songsInternal.value = props.songs
|
||||||
columns.value = createColumns()
|
columns.value = createColumns()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
歌单 {{ songs.length }}
|
歌单 {{ songsInternal.length }}
|
||||||
<Transition>
|
<Transition>
|
||||||
<APlayer v-if="aplayerMusic" :music="aplayerMusic" />
|
<APlayer v-if="aplayerMusic" :music="aplayerMusic" />
|
||||||
</Transition>
|
</Transition>
|
||||||
<NDataTable :columns="columns" :data="songs"> </NDataTable>
|
<NDataTable :columns="columns" :data="songsInternal"> </NDataTable>
|
||||||
|
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
||||||
|
<template #header> 修改信息 </template>
|
||||||
|
<NForm ref="formRef" :rules="updateSongRules" :model="updateSongModel" :render-cell="renderCell">
|
||||||
|
<NFormItem path="name" label="名称">
|
||||||
|
<NInput v-model:value="updateSongModel.name" autosize style="min-width: 200px" placeholder="就是歌曲名称" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="author" label="作者">
|
||||||
|
<NSelect v-model:value="updateSongModel.author" filterable multiple tag placeholder="输入,按回车确认" :show-arrow="false" :show="false" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="description" label="备注">
|
||||||
|
<NInput v-model:value="updateSongModel.description" placeholder="可选" :maxlength="250" show-count autosize style="min-width: 300px" clearable />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="language" label="语言">
|
||||||
|
<NSelect v-model:value="updateSongModel.language" multiple :options="songSelectOption" placeholder="可选" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="tags" label="标签">
|
||||||
|
<NSelect v-model:value="updateSongModel.tags" filterable multiple tag placeholder="可选,按回车确认" :show-arrow="false" :show="false" :options="tagsSelectOption" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="url" label="链接">
|
||||||
|
<NInput v-model:value="updateSongModel.url" placeholder="可选, 后缀为mp3、wav、ogg时将会尝试播放, 否则会在新页面打开" :disabled="updateSongModel.from != SongFrom.Custom" />
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
<NDivider style="margin: 10px" />
|
||||||
|
<NButton @click="updateSong"> 更新 </NButton>
|
||||||
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ export const TURNSTILE_KEY = '0x4AAAAAAAETUSAKbds019h0'
|
|||||||
export const USER_API_URL = `${BASE_API}user/`
|
export const USER_API_URL = `${BASE_API}user/`
|
||||||
export const ACCOUNT_API_URL = `${BASE_API}account/`
|
export const ACCOUNT_API_URL = `${BASE_API}account/`
|
||||||
export const BILI_API_URL = `${BASE_API}bili/`
|
export const BILI_API_URL = `${BASE_API}bili/`
|
||||||
|
export const SONG_API_URL = `${BASE_API}song-list/`
|
||||||
|
|||||||
@@ -1,11 +1,241 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SongsInfo } from '@/api/api-models'
|
import { useAccount } from '@/api/account'
|
||||||
|
import { SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
||||||
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import SongList from '@/components/SongList.vue'
|
import SongList from '@/components/SongList.vue'
|
||||||
import { ref } from 'vue'
|
import { SONG_API_URL } from '@/data/constants'
|
||||||
|
import { ca } from 'date-fns/locale'
|
||||||
|
import { FormInst, FormRules, NButton, NDivider, NForm, NFormItem, NInput, NInputGroup, NInputGroupLabel, NModal, NSelect, NSpace, NSpin, NTabPane, NTabs, NTag, NTransfer, useMessage } from 'naive-ui'
|
||||||
|
import { Option } from 'naive-ui/es/transfer/src/interface'
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const accountInfo = useAccount()
|
||||||
|
|
||||||
|
const showModal = ref(false)
|
||||||
|
const neteaseIdInput = ref()
|
||||||
|
const isModalLoading = ref(false)
|
||||||
|
|
||||||
|
const neteaseSongListId = computed(() => {
|
||||||
|
try {
|
||||||
|
const url = new URL(neteaseIdInput.value)
|
||||||
|
console.log(url)
|
||||||
|
if (url.host == 'music.163.com') {
|
||||||
|
let regex = /id=(\d+)/
|
||||||
|
|
||||||
|
// 使用exec方法在链接中查找匹配项
|
||||||
|
let match = regex.exec(neteaseIdInput.value)
|
||||||
|
|
||||||
|
// 如果找到了匹配项,那么match[1]就是分组1的值,也就是id的值
|
||||||
|
if (match) {
|
||||||
|
return Number(match[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
try {
|
||||||
|
return Number(neteaseIdInput.value)
|
||||||
|
} catch {}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
const songs = ref<SongsInfo[]>([])
|
const songs = ref<SongsInfo[]>([])
|
||||||
|
const neteaseSongs = ref<SongsInfo[]>([])
|
||||||
|
const neteaseSongsOptions = ref<Option[]>([])
|
||||||
|
const selectedNeteaseSongs = ref<string[]>([])
|
||||||
|
|
||||||
|
const formRef = ref<FormInst | null>(null)
|
||||||
|
const addSongModel = ref<SongsInfo>({} as SongsInfo)
|
||||||
|
const addSongRules: FormRules = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入歌曲名称',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入密码',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const songSelectOption = [
|
||||||
|
{
|
||||||
|
label: '中文',
|
||||||
|
value: SongLanguage.Chinese,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '日语',
|
||||||
|
value: SongLanguage.Japanese,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '英语',
|
||||||
|
value: SongLanguage.English,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '法语',
|
||||||
|
value: SongLanguage.French,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '西语',
|
||||||
|
value: SongLanguage.Spanish,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '其他',
|
||||||
|
value: SongLanguage.Other,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async function addCustomSong() {
|
||||||
|
isModalLoading.value = true
|
||||||
|
formRef.value
|
||||||
|
?.validate()
|
||||||
|
.then(async () => {
|
||||||
|
await addSongs([addSongModel.value], SongFrom.Custom)
|
||||||
|
.then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
if (data.data.length == 1) {
|
||||||
|
message.success('成功添加歌曲: ' + addSongModel.value.name)
|
||||||
|
songs.value.push(data.data[0])
|
||||||
|
addSongModel.value = {} as SongsInfo
|
||||||
|
} else {
|
||||||
|
message.error('未能添加歌曲, 已存在相同名称的曲目')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error('添加失败: ' + data.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
message.error('添加失败')
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isModalLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function addNeteaseSongs() {
|
||||||
|
isModalLoading.value = true
|
||||||
|
const selected = neteaseSongs.value.filter((s) => selectedNeteaseSongs.value.find((select) => s.key == select))
|
||||||
|
await addSongs(selected, SongFrom.Netease)
|
||||||
|
.then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
message.success(`已添加 ${data.data.length} 首歌曲`)
|
||||||
|
songs.value.push(...data.data)
|
||||||
|
neteaseSongsOptions.value = neteaseSongs.value.map((s) => ({
|
||||||
|
label: `${s.name} - ${s.author.join('/')}`,
|
||||||
|
value: s.key,
|
||||||
|
disabled: songs.value.findIndex((exist) => exist.id == s.id) > -1 || data.data.findIndex((add) => add.id == s.id) > -1,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
message.error('添加失败: ' + data.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
message.error('添加失败')
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isModalLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function addSongs(songsShoudAdd: SongsInfo[], from: SongFrom) {
|
||||||
|
return QueryPostAPI<SongsInfo[]>(
|
||||||
|
SONG_API_URL + 'add',
|
||||||
|
songsShoudAdd.map((s) => ({
|
||||||
|
Name: s.name,
|
||||||
|
Id: from == SongFrom.Custom ? -1 : s.id,
|
||||||
|
From: from,
|
||||||
|
Author: s.author,
|
||||||
|
Url: s.url,
|
||||||
|
Description: s.description,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getNeteaseSongList() {
|
||||||
|
isModalLoading.value = true
|
||||||
|
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get-netease-list', {
|
||||||
|
id: neteaseSongListId.value,
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
neteaseSongs.value = data.data
|
||||||
|
neteaseSongsOptions.value = data.data.map((s) => ({
|
||||||
|
label: `${s.name} - ${s.author.join('/')}`,
|
||||||
|
value: s.key,
|
||||||
|
disabled: songs.value.findIndex((exist) => exist.id == s.id) > -1,
|
||||||
|
}))
|
||||||
|
message.success(`成功获取歌曲信息, 共 ${data.data.length} 条, 歌单中已存在 ${neteaseSongsOptions.value.filter((s) => s.disabled).length} 首`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
message.error(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isModalLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function getSongs() {
|
||||||
|
await QueryGetAPI<any>(SONG_API_URL + 'get', {
|
||||||
|
uId: accountInfo.value?.biliId,
|
||||||
|
}).then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
songs.value = data.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getSongs()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<NButton @click="showModal = true"> 添加歌曲 </NButton>
|
||||||
|
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
||||||
|
<template #header> 添加歌曲 </template>
|
||||||
|
<NSpin :show="isModalLoading">
|
||||||
|
<NTabs default-value="custom" animated>
|
||||||
|
<NTabPane name="custom" tab="手动录入">
|
||||||
|
<NForm ref="formRef" :rules="addSongRules" :model="addSongModel">
|
||||||
|
<NFormItem path="name" label="名称">
|
||||||
|
<NInput v-model:value="addSongModel.name" autosize style="min-width: 200px" placeholder="就是歌曲名称" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="author" label="作者">
|
||||||
|
<NSelect v-model:value="addSongModel.author" filterable multiple tag placeholder="输入,按回车确认" :show-arrow="false" :show="false" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="description" label="备注">
|
||||||
|
<NInput v-model:value="addSongModel.description" placeholder="可选" :maxlength="250" show-count autosize style="min-width: 300px" clearable />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="language" label="语言">
|
||||||
|
<NSelect v-model:value="addSongModel.language" multiple :options="songSelectOption" placeholder="可选" />
|
||||||
|
</NFormItem>
|
||||||
|
<NFormItem path="url" label="链接">
|
||||||
|
<NInput v-model:value="addSongModel.url" placeholder="可选, 后缀为mp3、wav、ogg时将会尝试播放, 否则会在新页面打开" />
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
<NButton type="primary" @click="addCustomSong"> 添加 </NButton>
|
||||||
|
</NTabPane>
|
||||||
|
<NTabPane name="netease" tab="从网易云歌单导入">
|
||||||
|
<NInput clearable style="width: 100%" autosize :status="neteaseSongListId ? 'success' : 'error'" v-model:value="neteaseIdInput" placeholder="直接输入歌单Id或者网页链接">
|
||||||
|
<template #suffix>
|
||||||
|
<NTag v-if="neteaseSongListId" type="success" size="small"> 歌单Id: {{ neteaseSongListId }} </NTag>
|
||||||
|
</template>
|
||||||
|
</NInput>
|
||||||
|
<NDivider style="margin: 10px" />
|
||||||
|
<NButton type="primary" @click="getNeteaseSongList"> 获取 </NButton>
|
||||||
|
<template v-if="neteaseSongsOptions.length > 0">
|
||||||
|
<NDivider style="margin: 10px" />
|
||||||
|
<NTransfer style="height: 500px" ref="transfer" v-model:value="selectedNeteaseSongs" :options="neteaseSongsOptions" source-filterable />
|
||||||
|
<NDivider style="margin: 10px" />
|
||||||
|
<NButton type="primary" @click="addNeteaseSongs"> 添加到歌单 | {{ selectedNeteaseSongs.length }} 首 </NButton>
|
||||||
|
</template>
|
||||||
|
</NTabPane>
|
||||||
|
<NTabPane name="5sing" tab="从5sing搜索"> </NTabPane>
|
||||||
|
</NTabs>
|
||||||
|
</NSpin>
|
||||||
|
</NModal>
|
||||||
<SongList :songs="songs" />
|
<SongList :songs="songs" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,55 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SongsInfo } from '@/api/api-models'
|
import { SongsInfo } from '@/api/api-models'
|
||||||
import { QueryGetPaginationAPI } from '@/api/query'
|
import { QueryGetAPI, QueryGetPaginationAPI } from '@/api/query'
|
||||||
import SongList from '@/components/SongList.vue'
|
import SongList from '@/components/SongList.vue'
|
||||||
import { USER_API_URL } from '@/data/constants'
|
import { SONG_API_URL, USER_API_URL } from '@/data/constants'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useRouteParams } from '@vueuse/router'
|
import { useRouteParams } from '@vueuse/router'
|
||||||
|
import { useAccount } from '@/api/account'
|
||||||
|
|
||||||
|
const accountInfo = useAccount()
|
||||||
const songs = ref<SongsInfo[]>()
|
const songs = ref<SongsInfo[]>()
|
||||||
const uId = useRouteParams('id', '-1', { transform: Number })
|
const uId = useRouteParams('id', '-1', { transform: Number })
|
||||||
|
|
||||||
async function RequestData() {
|
async function getSongs() {
|
||||||
songs.value = [
|
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get', {
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: 'test',
|
|
||||||
author: '雪路',
|
|
||||||
url: 'https://music.163.com/song/media/outer/url?id=1995844771.mp3',
|
|
||||||
cover: 'https://ukamnads.icu/file/components.png',
|
|
||||||
from: '网易云',
|
|
||||||
language: '中文',
|
|
||||||
desc: 'xuelu',
|
|
||||||
tags: ['hao'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: 'test2',
|
|
||||||
author: '雪路2',
|
|
||||||
url: 'https://music.163.com/song/media/outer/url?id=1995844771.mp3',
|
|
||||||
cover: 'https://ukamnads.icu/file/components.png',
|
|
||||||
from: '网易云2',
|
|
||||||
language: '中文2',
|
|
||||||
desc: 'xuelu',
|
|
||||||
tags: ['hao'],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
await QueryGetPaginationAPI<SongsInfo[]>(`${USER_API_URL}info`, {
|
|
||||||
uId: uId.value,
|
uId: uId.value,
|
||||||
|
}).then((data) => {
|
||||||
|
if (data.code == 200) {
|
||||||
|
songs.value = data.data
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then((result) => {
|
|
||||||
if (result.code == 200) {
|
|
||||||
songs.value = result.data.datas
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await RequestData()
|
await getSongs()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user