mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 更新 LiveRequest 组件,重构代码并优化歌曲请求逻辑
- 添加时间戳以解决缓存问题 - 重构组件结构,简化逻辑,增强可读性 - 更新歌曲请求设置和管理功能
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,3 +24,4 @@ pnpm-debug.log*
|
||||
*.sw?
|
||||
env.d.ts
|
||||
/.specstory
|
||||
/.cursor
|
||||
|
||||
@@ -141,6 +141,10 @@ function getParams(params: any) {
|
||||
if (urlParams.has('token')) {
|
||||
resultParams.set('token', urlParams.get('token') || '')
|
||||
}
|
||||
|
||||
// 添加时间戳用于解决意外添加的缓存
|
||||
resultParams.set('timestamp', Date.now().toString())
|
||||
|
||||
return resultParams.toString()
|
||||
}
|
||||
export async function QueryPostPaginationAPI<T>(
|
||||
|
||||
522
src/composables/useLiveRequest.ts
Normal file
522
src/composables/useLiveRequest.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
import { ref, computed, Ref } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { List } from 'linqts'
|
||||
import { NTime, useMessage, useNotification } from 'naive-ui'
|
||||
import { h } from 'vue'
|
||||
|
||||
import {
|
||||
DanmakuUserInfo,
|
||||
EventDataTypes,
|
||||
EventModel,
|
||||
QueueSortType,
|
||||
SongRequestFrom,
|
||||
SongRequestInfo,
|
||||
SongRequestStatus,
|
||||
SongsInfo
|
||||
} from '@/api/api-models'
|
||||
import {
|
||||
QueryGetAPI,
|
||||
QueryPostAPI,
|
||||
QueryPostAPIWithParams
|
||||
} from '@/api/query'
|
||||
import { SONG_REQUEST_API_URL } from '@/data/constants'
|
||||
import { AddBiliBlackList, useAccount } from '@/api/account'
|
||||
|
||||
export const useLiveRequest = defineStore('songRequest', () => {
|
||||
const accountInfo = useAccount()
|
||||
|
||||
const localActiveSongs = useStorage('SongRequest.ActiveSongs', [] as SongRequestInfo[])
|
||||
const isWarnMessageAutoClose = useStorage('SongRequest.Settings.WarnMessageAutoClose', false)
|
||||
const isReverse = useStorage('SongRequest.Settings.Reverse', false)
|
||||
const defaultPrefix = useStorage('Settings.SongRequest.DefaultPrefix', '点播')
|
||||
|
||||
const isLoading = ref(false)
|
||||
const originSongs = ref<SongRequestInfo[]>([])
|
||||
const updateKey = ref(0)
|
||||
const filterSongName = ref('')
|
||||
const filterSongNameContains = ref(false)
|
||||
const filterName = ref('')
|
||||
const filterNameContains = ref(false)
|
||||
const newSongName = ref('')
|
||||
const selectedSong = ref<SongsInfo>()
|
||||
const isLrcLoading = ref('')
|
||||
|
||||
const configCanEdit = computed(() => {
|
||||
return accountInfo.value != null && accountInfo.value?.id !== undefined
|
||||
})
|
||||
|
||||
const songs = computed(() => {
|
||||
let result = new List(originSongs.value).Where((s) => {
|
||||
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 settings = accountInfo.value?.settings?.songRequest || {}
|
||||
|
||||
switch (settings.sortType) {
|
||||
case QueueSortType.TimeFirst: {
|
||||
result = result.ThenBy((q) => q.createAt)
|
||||
break
|
||||
}
|
||||
case QueueSortType.GuardFirst: {
|
||||
result = result
|
||||
.OrderBy((q) => (q.user?.guard_level == 0 || q.user?.guard_level == null ? 4 : q.user.guard_level))
|
||||
.ThenBy((q) => q.createAt)
|
||||
break
|
||||
}
|
||||
case QueueSortType.PaymentFist: {
|
||||
result = result.OrderByDescending((q) => q.price ?? 0).ThenBy((q) => q.createAt)
|
||||
break
|
||||
}
|
||||
case QueueSortType.FansMedalFirst: {
|
||||
result = result.OrderByDescending((q) => q.user?.fans_medal_level ?? 0).ThenBy((q) => q.createAt)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ((configCanEdit.value && settings.isReverse) || (!configCanEdit.value && isReverse.value)) {
|
||||
return result.Reverse().ToArray()
|
||||
} else {
|
||||
return result.ToArray()
|
||||
}
|
||||
})
|
||||
|
||||
const activeSongs = computed(() => {
|
||||
return (accountInfo.value?.id ? songs.value : localActiveSongs.value)
|
||||
.sort((a, b) => b.status - a.status)
|
||||
.filter((song) => {
|
||||
return song.status == SongRequestStatus.Waiting || song.status == SongRequestStatus.Singing
|
||||
})
|
||||
})
|
||||
|
||||
const historySongs = computed(() => {
|
||||
return (accountInfo.value?.id ? songs.value : localActiveSongs.value)
|
||||
.sort((a, b) => a.status - b.status)
|
||||
.filter((song) => {
|
||||
return song.status == SongRequestStatus.Finish || song.status == SongRequestStatus.Cancel
|
||||
})
|
||||
})
|
||||
|
||||
const STATUS_MAP = {
|
||||
[SongRequestStatus.Waiting]: '等待中',
|
||||
[SongRequestStatus.Singing]: '处理中',
|
||||
[SongRequestStatus.Finish]: '已处理',
|
||||
[SongRequestStatus.Cancel]: '已取消',
|
||||
}
|
||||
|
||||
async function getAllSong() {
|
||||
if (accountInfo.value?.id) {
|
||||
try {
|
||||
const data = await QueryGetAPI<SongRequestInfo[]>(SONG_REQUEST_API_URL + 'get-all', {
|
||||
id: accountInfo.value.id,
|
||||
})
|
||||
if (data.code == 200) {
|
||||
console.log('[SONG-REQUEST] 已获取所有数据')
|
||||
return new List(data.data).OrderByDescending((s) => s.createAt).ToArray()
|
||||
} else {
|
||||
window.$message.error('无法获取数据: ' + data.message)
|
||||
return []
|
||||
}
|
||||
} catch (err) {
|
||||
window.$message.error('无法获取数据')
|
||||
}
|
||||
return []
|
||||
} else {
|
||||
return localActiveSongs.value
|
||||
}
|
||||
}
|
||||
|
||||
async function initData() {
|
||||
originSongs.value = await getAllSong()
|
||||
}
|
||||
|
||||
async function addSong(danmaku: EventModel) {
|
||||
console.log(
|
||||
`[SONG-REQUEST] 收到 [${danmaku.uname}] 的点播${danmaku.type == EventDataTypes.SC ? 'SC' : '弹幕'}: ${danmaku.msg}`,
|
||||
)
|
||||
|
||||
const settings = accountInfo.value?.settings?.songRequest || {}
|
||||
|
||||
if (settings.enableOnStreaming && accountInfo.value?.streamerInfo?.isStreaming != true) {
|
||||
window.$notification.info({
|
||||
title: `${danmaku.uname} 点播失败`,
|
||||
description: '当前未在直播中, 无法添加点播请求. 或者关闭设置中的仅允许直播时加入',
|
||||
meta: () => h(NTime, { type: 'relative', time: Date.now(), key: updateKey.value }),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (accountInfo.value?.id) {
|
||||
await QueryPostAPI<SongRequestInfo>(SONG_REQUEST_API_URL + 'try-add', danmaku).then((data) => {
|
||||
if (data.code == 200) {
|
||||
window.$message.success(`[${danmaku.uname}] 添加曲目: ${data.data.songName}`)
|
||||
if (data.message != 'EventFetcher') originSongs.value.unshift(data.data)
|
||||
} else {
|
||||
const time = Date.now()
|
||||
window.$notification.warning({
|
||||
title: danmaku.uname + ' 点播失败',
|
||||
description: data.message,
|
||||
duration: isWarnMessageAutoClose.value ? 3000 : 0,
|
||||
meta: () => h(NTime, { type: 'relative', time: time, key: updateKey.value }),
|
||||
})
|
||||
console.log(`[SONG-REQUEST] [${danmaku.uname}] 添加曲目失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const songData = {
|
||||
songName: danmaku.msg.trim().substring(settings.orderPrefix?.length || defaultPrefix.value.length),
|
||||
song: undefined,
|
||||
status: SongRequestStatus.Waiting,
|
||||
from: danmaku.type == EventDataTypes.Message ? SongRequestFrom.Danmaku : SongRequestFrom.SC,
|
||||
scPrice: danmaku.type == EventDataTypes.SC ? danmaku.price : 0,
|
||||
user: {
|
||||
name: danmaku.uname,
|
||||
uid: danmaku.uid,
|
||||
oid: danmaku.open_id,
|
||||
face: danmaku.uface,
|
||||
fans_medal_level: danmaku.fans_medal_level,
|
||||
fans_medal_name: danmaku.fans_medal_name,
|
||||
fans_medal_wearing_status: danmaku.fans_medal_wearing_status,
|
||||
guard_level: danmaku.guard_level,
|
||||
} as DanmakuUserInfo,
|
||||
createAt: Date.now(),
|
||||
isInLocal: true,
|
||||
id: songs.value.length == 0 ? 1 : new List(songs.value).Max((s) => s.id) + 1,
|
||||
} as SongRequestInfo
|
||||
|
||||
localActiveSongs.value.unshift(songData)
|
||||
window.$message.success(`[${danmaku.uname}] 添加: ${songData.songName}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function addSongManual() {
|
||||
if (!newSongName.value) {
|
||||
window.$message.error('请输入名称')
|
||||
return
|
||||
}
|
||||
|
||||
if (accountInfo.value?.id) {
|
||||
await QueryPostAPIWithParams<SongRequestInfo>(SONG_REQUEST_API_URL + 'add', {
|
||||
name: newSongName.value,
|
||||
}).then((data) => {
|
||||
if (data.code == 200) {
|
||||
window.$message.success(`已手动添加: ${data.data.songName}`)
|
||||
originSongs.value.unshift(data.data)
|
||||
newSongName.value = ''
|
||||
console.log(`[SONG-REQUEST] 已手动添加: ${data.data.songName}`)
|
||||
} else {
|
||||
window.$message.error(`手动添加失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const songData = {
|
||||
songName: newSongName.value,
|
||||
song: undefined,
|
||||
status: SongRequestStatus.Waiting,
|
||||
from: SongRequestFrom.Manual,
|
||||
scPrice: undefined,
|
||||
user: undefined,
|
||||
createAt: Date.now(),
|
||||
isInLocal: true,
|
||||
id: songs.value.length == 0 ? 1 : new List(songs.value).Max((s) => s.id) + 1,
|
||||
} as SongRequestInfo
|
||||
|
||||
localActiveSongs.value.unshift(songData)
|
||||
window.$message.success(`已手动添加: ${songData.songName}`)
|
||||
newSongName.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSongStatus(song: SongRequestInfo, status: SongRequestStatus) {
|
||||
if (!configCanEdit.value) {
|
||||
song.status = status
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
let statusString = ''
|
||||
let statusString2 = ''
|
||||
|
||||
switch (status) {
|
||||
case SongRequestStatus.Waiting:
|
||||
statusString = 'active'
|
||||
statusString2 = '等待中'
|
||||
break
|
||||
case SongRequestStatus.Cancel:
|
||||
statusString = 'cancel'
|
||||
statusString2 = '已取消'
|
||||
break
|
||||
case SongRequestStatus.Finish:
|
||||
statusString = 'finish'
|
||||
statusString2 = '已完成'
|
||||
break
|
||||
case SongRequestStatus.Singing:
|
||||
statusString = 'singing'
|
||||
statusString2 = '处理中'
|
||||
break
|
||||
}
|
||||
|
||||
await QueryGetAPI(SONG_REQUEST_API_URL + statusString, {
|
||||
id: song.id,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
console.log(`[SONG-REQUEST] 更新状态: ${song.songName} -> ${statusString}`)
|
||||
song.status = status
|
||||
if (status > SongRequestStatus.Singing) {
|
||||
song.finishAt = Date.now()
|
||||
}
|
||||
window.$message.success(`已更新状态为: ${statusString2}`)
|
||||
} else {
|
||||
console.log(`[SONG-REQUEST] 更新状态失败: ${data.message}`)
|
||||
window.$message.error(`更新状态失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
window.$message.error(`更新状态失败`)
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteSongs(values: SongRequestInfo[]) {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const data = await QueryPostAPI(
|
||||
SONG_REQUEST_API_URL + 'del',
|
||||
values.map((s) => s.id),
|
||||
)
|
||||
|
||||
if (data.code == 200) {
|
||||
window.$message.success('删除成功')
|
||||
originSongs.value = originSongs.value.filter((s) => !values.includes(s))
|
||||
} else {
|
||||
window.$message.error('删除失败: ' + data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
window.$message.error('删除失败')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deactiveAllSongs() {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const data = await QueryGetAPI(SONG_REQUEST_API_URL + 'deactive')
|
||||
|
||||
if (data.code == 200) {
|
||||
window.$message.success('已全部取消')
|
||||
songs.value.forEach((s) => {
|
||||
if (s.status <= SongRequestStatus.Singing) {
|
||||
s.status = SongRequestStatus.Cancel
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.$message.error('取消失败: ' + data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
window.$message.error('取消失败')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateActive() {
|
||||
if (!accountInfo.value?.id) 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)
|
||||
if (item.from == SongRequestFrom.Web) {
|
||||
window.$message.success(`[${item.user?.name}] 直接从网页歌单点播: ${item.songName}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.$message.error('无法获取点播队列: ' + data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[SONG-REQUEST] 更新活跃歌曲失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function blockUser(item: SongRequestInfo) {
|
||||
if (item.from != SongRequestFrom.Danmaku) {
|
||||
window.$message.error(`[${item.user?.name}] 不是来自弹幕的用户`)
|
||||
return
|
||||
}
|
||||
|
||||
if (item.user) {
|
||||
try {
|
||||
const data = await AddBiliBlackList(item.user.uid, item.user.name)
|
||||
|
||||
if (data.code == 200) {
|
||||
window.$message.success(`[${item.user?.name}] 已添加到黑名单`)
|
||||
updateSongStatus(item, SongRequestStatus.Cancel)
|
||||
} else {
|
||||
window.$message.error(data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
window.$message.error('添加黑名单失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkMessage(msg: string) {
|
||||
if (accountInfo.value?.settings?.enableFunctions?.includes(6) != true) {
|
||||
return false
|
||||
}
|
||||
|
||||
const prefix = accountInfo.value?.settings?.songRequest?.orderPrefix || defaultPrefix.value
|
||||
return msg.trim().toLowerCase().startsWith(prefix.toLowerCase())
|
||||
}
|
||||
|
||||
function getSCColor(price: number): string {
|
||||
if (price === 0) return `#2a60b2`
|
||||
if (price > 0 && price < 30) return `#2a60b2`
|
||||
if (price >= 30 && price < 50) return `#2a60b2`
|
||||
if (price >= 50 && price < 100) return `#427d9e`
|
||||
if (price >= 100 && price < 500) return `#c99801`
|
||||
if (price >= 500 && price < 1000) return `#e09443`
|
||||
if (price >= 1000 && price < 2000) return `#e54d4d`
|
||||
if (price >= 2000) return `#ab1a32`
|
||||
return ''
|
||||
}
|
||||
|
||||
function getGuardColor(level: number | null | undefined): string {
|
||||
if (level) {
|
||||
switch (level) {
|
||||
case 1: return 'rgb(122, 4, 35)'
|
||||
case 2: return 'rgb(157, 155, 255)'
|
||||
case 3: return 'rgb(104, 136, 241)'
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function onGetDanmaku(danmaku: EventModel) {
|
||||
if (checkMessage(danmaku.msg)) {
|
||||
addSong(danmaku)
|
||||
}
|
||||
}
|
||||
|
||||
function onGetSC(danmaku: EventModel) {
|
||||
const settings = accountInfo.value?.settings?.songRequest || {}
|
||||
|
||||
if (settings.allowSC && checkMessage(danmaku.msg)) {
|
||||
addSong(danmaku)
|
||||
}
|
||||
}
|
||||
|
||||
let updateActiveTimer: any = null
|
||||
|
||||
function startUpdateTimer() {
|
||||
stopUpdateTimer()
|
||||
updateActiveTimer = setInterval(() => {
|
||||
updateActive()
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function stopUpdateTimer() {
|
||||
if (updateActiveTimer) {
|
||||
clearInterval(updateActiveTimer)
|
||||
updateActiveTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
let updateKeyTimer: any = null
|
||||
|
||||
function startUpdateKeyTimer() {
|
||||
stopUpdateKeyTimer()
|
||||
updateKeyTimer = setInterval(() => {
|
||||
updateKey.value++
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function stopUpdateKeyTimer() {
|
||||
if (updateKeyTimer) {
|
||||
clearInterval(updateKeyTimer)
|
||||
updateKeyTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await initData()
|
||||
startUpdateKeyTimer()
|
||||
startUpdateTimer()
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
stopUpdateKeyTimer()
|
||||
stopUpdateTimer()
|
||||
}
|
||||
|
||||
return {
|
||||
// 状态
|
||||
isLoading,
|
||||
originSongs,
|
||||
songs,
|
||||
activeSongs,
|
||||
historySongs,
|
||||
newSongName,
|
||||
selectedSong,
|
||||
isLrcLoading,
|
||||
filterSongName,
|
||||
filterSongNameContains,
|
||||
filterName,
|
||||
filterNameContains,
|
||||
updateKey,
|
||||
STATUS_MAP,
|
||||
isWarnMessageAutoClose,
|
||||
isReverse,
|
||||
defaultPrefix,
|
||||
|
||||
// 计算
|
||||
configCanEdit,
|
||||
|
||||
// 方法
|
||||
init,
|
||||
dispose,
|
||||
addSong,
|
||||
addSongManual,
|
||||
updateSongStatus,
|
||||
deleteSongs,
|
||||
deactiveAllSongs,
|
||||
updateActive,
|
||||
blockUser,
|
||||
checkMessage,
|
||||
onGetDanmaku,
|
||||
onGetSC,
|
||||
getSCColor,
|
||||
getGuardColor
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
267
src/views/open_live/components/SongRequestHistory.vue
Normal file
267
src/views/open_live/components/SongRequestHistory.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<script setup lang="ts">
|
||||
import { h, ref, computed } from 'vue'
|
||||
import { NCard, NSpace, NInputGroup, NInputGroupLabel, NInput, NCheckbox, NDataTable, NTime, NTag, NText, NIcon, NButton, NPopconfirm, NTooltip } from 'naive-ui'
|
||||
import { Delete24Filled, ArrowCounterclockwise24Regular } from '@vicons/fluent'
|
||||
import { SongRequestFrom, SongRequestInfo, SongRequestStatus } from '@/api/api-models'
|
||||
import { useLiveRequest } from '@/composables/useLiveRequest'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
|
||||
// 使用useLiveRequest
|
||||
const songRequest = useLiveRequest()
|
||||
|
||||
const table = ref()
|
||||
|
||||
const statusFilterOptions = computed(() => {
|
||||
return Object.values(SongRequestStatus)
|
||||
.filter((t) => /^\d+$/.test(t.toString()))
|
||||
.map((t) => {
|
||||
return {
|
||||
label: songRequest.STATUS_MAP[t as SongRequestStatus],
|
||||
value: t,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const columns: DataTableColumns<SongRequestInfo> = [
|
||||
{
|
||||
title: '曲名',
|
||||
key: 'songName',
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'user.name',
|
||||
render: (row: SongRequestInfo) => {
|
||||
return h(
|
||||
NTooltip,
|
||||
{ trigger: 'hover' },
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NTag,
|
||||
{ bordered: false, size: 'small' },
|
||||
row.from == 3 // Manual
|
||||
? () => h(NText, { italic: true }, () => '手动添加')
|
||||
: () => row.user?.name || '未知用户',
|
||||
),
|
||||
default: () => (row.from == 3 ? '就是主播自己' : row.user?.uid || '未知ID'),
|
||||
},
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '来自',
|
||||
key: 'from',
|
||||
render(row: SongRequestInfo) {
|
||||
let fromType: 'info' | 'success' | 'default' | 'error' = 'info'
|
||||
switch (row.from) {
|
||||
case SongRequestFrom.Danmaku: { // Danmaku
|
||||
fromType = 'info'
|
||||
break
|
||||
}
|
||||
case SongRequestFrom.SC: { // SC
|
||||
fromType = 'error'
|
||||
break
|
||||
}
|
||||
case SongRequestFrom.Web: { // Web
|
||||
fromType = 'success'
|
||||
break
|
||||
}
|
||||
case SongRequestFrom.Manual: { // Manual
|
||||
fromType = 'default'
|
||||
break
|
||||
}
|
||||
}
|
||||
return h(NTag, { size: 'small', type: fromType }, () => {
|
||||
switch (row.from) {
|
||||
case SongRequestFrom.Danmaku: {
|
||||
return '弹幕'
|
||||
}
|
||||
case SongRequestFrom.SC: {
|
||||
return 'SuperChat' + (row.price ? ` | ${row.price}` : '')
|
||||
}
|
||||
case SongRequestFrom.Gift: {
|
||||
return '礼物' + (row.price ? ` | ${row.price}` : '')
|
||||
}
|
||||
case SongRequestFrom.Manual: {
|
||||
return '手动添加'
|
||||
}
|
||||
case SongRequestFrom.Web: {
|
||||
return '网页添加'
|
||||
}
|
||||
default:
|
||||
return '未知'
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
filter(value, row: SongRequestInfo) {
|
||||
return ~row.status == Number(value)
|
||||
},
|
||||
filterOptions: statusFilterOptions.value,
|
||||
render(row: SongRequestInfo) {
|
||||
let statusType: 'info' | 'success' | 'warning' | 'error' = 'info'
|
||||
switch (row.status) {
|
||||
case SongRequestStatus.Singing: {
|
||||
statusType = 'success'
|
||||
break
|
||||
}
|
||||
case SongRequestStatus.Waiting: {
|
||||
statusType = 'warning'
|
||||
break
|
||||
}
|
||||
case SongRequestStatus.Finish: {
|
||||
statusType = 'info'
|
||||
break
|
||||
}
|
||||
case SongRequestStatus.Cancel: {
|
||||
statusType = 'error'
|
||||
break
|
||||
}
|
||||
}
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: statusType,
|
||||
size: 'small',
|
||||
style: row.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : '',
|
||||
},
|
||||
() => songRequest.STATUS_MAP[row.status],
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
key: 'time',
|
||||
sorter: (a: SongRequestInfo, b: SongRequestInfo) => a.createAt - b.createAt,
|
||||
render: (row: SongRequestInfo) => {
|
||||
return h(NTime, { time: row.createAt })
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'manage',
|
||||
width: 100,
|
||||
render(row: SongRequestInfo) {
|
||||
return h(
|
||||
NSpace,
|
||||
{
|
||||
justify: 'center',
|
||||
size: 10,
|
||||
},
|
||||
() => [
|
||||
row.status == SongRequestStatus.Finish || row.status == SongRequestStatus.Cancel
|
||||
? h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'info',
|
||||
circle: true,
|
||||
loading: songRequest.isLoading,
|
||||
onClick: () => {
|
||||
songRequest.updateSongStatus(row, SongRequestStatus.Waiting)
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: ArrowCounterclockwise24Regular }),
|
||||
},
|
||||
),
|
||||
default: () => '重新放回等待列表',
|
||||
})
|
||||
: undefined,
|
||||
h(
|
||||
NPopconfirm,
|
||||
{ onPositiveClick: () => songRequest.deleteSongs([row]) },
|
||||
{
|
||||
trigger: () =>
|
||||
h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
circle: true,
|
||||
loading: songRequest.isLoading,
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, { component: Delete24Filled }),
|
||||
},
|
||||
),
|
||||
default: () => '删除记录',
|
||||
}),
|
||||
default: () => '确定删除?',
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard size="small">
|
||||
<NSpace>
|
||||
<NInputGroup style="width: 300px">
|
||||
<NInputGroupLabel> 筛选曲名 </NInputGroupLabel>
|
||||
<NInput
|
||||
:value="songRequest.filterSongName"
|
||||
clearable
|
||||
@update:value="songRequest.filterSongName = $event"
|
||||
>
|
||||
<template #suffix>
|
||||
<NCheckbox
|
||||
:checked="songRequest.filterSongNameContains"
|
||||
@update:checked="songRequest.filterSongNameContains = $event"
|
||||
>
|
||||
包含
|
||||
</NCheckbox>
|
||||
</template>
|
||||
</NInput>
|
||||
</NInputGroup>
|
||||
<NInputGroup style="width: 300px">
|
||||
<NInputGroupLabel> 筛选用户名 </NInputGroupLabel>
|
||||
<NInput
|
||||
:value="songRequest.filterName"
|
||||
clearable
|
||||
@update:value="songRequest.filterName = $event"
|
||||
>
|
||||
<template #suffix>
|
||||
<NCheckbox
|
||||
:checked="songRequest.filterNameContains"
|
||||
@update:checked="songRequest.filterNameContains = $event"
|
||||
>
|
||||
包含
|
||||
</NCheckbox>
|
||||
</template>
|
||||
</NInput>
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<br>
|
||||
<NDataTable
|
||||
ref="table"
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data="songRequest.songs"
|
||||
:bordered="false"
|
||||
:loading="songRequest.isLoading"
|
||||
:row-class-name="(row, index) => (row.status == SongRequestStatus.Singing || row.status == SongRequestStatus.Waiting ? 'song-active' : '')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.song-active {
|
||||
color: white;
|
||||
background-color: #24292e;
|
||||
}
|
||||
|
||||
.song-active:hover {
|
||||
background-color: #586069 !important;
|
||||
}
|
||||
</style>
|
||||
307
src/views/open_live/components/SongRequestItem.vue
Normal file
307
src/views/open_live/components/SongRequestItem.vue
Normal file
@@ -0,0 +1,307 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import {
|
||||
Checkmark12Regular,
|
||||
Dismiss16Filled,
|
||||
Mic24Filled,
|
||||
Play24Filled,
|
||||
PresenceBlocked16Regular,
|
||||
} from '@vicons/fluent'
|
||||
import {
|
||||
NSpace,
|
||||
NText,
|
||||
NTag,
|
||||
NTime,
|
||||
NTooltip,
|
||||
NButton,
|
||||
NIcon,
|
||||
NCard,
|
||||
NPopconfirm
|
||||
} from 'naive-ui'
|
||||
import { SongRequestInfo, SongRequestStatus, SongsInfo, SongRequestFrom } from '@/api/api-models'
|
||||
import { useLiveRequest } from '@/composables/useLiveRequest'
|
||||
|
||||
const props = defineProps<{
|
||||
song: SongRequestInfo
|
||||
isLoading: boolean
|
||||
isLrcLoading: string
|
||||
updateKey: number
|
||||
hasOtherSingSong?: boolean
|
||||
}>()
|
||||
|
||||
// 使用useLiveRequest
|
||||
const songRequest = useLiveRequest()
|
||||
|
||||
const isActiveSong = computed(() => props.song.status <= SongRequestStatus.Singing)
|
||||
const isSingingStatus = computed(() => props.song.status === SongRequestStatus.Singing)
|
||||
const hasSong = computed(() => !!props.song.song?.url)
|
||||
|
||||
const canStartSinging = computed(() => {
|
||||
return props.song.status === SongRequestStatus.Waiting
|
||||
})
|
||||
|
||||
function onSelectSong() {
|
||||
if (hasSong.value) {
|
||||
songRequest.selectedSong = props.song.song!
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateStatus(status: SongRequestStatus) {
|
||||
songRequest.updateSongStatus(props.song, status)
|
||||
}
|
||||
|
||||
function onBlockUser() {
|
||||
songRequest.blockUser(props.song)
|
||||
}
|
||||
|
||||
function getSCColor(price: number): string {
|
||||
if (price === 0) return `#2a60b2`
|
||||
if (price > 0 && price < 30) return `#2a60b2`
|
||||
if (price >= 30 && price < 50) return `#2a60b2`
|
||||
if (price >= 50 && price < 100) return `#427d9e`
|
||||
if (price >= 100 && price < 500) return `#c99801`
|
||||
if (price >= 500 && price < 1000) return `#e09443`
|
||||
if (price >= 1000 && price < 2000) return `#e54d4d`
|
||||
if (price >= 2000) return `#ab1a32`
|
||||
return ''
|
||||
}
|
||||
|
||||
function getGuardColor(level: number | null | undefined): string {
|
||||
if (level) {
|
||||
switch (level) {
|
||||
case 1: return 'rgb(122, 4, 35)'
|
||||
case 2: return 'rgb(157, 155, 255)'
|
||||
case 3: return 'rgb(104, 136, 241)'
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 获取父组件中的活跃歌曲
|
||||
const activeSongs = inject<SongRequestInfo[]>('activeSongs', [])
|
||||
|
||||
// 判断是否有其他正在演唱的歌曲
|
||||
const hasOtherSingSong = computed(() => {
|
||||
return activeSongs.findIndex((s: SongRequestInfo) => s.id != props.song.id && s.status == SongRequestStatus.Singing) > -1
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard
|
||||
embedded
|
||||
size="small"
|
||||
content-style="padding: 5px;"
|
||||
:style="`${isSingingStatus ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`"
|
||||
>
|
||||
<NSpace
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="height: 100%; margin: 0 5px 0 5px"
|
||||
>
|
||||
<NSpace align="center">
|
||||
<div
|
||||
:style="`border-radius: 4px; background-color: ${isSingingStatus ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`"
|
||||
/>
|
||||
<NText
|
||||
strong
|
||||
style="font-size: 18px"
|
||||
>
|
||||
{{ song.songName }}
|
||||
</NText>
|
||||
<template v-if="song.from == SongRequestFrom.Manual">
|
||||
<!-- Manual -->
|
||||
<NTag
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
手动添加
|
||||
</NTag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NTag
|
||||
size="small"
|
||||
:bordered="false"
|
||||
type="info"
|
||||
>
|
||||
<NText
|
||||
italic
|
||||
depth="3"
|
||||
>
|
||||
{{ song.user?.name || '未知用户' }}
|
||||
</NText>
|
||||
</NTag>
|
||||
</template>
|
||||
{{ song.user?.uid || '未知ID' }}
|
||||
</NTooltip>
|
||||
</template>
|
||||
<NSpace
|
||||
v-if="
|
||||
(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC) &&
|
||||
song.user?.fans_medal_wearing_status
|
||||
"
|
||||
>
|
||||
<NTag
|
||||
size="tiny"
|
||||
round
|
||||
>
|
||||
<NTag
|
||||
size="tiny"
|
||||
round
|
||||
:bordered="false"
|
||||
>
|
||||
<NText depth="3">
|
||||
{{ song.user?.fans_medal_level }}
|
||||
</NText>
|
||||
</NTag>
|
||||
<span style="color: #577fb8">
|
||||
{{ song.user?.fans_medal_name }}
|
||||
</span>
|
||||
</NTag>
|
||||
</NSpace>
|
||||
<NTag
|
||||
v-if="(song.user?.guard_level ?? 0) > 0"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
:color="{ textColor: 'white', color: songRequest.getGuardColor(song.user?.guard_level) }"
|
||||
>
|
||||
{{ song.user?.guard_level == 1 ? '总督' : song.user?.guard_level == 2 ? '提督' : '舰长' }}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="song.from == SongRequestFrom.SC"
|
||||
size="small"
|
||||
:color="{ textColor: 'white', color: songRequest.getSCColor(song.price ?? 0) }"
|
||||
>
|
||||
SC{{ song.price ? ` | ${song.price}` : '' }}
|
||||
</NTag>
|
||||
<NTag
|
||||
v-if="song.from == SongRequestFrom.Gift"
|
||||
size="small"
|
||||
:color="{ textColor: 'white', color: songRequest.getSCColor(song.price ?? 0) }"
|
||||
>
|
||||
礼物{{ song.price ? ` | ${song.price}` : '' }}
|
||||
</NTag>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NText style="font-size: small">
|
||||
<NTime
|
||||
:key="updateKey"
|
||||
:time="song.createAt"
|
||||
type="relative"
|
||||
/>
|
||||
</NText>
|
||||
</template>
|
||||
<NTime :time="song.createAt" />
|
||||
</NTooltip>
|
||||
</NSpace>
|
||||
<NSpace
|
||||
justify="end"
|
||||
align="center"
|
||||
>
|
||||
<NTooltip v-if="hasSong">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
type="success"
|
||||
style="height: 30px; width: 30px"
|
||||
:loading="isLrcLoading == song?.song?.key"
|
||||
@click="onSelectSong"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="Play24Filled" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
试听
|
||||
</NTooltip>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
type="primary"
|
||||
style="height: 30px; width: 30px"
|
||||
:disabled="hasOtherSingSong"
|
||||
:style="`animation: ${song.status == SongRequestStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
|
||||
:secondary="song.status == SongRequestStatus.Singing"
|
||||
:loading="isLoading"
|
||||
@click="
|
||||
onUpdateStatus(
|
||||
song.status == SongRequestStatus.Singing
|
||||
? SongRequestStatus.Waiting
|
||||
: SongRequestStatus.Singing,
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="Mic24Filled" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
{{
|
||||
hasOtherSingSong
|
||||
? '还有其他正在演唱的歌曲'
|
||||
: song.status == SongRequestStatus.Waiting && song.id
|
||||
? '开始演唱'
|
||||
: '停止演唱'
|
||||
}}
|
||||
</NTooltip>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
type="primary"
|
||||
style="height: 30px; width: 30px"
|
||||
:loading="isLoading"
|
||||
@click="onUpdateStatus(SongRequestStatus.Finish)"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="Checkmark12Regular" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
完成
|
||||
</NTooltip>
|
||||
<NPopconfirm
|
||||
@positive-click="onUpdateStatus(SongRequestStatus.Cancel)"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
type="error"
|
||||
style="height: 30px; width: 30px"
|
||||
:loading="isLoading"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="Dismiss16Filled" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
是否取消处理?
|
||||
</NPopconfirm>
|
||||
<NPopconfirm
|
||||
v-if="
|
||||
song.from == SongRequestFrom.Danmaku &&
|
||||
song.user?.uid &&
|
||||
song.status !== SongRequestStatus.Cancel
|
||||
"
|
||||
@positive-click="onBlockUser"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
circle
|
||||
type="error"
|
||||
style="height: 30px; width: 30px"
|
||||
:loading="isLoading"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="PresenceBlocked16Regular" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
是否拉黑此用户?
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</template>
|
||||
157
src/views/open_live/components/SongRequestList.vue
Normal file
157
src/views/open_live/components/SongRequestList.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, provide } from 'vue'
|
||||
import { NCard, NSpace, NTag, NIcon, NInput, NInputGroup, NButton, NPopconfirm, NRadioGroup, NRadioButton, NCheckbox, NEmpty, NList, NListItem, NDivider } from 'naive-ui'
|
||||
import { PeopleQueue24Filled } from '@vicons/fluent'
|
||||
import { Checkmark12Regular } from '@vicons/fluent'
|
||||
import { isSameDay } from 'date-fns'
|
||||
import { SongRequestInfo, SongRequestStatus, QueueSortType } from '@/api/api-models'
|
||||
import SongRequestItem from './SongRequestItem.vue'
|
||||
import { useLiveRequest } from '@/composables/useLiveRequest'
|
||||
import { useAccount } from '@/api/account'
|
||||
import { SaveSetting } from '@/api/account'
|
||||
|
||||
// 使用useLiveRequest
|
||||
const songRequest = useLiveRequest()
|
||||
const accountInfo = useAccount()
|
||||
|
||||
// 提供activeSongs给子组件
|
||||
provide('activeSongs', songRequest.activeSongs)
|
||||
|
||||
const todayFinishedCount = computed(() => {
|
||||
return songRequest.songs.filter(s => s.status != SongRequestStatus.Cancel &&
|
||||
isSameDay(s.finishAt ?? 0, Date.now())).length
|
||||
})
|
||||
|
||||
const waitingCount = computed(() => {
|
||||
return songRequest.activeSongs.filter(s => s.status === SongRequestStatus.Waiting).length
|
||||
})
|
||||
|
||||
// 当前的排序顺序
|
||||
const currentIsReverse = computed(() =>
|
||||
songRequest.configCanEdit ? accountInfo.value?.settings?.songRequest?.isReverse : songRequest.isReverse
|
||||
)
|
||||
|
||||
// 保存排序设置
|
||||
async function updateSettings() {
|
||||
if (accountInfo.value?.id) {
|
||||
songRequest.isLoading = true
|
||||
await SaveSetting('SongRequest', accountInfo.value.settings.songRequest)
|
||||
.then((msg) => {
|
||||
if (msg) {
|
||||
window.$message.success('已保存')
|
||||
return true
|
||||
} else {
|
||||
window.$message.error('保存失败: ' + msg)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
songRequest.isLoading = false
|
||||
})
|
||||
} else {
|
||||
window.$message.success('完成')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard size="small">
|
||||
<NSpace align="center">
|
||||
<NTag
|
||||
type="success"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="PeopleQueue24Filled" />
|
||||
</template>
|
||||
队列 | {{ waitingCount }}
|
||||
</NTag>
|
||||
<NTag
|
||||
type="success"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #icon>
|
||||
<NIcon :component="Checkmark12Regular" />
|
||||
</template>
|
||||
今日已处理 | {{ todayFinishedCount }} 个
|
||||
</NTag>
|
||||
<NInputGroup>
|
||||
<NInput
|
||||
:value="songRequest.newSongName"
|
||||
placeholder="手动添加"
|
||||
@update:value="songRequest.newSongName = $event"
|
||||
/>
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="songRequest.addSongManual()"
|
||||
>
|
||||
添加
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
<NRadioGroup
|
||||
v-model:value="accountInfo.settings.songRequest.sortType"
|
||||
:disabled="!songRequest.configCanEdit"
|
||||
type="button"
|
||||
@update:value="value => {
|
||||
updateSettings()
|
||||
}"
|
||||
>
|
||||
<NRadioButton :value="QueueSortType.TimeFirst">
|
||||
加入时间优先
|
||||
</NRadioButton>
|
||||
<NRadioButton :value="QueueSortType.PaymentFist">
|
||||
付费价格优先
|
||||
</NRadioButton>
|
||||
<NRadioButton :value="QueueSortType.GuardFirst">
|
||||
舰长优先 (按等级)
|
||||
</NRadioButton>
|
||||
<NRadioButton :value="QueueSortType.FansMedalFirst">
|
||||
粉丝牌等级优先
|
||||
</NRadioButton>
|
||||
</NRadioGroup>
|
||||
<NCheckbox
|
||||
:checked="currentIsReverse"
|
||||
@update:checked="value => {
|
||||
if (songRequest.configCanEdit) {
|
||||
accountInfo.settings.songRequest.isReverse = value
|
||||
updateSettings()
|
||||
} else {
|
||||
songRequest.isReverse = value
|
||||
}
|
||||
}"
|
||||
>
|
||||
倒序
|
||||
</NCheckbox>
|
||||
<NPopconfirm @positive-click="songRequest.deactiveAllSongs()">
|
||||
<template #trigger>
|
||||
<NButton type="error">
|
||||
全部取消
|
||||
</NButton>
|
||||
</template>
|
||||
确定全部取消吗?
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NDivider> 共 {{ songRequest.activeSongs.length }} 首 </NDivider>
|
||||
<NList
|
||||
v-if="songRequest.activeSongs.length > 0"
|
||||
:show-divider="false"
|
||||
hoverable
|
||||
>
|
||||
<NListItem
|
||||
v-for="song in songRequest.activeSongs"
|
||||
:key="song.id"
|
||||
style="padding: 5px"
|
||||
>
|
||||
<SongRequestItem
|
||||
:song="song"
|
||||
:is-loading="songRequest.isLoading"
|
||||
:is-lrc-loading="songRequest.isLrcLoading"
|
||||
:update-key="songRequest.updateKey"
|
||||
/>
|
||||
</NListItem>
|
||||
</NList>
|
||||
<NEmpty
|
||||
v-else
|
||||
description="暂无曲目"
|
||||
/>
|
||||
</template>
|
||||
350
src/views/open_live/components/SongRequestSettings.vue
Normal file
350
src/views/open_live/components/SongRequestSettings.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<script setup lang="ts">
|
||||
import { FunctionTypes } from '@/api/api-models';
|
||||
import { useLiveRequest } from '@/composables/useLiveRequest';
|
||||
import { SaveEnableFunctions, SaveSetting, useAccount } from '@/api/account'
|
||||
import {
|
||||
NAlert,
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NDivider,
|
||||
NInput,
|
||||
NInputGroup,
|
||||
NInputGroupLabel,
|
||||
NInputNumber,
|
||||
NSpace,
|
||||
NSpin,
|
||||
useMessage
|
||||
} from 'naive-ui';
|
||||
import { computed } from 'vue';
|
||||
|
||||
// 使用useLiveRequest
|
||||
const liveRequest = useLiveRequest()
|
||||
const accountInfo = useAccount()
|
||||
const message = useMessage()
|
||||
|
||||
const enableSongRequest = computed({
|
||||
get: () => {
|
||||
return accountInfo.value?.settings?.enableFunctions?.includes(FunctionTypes.SongRequest) || false
|
||||
},
|
||||
set: async () => {
|
||||
await updateEnableFunctions()
|
||||
}
|
||||
})
|
||||
|
||||
// 控制歌曲请求功能开关
|
||||
async function updateEnableFunctions() {
|
||||
if (accountInfo.value.id) {
|
||||
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
|
||||
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest)) {
|
||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
||||
(f: number) => f != FunctionTypes.SongRequest,
|
||||
)
|
||||
} else {
|
||||
accountInfo.value.settings.enableFunctions.push(FunctionTypes.SongRequest)
|
||||
}
|
||||
if (!accountInfo.value.settings.songRequest.orderPrefix) {
|
||||
accountInfo.value.settings.songRequest.orderPrefix = liveRequest.defaultPrefix
|
||||
}
|
||||
await SaveEnableFunctions(accountInfo.value?.settings.enableFunctions)
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success(
|
||||
`已${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}点播功能`,
|
||||
)
|
||||
} else {
|
||||
if (accountInfo.value.id) {
|
||||
accountInfo.value.settings.enableFunctions = oldValue
|
||||
}
|
||||
message.error(
|
||||
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`,
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error(
|
||||
`点播功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 更新歌曲请求设置
|
||||
async function updateSettings() {
|
||||
if (accountInfo.value.id) {
|
||||
liveRequest.isLoading = true
|
||||
await SaveSetting('SongRequest', accountInfo.value.settings.songRequest)
|
||||
.then((msg) => {
|
||||
if (msg) {
|
||||
message.success('已保存')
|
||||
return true
|
||||
} else {
|
||||
message.error('保存失败: ' + msg)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
liveRequest.isLoading = false
|
||||
})
|
||||
} else {
|
||||
message.success('完成')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="liveRequest.isLoading">
|
||||
<NDivider> 规则 </NDivider>
|
||||
<NSpace vertical>
|
||||
<NSpace align="center">
|
||||
<NInputGroup style="width: 250px">
|
||||
<NInputGroupLabel> 点播弹幕前缀 </NInputGroupLabel>
|
||||
<template v-if="liveRequest.configCanEdit">
|
||||
<NInput v-model:value="accountInfo.settings.songRequest.orderPrefix" />
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</template>
|
||||
<NInput
|
||||
v-else
|
||||
v-model:value="liveRequest.defaultPrefix"
|
||||
/>
|
||||
</NInputGroup>
|
||||
<NAlert
|
||||
v-if="accountInfo.settings.songRequest.orderPrefix && accountInfo.settings.songRequest.orderPrefix.includes(' ')"
|
||||
type="info"
|
||||
>
|
||||
前缀包含空格
|
||||
</NAlert>
|
||||
</NSpace>
|
||||
<NInputGroup style="width: 250px">
|
||||
<NInputGroupLabel> 最大队列长度 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.queueMaxSize"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
<NSpace align="center">
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.enableOnStreaming"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
仅在直播时才允许加入
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.allowAllDanmaku"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
允许所有弹幕点播
|
||||
</NCheckbox>
|
||||
<template v-if="!accountInfo.settings.songRequest.allowAllDanmaku">
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.needWearFanMedal"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
需要拥有粉丝牌
|
||||
</NCheckbox>
|
||||
<NInputGroup
|
||||
v-if="accountInfo.settings.songRequest.needWearFanMedal"
|
||||
style="width: 250px"
|
||||
>
|
||||
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.fanMedalMinLevel"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
<NCheckbox
|
||||
v-if="!accountInfo.settings.songRequest.allowAllDanmaku"
|
||||
v-model:checked="accountInfo.settings.songRequest.needJianzhang"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
只允许舰长
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-if="!accountInfo.settings.songRequest.allowAllDanmaku"
|
||||
v-model:checked="accountInfo.settings.songRequest.needTidu"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
只允许提督
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-if="!accountInfo.settings.songRequest.allowAllDanmaku"
|
||||
v-model:checked="accountInfo.settings.songRequest.needZongdu"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
只允许总督
|
||||
</NCheckbox>
|
||||
</template>
|
||||
</NSpace>
|
||||
<NSpace align="center">
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.allowSC"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
允许通过 SuperChat 点播
|
||||
</NCheckbox>
|
||||
<span v-if="accountInfo.settings.songRequest.allowSC">
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.scIgnoreLimit"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
SC 点播无视限制
|
||||
</NCheckbox>
|
||||
</span>
|
||||
<NInputGroup
|
||||
v-if="accountInfo.settings.songRequest.allowSC"
|
||||
style="width: 250px"
|
||||
>
|
||||
<NInputGroupLabel> 最低SC价格 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.scMinPrice"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
<NDivider> 点歌 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.onlyAllowSongList"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
仅允许点
|
||||
<NButton
|
||||
text
|
||||
tag="a"
|
||||
href="/manage/song-list"
|
||||
target="_blank"
|
||||
type="info"
|
||||
>
|
||||
歌单
|
||||
</NButton>
|
||||
内的歌曲
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.allowFromWeb"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
允许通过网页点歌
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-if="accountInfo.settings.songRequest.allowFromWeb"
|
||||
v-model:checked="accountInfo.settings.songRequest.allowAnonymousFromWeb"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
允许匿名通过网页点歌
|
||||
</NCheckbox>
|
||||
</NSpace>
|
||||
<NDivider> 冷却 (单位: 秒) </NDivider>
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.songRequest.enableCooldown"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@update:checked="updateSettings"
|
||||
>
|
||||
启用点播冷却
|
||||
</NCheckbox>
|
||||
<NSpace v-if="accountInfo.settings.songRequest.enableCooldown">
|
||||
<NInputGroup style="width: 250px">
|
||||
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.cooldownSecond"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
<NInputGroup style="width: 220px">
|
||||
<NInputGroupLabel> 舰长 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.jianzhangCooldownSecond"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
<NSpace v-if="accountInfo.settings.songRequest.enableCooldown">
|
||||
<NInputGroup style="width: 220px">
|
||||
<NInputGroupLabel> 提督 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.tiduCooldownSecond"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
<NInputGroup style="width: 220px">
|
||||
<NInputGroupLabel> 总督 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="accountInfo.settings.songRequest.zongduCooldownSecond"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
/>
|
||||
<NButton
|
||||
type="info"
|
||||
:disabled="!liveRequest.configCanEdit"
|
||||
@click="updateSettings"
|
||||
>
|
||||
确定
|
||||
</NButton>
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
<NDivider> 警告消息 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox
|
||||
:checked="liveRequest.isWarnMessageAutoClose"
|
||||
@update:checked="liveRequest.isWarnMessageAutoClose = $event"
|
||||
>
|
||||
自动关闭警告消息
|
||||
</NCheckbox>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
</NSpin>
|
||||
</template>
|
||||
Reference in New Issue
Block a user