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