mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
add song request from web
This commit is contained in:
@@ -41,6 +41,7 @@ export interface AccountInfo extends UserInfo {
|
||||
|
||||
eventFetcherOnline: boolean
|
||||
eventFetcherStatus: string
|
||||
canRequestSong: boolean
|
||||
|
||||
nextSendEmailTime?: number
|
||||
}
|
||||
@@ -116,6 +117,7 @@ export interface SongsInfo {
|
||||
tags?: string[]
|
||||
createTime: number
|
||||
updateTime: number
|
||||
paidSong: boolean
|
||||
}
|
||||
export enum SongLanguage {
|
||||
Chinese, // 中文
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
NAvatar,
|
||||
NButton,
|
||||
NCard,
|
||||
NCheckbox,
|
||||
NCollapseTransition,
|
||||
NDataTable,
|
||||
NDivider,
|
||||
@@ -31,9 +32,9 @@ import {
|
||||
NTooltip,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { onMounted, h, ref, watch, computed, reactive } from 'vue'
|
||||
import { onMounted, h, ref, watch, computed, reactive, VNodeChild } from 'vue'
|
||||
import APlayer from 'vue3-aplayer'
|
||||
import { NotepadEdit20Filled, Delete24Filled, Play24Filled, SquareArrowForward24Filled } from '@vicons/fluent'
|
||||
import { NotepadEdit20Filled, Delete24Filled, Play24Filled, SquareArrowForward24Filled, Info24Filled } from '@vicons/fluent'
|
||||
import NeteaseIcon from '@/svgs/netease.svg'
|
||||
import FiveSingIcon from '@/svgs/fivesing.svg'
|
||||
|
||||
@@ -41,6 +42,7 @@ const props = defineProps<{
|
||||
songs: SongsInfo[]
|
||||
canEdit?: boolean
|
||||
isSelf: boolean
|
||||
extraButtom?: (song: SongsInfo) => VNodeChild[]
|
||||
}>()
|
||||
watch(
|
||||
() => props.songs,
|
||||
@@ -212,7 +214,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
||||
title: '操作',
|
||||
key: 'manage',
|
||||
disabled: () => !props.canEdit,
|
||||
width: props.isSelf ? 170 : 100,
|
||||
width: 170,
|
||||
render(data) {
|
||||
return h(
|
||||
NSpace,
|
||||
@@ -289,6 +291,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
||||
}),
|
||||
]
|
||||
: null,
|
||||
props.extraButtom?.(data),
|
||||
]
|
||||
)
|
||||
},
|
||||
@@ -528,6 +531,17 @@ onMounted(() => {
|
||||
<NFormItem path="tags" label="标签">
|
||||
<NSelect v-model:value="updateSongModel.tags" filterable multiple clearable tag placeholder="可选,按回车确认" :options="tagsSelectOption" />
|
||||
</NFormItem>
|
||||
<NFormItem path="paidSong" label="付费歌曲">
|
||||
<NCheckbox v-model:checked="updateSongModel.paidSong">
|
||||
是否付费歌曲
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NIcon :component="Info24Filled" />
|
||||
</template>
|
||||
用于区分是否可以从网页进行点歌
|
||||
</NTooltip>
|
||||
</NCheckbox>
|
||||
</NFormItem>
|
||||
<NFormItem path="url" label="链接">
|
||||
<NInput v-model:value="updateSongModel.url" placeholder="可选, 后缀为mp3、wav、ogg时将会尝试播放, 否则会在新页面打开" :disabled="updateSongModel.from != SongFrom.Custom" />
|
||||
</NFormItem>
|
||||
|
||||
@@ -219,7 +219,9 @@ onMounted(async () => {
|
||||
</NLayout>
|
||||
</NLayout>
|
||||
<NModal v-model:show="registerAndLoginModalVisiable" style="width: 500px; max-width: 90vw">
|
||||
<RegisterAndLogin />
|
||||
<div>
|
||||
<RegisterAndLogin />
|
||||
</div>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,13 +4,16 @@ import { SongFrom, SongLanguage, SongsInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import SongList from '@/components/SongList.vue'
|
||||
import { FETCH_API, SONG_API_URL } from '@/data/constants'
|
||||
import { Info24Filled } from '@vicons/fluent'
|
||||
import {
|
||||
FormInst,
|
||||
FormRules,
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NDivider,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NIcon,
|
||||
NInput,
|
||||
NModal,
|
||||
NPagination,
|
||||
@@ -21,6 +24,7 @@ import {
|
||||
NTable,
|
||||
NTabs,
|
||||
NTag,
|
||||
NTooltip,
|
||||
NTransfer,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
@@ -218,7 +222,7 @@ async function getNeteaseSongList() {
|
||||
if (data.code == 200) {
|
||||
neteaseSongs.value = data.data
|
||||
neteaseSongsOptions.value = data.data.map((s) => ({
|
||||
label: `${s.name} - ${s.author.join('/')}`,
|
||||
label: `${s.name} - ${!s.author ? '未知' : s.author.join('/')}`,
|
||||
value: s.key,
|
||||
disabled: songs.value.findIndex((exist) => exist.id == s.id) > -1,
|
||||
}))
|
||||
@@ -363,6 +367,17 @@ onMounted(async () => {
|
||||
<NFormItem path="url" label="链接">
|
||||
<NInput v-model:value="addSongModel.url" placeholder="可选, 后缀为mp3、wav、ogg时将会尝试播放, 否则会在新页面打开" />
|
||||
</NFormItem>
|
||||
<NFormItem path="paidSong" label="付费歌曲">
|
||||
<NCheckbox v-model:checked="addSongModel.paidSong">
|
||||
是否付费歌曲
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NIcon :component="Info24Filled" />
|
||||
</template>
|
||||
用于区分是否可以从网页进行点歌
|
||||
</NTooltip>
|
||||
</NCheckbox>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<NButton type="primary" @click="addCustomSong"> 添加 </NButton>
|
||||
</NTabPane>
|
||||
|
||||
@@ -69,7 +69,9 @@ const allowGuardTypes = computed(() => {
|
||||
async function update() {
|
||||
const r = await get()
|
||||
if (r) {
|
||||
songs.value = r.songs
|
||||
songs.value = r.songs.sort((a, b) => {
|
||||
return b.createAt - a.createAt
|
||||
})
|
||||
settings.value = r.setting
|
||||
}
|
||||
}
|
||||
@@ -90,7 +92,7 @@ onUnmounted(() => {
|
||||
<NDivider class="song-request-divider">
|
||||
<p class="song-request-header-count">已有 {{ activeSongs.length ?? 0 }} 首</p>
|
||||
</NDivider>
|
||||
<div class="song-request-singing-container" :singing="songs.findIndex((s) => s.status == SongRequestStatus.Singing) > -1">
|
||||
<div class="song-request-singing-container" :singing="songs.findIndex((s) => s.status == SongRequestStatus.Singing) > -1" :from="(singing?.from as number)" :status="(singing?.status as number)">
|
||||
<div class="song-request-singing-prefix"></div>
|
||||
<template v-if="singing">
|
||||
<img class="song-request-singing-avatar" :src="AVATAR_URL + singing?.user?.uid" referrerpolicy="no-referrer" />
|
||||
@@ -107,10 +109,10 @@ onUnmounted(() => {
|
||||
<div class="song-request-list-item-song-name">
|
||||
{{ song.songName }}
|
||||
</div>
|
||||
<p class="song-request-list-item-name">{{ song.from == SongRequestFrom.Manual ? '主播添加' : song.user?.name }}</p>
|
||||
<div class="song-request-list-item-level" :has-level="(song.user?.fans_medal_level ?? 0) > 0">
|
||||
{{ song.user?.fans_medal_level }}
|
||||
</div>
|
||||
<p class="song-request-list-item-name">{{ song.from == SongRequestFrom.Manual ? '主播添加' : song.user?.name }}</p>
|
||||
</span>
|
||||
</Vue3Marquee>
|
||||
</template>
|
||||
@@ -211,9 +213,16 @@ onUnmounted(() => {
|
||||
/* 添加无限旋转动画 */
|
||||
animation: rotate 20s linear infinite;
|
||||
}
|
||||
/* 网页点歌 */
|
||||
.song-request-singing-container[from='3'] .song-request-singing-avatar {
|
||||
display: none;
|
||||
}
|
||||
.song-request-singing-song-name {
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 80%;
|
||||
}
|
||||
.song-request-singing-name {
|
||||
font-size: 12px;
|
||||
@@ -236,12 +245,16 @@ onUnmounted(() => {
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.marquee {
|
||||
justify-items: left;
|
||||
}
|
||||
.song-request-list-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-self: flex-start;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
gap: 10px;
|
||||
@@ -249,13 +262,17 @@ onUnmounted(() => {
|
||||
.song-request-list-item-song-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
/* 手动添加 */
|
||||
.song-request-list-item[from='0'] .song-request-list-item-name {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
color: #c6e4d9;
|
||||
color: #d2d8d6;
|
||||
font-size: 12px;
|
||||
}
|
||||
.song-request-list-item[from='0'] .song-request-list-item-avatar {
|
||||
@@ -265,10 +282,15 @@ onUnmounted(() => {
|
||||
/* 弹幕点歌 */
|
||||
.song-request-list-item[from='1'] {
|
||||
}
|
||||
|
||||
.song-request-list-item-name {
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
color: rgba(204, 204, 204, 0.993);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
margin-left: auto;
|
||||
}
|
||||
.song-request-list-item-level {
|
||||
text-align: center;
|
||||
|
||||
@@ -85,6 +85,7 @@ const message = useMessage()
|
||||
const notice = useNotification()
|
||||
|
||||
const isWarnMessageAutoClose = useStorage('SongRequest.Settings.WarnMessageAutoClose', false)
|
||||
const volumn = useStorage('Settings.Volumn', 0.5)
|
||||
|
||||
const isLoading = ref(false)
|
||||
const showOBSModal = ref(false)
|
||||
@@ -114,12 +115,24 @@ const localActiveSongs = useStorage('SongRequest.ActiveSongs', [] as SongRequest
|
||||
const originSongs = ref<SongRequestInfo[]>(await getAllSong())
|
||||
const songs = computed(() => {
|
||||
return originSongs.value.filter((s) => {
|
||||
return (
|
||||
(filterSongName.value == '' || filterSongNameContains.value
|
||||
? s.songName.toLowerCase().includes(filterSongName.value.toLowerCase())
|
||||
: s.songName.toLowerCase() == filterSongName.value.toLowerCase()) &&
|
||||
(filterName.value == '' || filterNameContains.value ? s.user?.name.toLowerCase().includes(filterName.value.toLowerCase()) : s.user?.name.toLowerCase() == filterName.value.toLowerCase())
|
||||
)
|
||||
if (filterName.value) {
|
||||
if (filterNameContains.value) {
|
||||
if (!s.user?.name.toLowerCase().includes(filterName.value.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
} else if (s.user?.name.toLowerCase() !== filterName.value.toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
} else if (filterSongName.value) {
|
||||
if (filterSongNameContains.value) {
|
||||
if (!s.songName.toLowerCase().includes(filterSongName.value.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
} else if (s.songName.toLowerCase() !== filterSongName.value.toLowerCase()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
const activeSongs = computed(() => {
|
||||
@@ -172,27 +185,6 @@ async function getAllSong() {
|
||||
return localActiveSongs.value
|
||||
}
|
||||
}
|
||||
async function getActiveSong() {
|
||||
if (accountInfo.value) {
|
||||
try {
|
||||
const data = await QueryGetAPI<SongRequestInfo[]>(SONG_REQUEST_API_URL + 'get-active', {
|
||||
id: accountInfo.value.id,
|
||||
})
|
||||
if (data.code == 200) {
|
||||
console.log('[OPEN-LIVE-Song-Request] 已获取点歌队列')
|
||||
return data.data
|
||||
} else {
|
||||
message.error('无法获取点歌队列: ' + data.message)
|
||||
return []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
return []
|
||||
} else {
|
||||
return localActiveSongs.value
|
||||
}
|
||||
}
|
||||
async function addSong(danmaku: EventModel) {
|
||||
console.log(`[OPEN-LIVE-Song-Request] 收到 [${danmaku.name}] 的点歌${danmaku.type == EventDataTypes.SC ? 'SC' : '弹幕'}: ${danmaku.msg}`)
|
||||
if (accountInfo.value) {
|
||||
@@ -656,8 +648,32 @@ function GetGuardColor(level: number | null | undefined): string {
|
||||
}
|
||||
return ''
|
||||
}
|
||||
async function updateActive() {
|
||||
if (!accountInfo.value) return
|
||||
try {
|
||||
const data = await QueryGetAPI<SongRequestInfo[]>(SONG_REQUEST_API_URL + 'get-active', {
|
||||
id: accountInfo.value?.id,
|
||||
})
|
||||
if (data.code == 200) {
|
||||
data.data.forEach((item) => {
|
||||
const song = originSongs.value.find((s) => s.id == item.id)
|
||||
if (song) {
|
||||
if (song.status != item.status) song.status = item.status
|
||||
} else {
|
||||
originSongs.value.unshift(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
message.error('无法获取点歌队列: ' + data.message)
|
||||
return []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
let timer: any
|
||||
let updateActiveTimer: any
|
||||
const updateKey = ref(0)
|
||||
onMounted(() => {
|
||||
if (accountInfo.value) {
|
||||
@@ -668,11 +684,15 @@ onMounted(() => {
|
||||
timer = setInterval(() => {
|
||||
updateKey.value++
|
||||
}, 1000)
|
||||
updateActiveTimer = setInterval(() => {
|
||||
updateActive()
|
||||
}, 3000)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
props.client.off('danmaku', onGetDanmaku)
|
||||
props.client.off('sc', onGetSC)
|
||||
clearInterval(timer)
|
||||
clearInterval(updateActiveTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -771,7 +791,7 @@ onUnmounted(() => {
|
||||
</NTooltip>
|
||||
</NSpace>
|
||||
<NSpace justify="end" align="center">
|
||||
<audio v-if="song.song" :src="song.song?.url" controls style="width: 300px; height: 30px; margin-bottom: -5px"></audio>
|
||||
<audio v-if="song.song" :volumn="volumn" :src="song.song?.url" controls style="width: 300px; height: 30px; margin-bottom: -5px"></audio>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
<template>
|
||||
<NSpin v-if="isLoading" show />
|
||||
<component v-else :is="componentType" :user-info="userInfo" :bili-info="biliInfo" :currentData="currentData" />
|
||||
<component
|
||||
v-else
|
||||
:is="componentType"
|
||||
:user-info="userInfo"
|
||||
:bili-info="biliInfo"
|
||||
:currentData="currentData"
|
||||
:song-request-settings="settings"
|
||||
:song-request-active="songs"
|
||||
@request-song="requestSong"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { SongsInfo } from '@/api/api-models'
|
||||
import { Setting_SongRequest, SongRequestInfo, SongsInfo } from '@/api/api-models'
|
||||
import DefaultSongListTemplate from '@/views/view/songListTemplate/DefaultSongListTemplate.vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { UserInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { SONG_API_URL } from '@/data/constants'
|
||||
import { QueryGetAPI, QueryPostAPIWithParams } from '@/api/query'
|
||||
import { SONG_API_URL, SONG_REQUEST_API_URL } from '@/data/constants'
|
||||
import { NSpin, useMessage } from 'naive-ui'
|
||||
import { useAccount } from '@/api/account'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
|
||||
const props = defineProps<{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -39,7 +51,22 @@ const isLoading = ref(true)
|
||||
const message = useMessage()
|
||||
|
||||
const errMessage = ref('')
|
||||
const songs = ref<SongRequestInfo[]>([])
|
||||
const settings = ref<Setting_SongRequest>({} as Setting_SongRequest)
|
||||
|
||||
async function getSongRequestInfo() {
|
||||
try {
|
||||
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_SongRequest }>(SONG_REQUEST_API_URL + 'get-active-and-settings', {
|
||||
id: props.userInfo?.id,
|
||||
})
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
return {} as { songs: SongRequestInfo[]; setting: Setting_SongRequest }
|
||||
}
|
||||
async function getSongs() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get', {
|
||||
@@ -61,10 +88,33 @@ async function getSongs() {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
async function requestSong(song: SongsInfo) {
|
||||
if (props.userInfo && accountInfo.value?.id != props.userInfo?.id) {
|
||||
try {
|
||||
const data = await QueryPostAPIWithParams(SONG_REQUEST_API_URL + 'add-from-web', {
|
||||
target: props.userInfo?.id,
|
||||
song: song.key,
|
||||
})
|
||||
|
||||
if (data.code == 200) {
|
||||
message.success('点歌成功')
|
||||
} else {
|
||||
message.error('点歌失败: ' + data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('点歌失败: ' + err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!props.fakeData) {
|
||||
await getSongs()
|
||||
const r = await getSongRequestInfo()
|
||||
if (r) {
|
||||
songs.value = r.songs
|
||||
settings.value = r.setting
|
||||
}
|
||||
} else {
|
||||
currentData.value = props.fakeData
|
||||
isLoading.value = false
|
||||
|
||||
@@ -2,19 +2,71 @@
|
||||
import { useAccount } from '@/api/account'
|
||||
import { SongsInfo, UserInfo } from '@/api/api-models'
|
||||
import SongList from '@/components/SongList.vue'
|
||||
import { NDivider } from 'naive-ui'
|
||||
import { CloudAdd20Filled } from '@vicons/fluent'
|
||||
import { NButton, NDivider, NIcon, NTooltip, useMessage } from 'naive-ui'
|
||||
import { h, ref } from 'vue'
|
||||
import { Setting_SongRequest, SongRequestInfo } from '@/api/api-models'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
userInfo: UserInfo | undefined
|
||||
biliInfo: any | undefined
|
||||
songRequestSettings: Setting_SongRequest
|
||||
songRequestActive: SongRequestInfo[]
|
||||
currentData: SongsInfo[] | undefined
|
||||
}>()
|
||||
const emits = defineEmits(['requestSong'])
|
||||
|
||||
const isLoading = ref('')
|
||||
const message = useMessage()
|
||||
|
||||
const buttoms = (song: SongsInfo) => [
|
||||
accountInfo.value?.id != props.userInfo?.id
|
||||
? h(
|
||||
NTooltip,
|
||||
{ trigger: 'hover' },
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: 'warning',
|
||||
size: 'small',
|
||||
circle: true,
|
||||
loading: isLoading.value == song.key,
|
||||
disabled: !accountInfo,
|
||||
onClick: () => {
|
||||
if (song.paidSong || !props.songRequestSettings.allowFromWeb) {
|
||||
navigator.clipboard.writeText(`${props.songRequestSettings.orderPrefix} ${song.name}`)
|
||||
message.success('复制成功')
|
||||
} else {
|
||||
isLoading.value = song.key
|
||||
emits('requestSong', song)
|
||||
isLoading.value = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: CloudAdd20Filled }),
|
||||
}
|
||||
),
|
||||
default: () =>
|
||||
!props.songRequestSettings.allowFromWeb
|
||||
? '点歌 | 用户不允许从网页点歌, 点击后将复制点歌内容到剪切板'
|
||||
: song.paidSong
|
||||
? '点歌 | 这是付费SC歌曲, 点击后将复制点歌内容到剪切板'
|
||||
: !accountInfo
|
||||
? '点歌 | 你需要登录后才能点歌'
|
||||
: '点歌',
|
||||
}
|
||||
)
|
||||
: undefined,
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDivider style="margin-top: 10px" />
|
||||
<SongList v-if="currentData" :songs="currentData ?? []" :is-self="accountInfo?.id == userInfo?.id" v-bind="$attrs" />
|
||||
<SongList v-if="currentData" :songs="currentData ?? []" :is-self="accountInfo?.id == userInfo?.id" :extra-buttom="buttoms" v-bind="$attrs" />
|
||||
<NDivider />
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { Setting_SongRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models';
|
||||
import { NGridItem, NGrid } from 'naive-ui'
|
||||
|
||||
import { NGridItem,NGrid } from 'naive-ui';
|
||||
const props = defineProps<{
|
||||
userInfo: UserInfo | undefined
|
||||
biliInfo: any | undefined
|
||||
songRequestSettings: Setting_SongRequest
|
||||
songRequretActive: SongRequestInfo[]
|
||||
currentData: SongsInfo[] | undefined
|
||||
}>()
|
||||
</script>
|
||||
<template>
|
||||
<NGrid>
|
||||
<NGridItem>
|
||||
|
||||
</NGridItem>
|
||||
<NGridItem> </NGridItem>
|
||||
</NGrid>
|
||||
</template>
|
||||
Reference in New Issue
Block a user