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:
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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user