mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
refactor: 优化多个视图组件并添加功能
本次提交对多个视图组件进行了重构和功能增强:
PointGoodsView.vue:
- 清理了未使用的导入(`useAccount`)和变量(`accountInfo`, `biliInfo` prop)。
- 通过重组计算属性和方法提高了代码可读性。
- 增强了商品列表的筛选和排序逻辑。
- 为购买商品功能添加了错误处理和加载状态。
PointUserHistoryView.vue:
- 为获取积分历史记录实现了加载状态。
- 改进了 PointHistoryCard 组件的渲染。
QuestionBoxView.vue:
- 优化了可读性和性能(整合状态变量,改进命名)。
- 增强了文件上传处理和验证逻辑。
- 改进了标签选择逻辑和数据获取方法。
- 添加了代码注释以提高可理解性。
UserIndexView.vue:
- 简化了确定要显示的模板组件的逻辑。
- 确保无论用户信息是否存在,都一致返回默认模板。
This commit is contained in:
@@ -6,7 +6,6 @@ import MessageRender from './blivechat/MessageRender.vue';
|
||||
import * as constants from './blivechat/constants';
|
||||
// @ts-ignore
|
||||
import * as pronunciation from './blivechat/utils/pronunciation';
|
||||
// @ts-ignore
|
||||
import { DownloadConfig, useAccount } from '@/api/account';
|
||||
import { QueryGetAPI } from '@/api/query';
|
||||
import { VTSURU_API_URL } from '@/data/constants';
|
||||
@@ -54,6 +53,7 @@ const pronunciationConverter = new pronunciation.PronunciationConverter()
|
||||
const accountInfo = useAccount()
|
||||
const route = useRoute()
|
||||
|
||||
// 默认配置
|
||||
const defaultConfig: DanmujiConfig = {
|
||||
minGiftPrice: 0.1,
|
||||
showDanmaku: true,
|
||||
@@ -72,15 +72,17 @@ const defaultConfig: DanmujiConfig = {
|
||||
importPresetCss: false,
|
||||
|
||||
emoticons: []
|
||||
} as DanmujiConfig
|
||||
}
|
||||
|
||||
let textEmoticons: { keyword: string, url: string }[] = []
|
||||
const config = ref<DanmujiConfig>(JSON.parse(JSON.stringify(defaultConfig)))
|
||||
const rtc = await useWebRTC().Init('slave')
|
||||
|
||||
// 表情词典树计算
|
||||
const emoticonsTrie = computed(() => {
|
||||
let res = new trie.Trie()
|
||||
for (let emoticons of [config.value.emoticons, textEmoticons]) {
|
||||
for (let emoticon of emoticons) {
|
||||
const res = new trie.Trie()
|
||||
for (const emoticons of [config.value.emoticons, textEmoticons]) {
|
||||
for (const emoticon of emoticons) {
|
||||
if (emoticon.keyword !== '' && emoticon.url !== '') {
|
||||
res.set(emoticon.keyword, emoticon)
|
||||
}
|
||||
@@ -88,10 +90,12 @@ const emoticonsTrie = computed(() => {
|
||||
}
|
||||
return res
|
||||
})
|
||||
|
||||
// 屏蔽关键词词典树计算
|
||||
const blockKeywordsTrie = computed(() => {
|
||||
let blockKeywords = config.value.blockKeywords.split('\n')
|
||||
let res = new trie.Trie()
|
||||
for (let keyword of blockKeywords) {
|
||||
const blockKeywords = config.value.blockKeywords.split('\n')
|
||||
const res = new trie.Trie()
|
||||
for (const keyword of blockKeywords) {
|
||||
if (keyword !== '') {
|
||||
res.set(keyword, true)
|
||||
}
|
||||
@@ -99,22 +103,28 @@ const blockKeywordsTrie = computed(() => {
|
||||
return res
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置自定义CSS
|
||||
*/
|
||||
function setCss(css: string) {
|
||||
messageRender.value?.setCss(css)
|
||||
}
|
||||
|
||||
/** @param {chatModels.AddTextMsg} data */
|
||||
/**
|
||||
* 处理弹幕消息
|
||||
*/
|
||||
async function onAddText(data: DanmakuInfo, command: unknown) {
|
||||
if (!config.value.showDanmaku || !filterTextMessage(data)) {
|
||||
return
|
||||
}
|
||||
|
||||
let richContent = await getRichContent(data)
|
||||
const richContent = await getRichContent(data)
|
||||
// 合并要放在异步调用后面,因为异步调用后可能有新的消息,会漏合并
|
||||
if (mergeSimilarText(data.msg)) {
|
||||
return
|
||||
}
|
||||
let message = {
|
||||
|
||||
const message = {
|
||||
id: data.msg_id,
|
||||
type: constants.MESSAGE_TYPE_TEXT,
|
||||
avatarUrl: data.uface,
|
||||
@@ -129,19 +139,27 @@ async function onAddText(data: DanmakuInfo, command: unknown) {
|
||||
}
|
||||
messageRender.value.addMessage(message)
|
||||
}
|
||||
/** @param {chatModels.AddGiftMsg} data */
|
||||
function onAddGift(data: GiftInfo, command: any) {
|
||||
|
||||
/**
|
||||
* 处理礼物消息
|
||||
*/
|
||||
function onAddGift(data: GiftInfo, command: unknown) {
|
||||
if (!config.value.showGift) {
|
||||
return
|
||||
}
|
||||
let price = (data.price * data.gift_num) / 1000
|
||||
|
||||
const price = (data.price * data.gift_num) / 1000
|
||||
// 价格过滤
|
||||
if (price < (config.value.minGiftPrice ?? 0)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 尝试合并相似礼物
|
||||
if (mergeSimilarGift(data.uname, price, !data.paid ? price : 0, data.gift_name, data.gift_num)) {
|
||||
return
|
||||
}
|
||||
if (price < (config.value.minGiftPrice ?? 0)) { // 丢人
|
||||
return
|
||||
}
|
||||
let message = {
|
||||
|
||||
const message = {
|
||||
id: data.msg_id,
|
||||
type: constants.MESSAGE_TYPE_GIFT,
|
||||
avatarUrl: data.uface,
|
||||
@@ -149,18 +167,21 @@ function onAddGift(data: GiftInfo, command: any) {
|
||||
authorName: data.uname,
|
||||
authorNamePronunciation: getPronunciation(data.uname),
|
||||
price: price,
|
||||
// freePrice: data.totalFreeCoin, // 暂时没用到
|
||||
giftName: data.gift_name,
|
||||
num: data.gift_num
|
||||
}
|
||||
messageRender.value.addMessage(message)
|
||||
}
|
||||
/** @param {chatModels.AddMemberMsg} data */
|
||||
function onAddMember(data: GuardInfo, command: any) {
|
||||
|
||||
/**
|
||||
* 处理舰长上舰消息
|
||||
*/
|
||||
function onAddMember(data: GuardInfo, command: unknown) {
|
||||
if (!config.value.showGift || !filterNewMemberMessage(data)) {
|
||||
return
|
||||
}
|
||||
let message = {
|
||||
|
||||
const message = {
|
||||
id: data.msg_id,
|
||||
type: constants.MESSAGE_TYPE_MEMBER,
|
||||
avatarUrl: data.user_info.uface,
|
||||
@@ -172,15 +193,20 @@ function onAddMember(data: GuardInfo, command: any) {
|
||||
}
|
||||
messageRender.value.addMessage(message)
|
||||
}
|
||||
/** @param {chatModels.AddSuperChatMsg} data */
|
||||
|
||||
/**
|
||||
* 处理醒目留言消息
|
||||
*/
|
||||
function onAddSuperChat(data: SCInfo) {
|
||||
if (!config.value.showGift || !filterSuperChatMessage(data)) {
|
||||
return
|
||||
}
|
||||
if (data.rmb < (config.value.minGiftPrice ?? 0)) { // 丢人
|
||||
|
||||
if (data.rmb < (config.value.minGiftPrice ?? 0)) {
|
||||
return
|
||||
}
|
||||
let message = {
|
||||
|
||||
const message = {
|
||||
id: data.msg_id,
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
avatarUrl: data.uface,
|
||||
@@ -193,29 +219,41 @@ function onAddSuperChat(data: SCInfo) {
|
||||
}
|
||||
messageRender.value.addMessage(message)
|
||||
}
|
||||
/** @param {chatModels.DelSuperChatMsg} data */
|
||||
function onDelSuperChat(data: any) {
|
||||
|
||||
/**
|
||||
* 处理SC撤回
|
||||
*/
|
||||
function onDelSuperChat(data: { id: string }) {
|
||||
messageRender.value.deleteMessage(data.id)
|
||||
}
|
||||
function getAuthorType(open_id: string, guard_level: number) {
|
||||
let authorType
|
||||
|
||||
/**
|
||||
* 获取用户类型:0-普通用户,1-舰长,3-主播
|
||||
*/
|
||||
function getAuthorType(open_id: string, guard_level: number): number {
|
||||
if (open_id === client.authInfo?.anchor_info.open_id) {
|
||||
authorType = 3
|
||||
return 3 // 主播
|
||||
} else if (guard_level !== 0) {
|
||||
authorType = 1
|
||||
return 1 // 舰长
|
||||
} else {
|
||||
authorType = 0
|
||||
return 0 // 普通用户
|
||||
}
|
||||
}
|
||||
|
||||
type RichContentType = {
|
||||
type: string,
|
||||
type: number,
|
||||
text: string,
|
||||
url?: string,
|
||||
width?: number,
|
||||
height?: number
|
||||
}
|
||||
async function getRichContent(data: DanmakuInfo) {
|
||||
let richContent: RichContentType[] = []
|
||||
|
||||
/**
|
||||
* 获取富文本内容(处理表情等)
|
||||
*/
|
||||
async function getRichContent(data: DanmakuInfo): Promise<RichContentType[]> {
|
||||
const richContent: RichContentType[] = []
|
||||
|
||||
// 官方的非文本表情
|
||||
if (data.emoji_img_url) {
|
||||
richContent.push({
|
||||
@@ -225,7 +263,6 @@ async function getRichContent(data: DanmakuInfo) {
|
||||
width: 256,
|
||||
height: 256
|
||||
})
|
||||
//await fillImageContentSizes(richContent)
|
||||
return richContent
|
||||
}
|
||||
|
||||
@@ -242,8 +279,8 @@ async function getRichContent(data: DanmakuInfo) {
|
||||
let startPos = 0
|
||||
let pos = 0
|
||||
while (pos < data.msg.length) {
|
||||
let remainContent = data.msg.substring(pos)
|
||||
let matchEmoticon = emoticonsTrie.value.lazyMatch(remainContent)
|
||||
const remainContent = data.msg.substring(pos)
|
||||
const matchEmoticon = emoticonsTrie.value.lazyMatch(remainContent)
|
||||
if (matchEmoticon === null) {
|
||||
pos++
|
||||
continue
|
||||
@@ -268,6 +305,7 @@ async function getRichContent(data: DanmakuInfo) {
|
||||
pos += matchEmoticon.keyword.length
|
||||
startPos = pos
|
||||
}
|
||||
|
||||
// 加入尾部的文本
|
||||
if (pos !== startPos) {
|
||||
richContent.push({
|
||||
@@ -279,131 +317,175 @@ async function getRichContent(data: DanmakuInfo) {
|
||||
await fillImageContentSizes(richContent)
|
||||
return richContent
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充图片内容的尺寸信息
|
||||
*/
|
||||
async function fillImageContentSizes(richContent: RichContentType[]) {
|
||||
let urlSizeMap = new Map()
|
||||
for (let content of richContent) {
|
||||
if (content.type === constants.CONTENT_TYPE_IMAGE) {
|
||||
const urlSizeMap = new Map()
|
||||
|
||||
// 收集所有需要获取尺寸的图片URL
|
||||
for (const content of richContent) {
|
||||
if (content.type === constants.CONTENT_TYPE_IMAGE && content.url) {
|
||||
urlSizeMap.set(content.url, { width: 0, height: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
if (urlSizeMap.size === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
let promises = []
|
||||
for (let url of urlSizeMap.keys()) {
|
||||
let urlInClosure = url
|
||||
promises.push(new Promise(
|
||||
resolve => {
|
||||
let img = document.createElement('img')
|
||||
img.onload = () => {
|
||||
let size = urlSizeMap.get(urlInClosure)
|
||||
size.width = img.naturalWidth
|
||||
size.height = img.naturalHeight
|
||||
// @ts-expect-error 忽略这里测错误
|
||||
resolve()
|
||||
}
|
||||
// 获取失败了默认为0
|
||||
img.onerror = resolve
|
||||
// 超时保底
|
||||
window.setTimeout(resolve, 5000)
|
||||
img.src = urlInClosure
|
||||
// 并行加载所有图片获取尺寸
|
||||
const promises = []
|
||||
for (const url of urlSizeMap.keys()) {
|
||||
promises.push(new Promise<void>(resolve => {
|
||||
const img = document.createElement('img')
|
||||
img.onload = () => {
|
||||
const size = urlSizeMap.get(url)
|
||||
size.width = img.naturalWidth
|
||||
size.height = img.naturalHeight
|
||||
resolve()
|
||||
}
|
||||
))
|
||||
// 获取失败了默认为0
|
||||
img.onerror = () => resolve()
|
||||
// 超时保底
|
||||
window.setTimeout(() => resolve(), 5000)
|
||||
img.src = url
|
||||
}))
|
||||
}
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
for (let content of richContent) {
|
||||
if (content.type === constants.CONTENT_TYPE_IMAGE) {
|
||||
let size = urlSizeMap.get(content.url)
|
||||
// 应用获取的尺寸到富文本内容
|
||||
for (const content of richContent) {
|
||||
if (content.type === constants.CONTENT_TYPE_IMAGE && content.url) {
|
||||
const size = urlSizeMap.get(content.url)
|
||||
content.width = size.width
|
||||
content.height = size.height
|
||||
}
|
||||
}
|
||||
}
|
||||
function getPronunciation(text: string) {
|
||||
if (pronunciationConverter === null) {
|
||||
|
||||
/**
|
||||
* 获取名称发音
|
||||
*/
|
||||
function getPronunciation(text: string): string {
|
||||
if (!pronunciationConverter) {
|
||||
return ''
|
||||
}
|
||||
return pronunciationConverter.getPronunciation(text)
|
||||
}
|
||||
function filterSuperChatMessage(data: SCInfo) {
|
||||
|
||||
/**
|
||||
* 过滤SC消息
|
||||
*/
|
||||
function filterSuperChatMessage(data: SCInfo): boolean {
|
||||
return filterByContent(data.message) && filterByAuthorName(data.uname)
|
||||
}
|
||||
function filterNewMemberMessage(data: GuardInfo) {
|
||||
|
||||
/**
|
||||
* 过滤新舰长消息
|
||||
*/
|
||||
function filterNewMemberMessage(data: GuardInfo): boolean {
|
||||
return filterByAuthorName(data.user_info.uname)
|
||||
}
|
||||
function filterByContent(content: string) {
|
||||
|
||||
/**
|
||||
* 根据内容过滤消息
|
||||
*/
|
||||
function filterByContent(content: string): boolean {
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
let remainContent = content.substring(i)
|
||||
const remainContent = content.substring(i)
|
||||
if (blockKeywordsTrie.value.lazyMatch(remainContent) !== null) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
function filterByAuthorName(id: string) {
|
||||
return Object.keys(accountInfo.value.biliBlackList).indexOf(id) === -1
|
||||
|
||||
/**
|
||||
* 根据用户名过滤消息
|
||||
*/
|
||||
function filterByAuthorName(id: string): boolean {
|
||||
return !(id in accountInfo.value.biliBlackList)
|
||||
}
|
||||
function filterTextMessage(data: DanmakuInfo) {
|
||||
|
||||
/**
|
||||
* 过滤弹幕消息
|
||||
*/
|
||||
function filterTextMessage(data: DanmakuInfo): boolean {
|
||||
// 舰长等级过滤
|
||||
if (config.value.blockLevel > 0 && data.guard_level < config.value.blockLevel) {
|
||||
return false
|
||||
} else if (config.value.blockMedalLevel > 0 && data.fans_medal_level < config.value.blockMedalLevel) {
|
||||
}
|
||||
// 粉丝牌等级过滤
|
||||
else if (config.value.blockMedalLevel > 0 && data.fans_medal_level < config.value.blockMedalLevel) {
|
||||
return false
|
||||
}
|
||||
return filterByContent(data.msg) && filterByAuthorName(data.uname)
|
||||
}
|
||||
function mergeSimilarText(content: string) {
|
||||
|
||||
/**
|
||||
* 合并相似文本
|
||||
*/
|
||||
function mergeSimilarText(content: string): boolean {
|
||||
if (!config.value.mergeSimilarDanmaku) {
|
||||
return false
|
||||
}
|
||||
return messageRender.value.mergeSimilarText(content)
|
||||
}
|
||||
function mergeSimilarGift(authorName: string, price: number, freePrice: number, giftName: string, num: number) {
|
||||
|
||||
/**
|
||||
* 合并相似礼物
|
||||
*/
|
||||
function mergeSimilarGift(authorName: string, price: number, freePrice: number, giftName: string, num: number): boolean {
|
||||
if (!config.value.mergeGift) {
|
||||
return false
|
||||
}
|
||||
return messageRender.value.mergeSimilarGift(authorName, price, freePrice, giftName, num)
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收配置更新
|
||||
*/
|
||||
function onReceiveConfig(data: DanmujiConfig) {
|
||||
config.value = data
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 注册事件监听
|
||||
client.on('danmaku', onAddText)
|
||||
client.on('gift', onAddGift)
|
||||
client.on('sc', onAddSuperChat)
|
||||
//client.onEvent('delsc', onSuperChatDel)
|
||||
client.on('guard', onAddMember)
|
||||
|
||||
rtc?.on('danmuji.config', onReceiveConfig)
|
||||
// 注册RTC配置接收
|
||||
if (rtc) {
|
||||
rtc.on('danmuji.config', onReceiveConfig)
|
||||
}
|
||||
|
||||
QueryGetAPI<{ keyword: string, url: string }[]>(VTSURU_API_URL + 'blivechat/emoticon').then((data) => {
|
||||
if (data.code === 200) {
|
||||
textEmoticons = data.data
|
||||
}
|
||||
})
|
||||
return
|
||||
while (true) {
|
||||
const result = await DownloadConfig('OBS.Danmuji')
|
||||
if (result.msg === undefined) {
|
||||
config.value = result.data as DanmujiConfig
|
||||
break
|
||||
}
|
||||
else if (result.status == 'error') {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
// 加载表情包
|
||||
try {
|
||||
const result = await QueryGetAPI<{ keyword: string, url: string }[]>(VTSURU_API_URL + 'blivechat/emoticon')
|
||||
if (result.code === 200) {
|
||||
textEmoticons = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载表情包失败:', error)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 取消事件监听
|
||||
client.off('danmaku', onAddText)
|
||||
client.off('gift', onAddGift)
|
||||
client.off('sc', onAddSuperChat)
|
||||
//client.offEvent('delsc', onSuperChatDel)
|
||||
client.off('guard', onAddMember)
|
||||
|
||||
rtc?.off('danmuji.config', onReceiveConfig)
|
||||
// 取消RTC配置接收
|
||||
if (rtc) {
|
||||
rtc.off('danmuji.config', onReceiveConfig)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user