feat: 更新 LiveRequest 组件,重构代码并优化歌曲请求逻辑

- 添加时间戳以解决缓存问题
- 重构组件结构,简化逻辑,增强可读性
- 更新歌曲请求设置和管理功能
This commit is contained in:
2025-04-27 03:33:48 +08:00
parent 4d997b6615
commit 00ce0fc7e1
8 changed files with 1669 additions and 1399 deletions

View 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
}
})