mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-08 11:26:56 +08:00
chore: format code style and update linting configuration
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { BiliAuthModel, ResponsePointGoodModel } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { BILI_AUTH_API_URL, POINT_API_URL } from '@/data/constants'
|
||||
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||
import type { BiliAuthModel, ResponsePointGoodModel } from '@/api/api-models'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { BILI_AUTH_API_URL, POINT_API_URL } from '@/data/constants'
|
||||
|
||||
export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
const biliAuth = ref<BiliAuthModel>({} as BiliAuthModel)
|
||||
@@ -36,13 +36,13 @@ export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
async function getAuthInfo() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
if(!currentToken.value) return
|
||||
await QueryBiliAuthGetAPI<BiliAuthModel>(BILI_AUTH_API_URL + 'info').then((data) => {
|
||||
if (!currentToken.value) return
|
||||
await QueryBiliAuthGetAPI<BiliAuthModel>(`${BILI_AUTH_API_URL}info`).then((data) => {
|
||||
if (data.code == 200) {
|
||||
biliAuth.value = data.data
|
||||
console.log('[bili-auth] 已获取 Bilibili 认证信息')
|
||||
// 将token加入到biliTokens
|
||||
const index = biliTokens.value.findIndex((t) => t.id == biliAuth.value.id)
|
||||
const index = biliTokens.value.findIndex(t => t.id == biliAuth.value.id)
|
||||
if (index >= 0) {
|
||||
biliTokens.value[index] = {
|
||||
id: biliAuth.value.id,
|
||||
@@ -50,7 +50,7 @@ export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
name: biliAuth.value.name,
|
||||
uId: biliAuth.value.userId,
|
||||
}
|
||||
//console.log('更新已存在的认证账户: ' + biliAuth.value.userId)
|
||||
// console.log('更新已存在的认证账户: ' + biliAuth.value.userId)
|
||||
} else {
|
||||
biliTokens.value.push({
|
||||
id: biliAuth.value.id,
|
||||
@@ -58,35 +58,35 @@ export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
name: biliAuth.value.name,
|
||||
uId: biliAuth.value.userId,
|
||||
})
|
||||
console.log('添加新的认证账户: ' + biliAuth.value.userId)
|
||||
console.log(`添加新的认证账户: ${biliAuth.value.userId}`)
|
||||
}
|
||||
isInvalid.value = false
|
||||
return true
|
||||
} else {
|
||||
console.error('[bili-auth] 无法获取 Bilibili 认证信息: ' + data.message)
|
||||
console.error(`[bili-auth] 无法获取 Bilibili 认证信息: ${data.message}`)
|
||||
isInvalid.value = true
|
||||
logout()
|
||||
//message.error('无法获取 Bilibili 认证信息: ' + data.message)
|
||||
// message.error('无法获取 Bilibili 认证信息: ' + data.message)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('[bili-auth] 无法获取 Bilibili 认证信息: ' + err)
|
||||
//message.error('无法获取 Bilibili 认证信息: ' + err)
|
||||
console.error(`[bili-auth] 无法获取 Bilibili 认证信息: ${err}`)
|
||||
// message.error('无法获取 Bilibili 认证信息: ' + err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
return false
|
||||
}
|
||||
function QueryBiliAuthGetAPI<T>(url: string, params?: any, headers?: [string, string][]) {
|
||||
async function QueryBiliAuthGetAPI<T>(url: string, params?: any, headers?: [string, string][]) {
|
||||
headers ??= []
|
||||
if (headers.find((h) => h[0] == 'Bili-Auth') == null) {
|
||||
if (headers.find(h => h[0] == 'Bili-Auth') == null) {
|
||||
headers.push(['Bili-Auth', currentToken.value ?? ''])
|
||||
}
|
||||
return QueryGetAPI<T>(url, params, headers)
|
||||
}
|
||||
function QueryBiliAuthPostAPI<T>(url: string, body?: unknown, headers?: [string, string][]) {
|
||||
async function QueryBiliAuthPostAPI<T>(url: string, body?: unknown, headers?: [string, string][]) {
|
||||
headers ??= []
|
||||
if (headers.find((h) => h[0] == 'Bili-Auth') == null) {
|
||||
if (headers.find(h => h[0] == 'Bili-Auth') == null) {
|
||||
headers.push(['Bili-Auth', currentToken.value ?? ''])
|
||||
}
|
||||
return QueryPostAPI<T>(url, body, headers)
|
||||
@@ -94,14 +94,14 @@ export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
|
||||
async function GetSpecificPoint(id: number) {
|
||||
try {
|
||||
const data = await QueryBiliAuthGetAPI<number>(POINT_API_URL + 'user/get-point', { id: id })
|
||||
const data = await QueryBiliAuthGetAPI<number>(`${POINT_API_URL}user/get-point`, { id })
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
} else {
|
||||
console.error('[point] 无法获取在指定直播间拥有的积分: ' + data.message)
|
||||
console.error(`[point] 无法获取在指定直播间拥有的积分: ${data.message}`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[point] 无法获取在指定直播间拥有的积分: ' + err)
|
||||
console.error(`[point] 无法获取在指定直播间拥有的积分: ${err}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -110,24 +110,24 @@ export const useBiliAuth = defineStore('BiliAuth', () => {
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const resp = await QueryGetAPI<ResponsePointGoodModel[]>(POINT_API_URL + 'get-goods', {
|
||||
id: id,
|
||||
const resp = await QueryGetAPI<ResponsePointGoodModel[]>(`${POINT_API_URL}get-goods`, {
|
||||
id,
|
||||
})
|
||||
if (resp.code == 200) {
|
||||
return resp.data
|
||||
} else {
|
||||
message?.error('无法获取数据: ' + resp.message)
|
||||
console.error('无法获取数据: ' + resp.message)
|
||||
message?.error(`无法获取数据: ${resp.message}`)
|
||||
console.error(`无法获取数据: ${resp.message}`)
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
console.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
console.error(`无法获取数据: ${err}`)
|
||||
}
|
||||
return []
|
||||
}
|
||||
function logout() {
|
||||
biliAuth.value = {} as BiliAuthModel
|
||||
biliTokens.value = biliTokens.value.filter((t) => t.token != currentToken.value)
|
||||
biliTokens.value = biliTokens.value.filter(t => t.token != currentToken.value)
|
||||
currentToken.value = ''
|
||||
console.log('[bili-auth] 已登出 Bilibili 认证')
|
||||
}
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
import { EventModel, OpenLiveInfo } from '@/api/api-models';
|
||||
import BaseDanmakuClient from '@/data/DanmakuClients/BaseDanmakuClient';
|
||||
import DirectClient, { DirectClientAuthInfo } from '@/data/DanmakuClients/DirectClient';
|
||||
import OpenLiveClient, { AuthInfo } from '@/data/DanmakuClients/OpenLiveClient';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref, shallowRef } from 'vue'; // 引入 shallowRef
|
||||
import type { EventModel, OpenLiveInfo } from '@/api/api-models'
|
||||
import type BaseDanmakuClient from '@/data/DanmakuClients/BaseDanmakuClient'
|
||||
import type { DirectClientAuthInfo } from '@/data/DanmakuClients/DirectClient'
|
||||
import type { AuthInfo } from '@/data/DanmakuClients/OpenLiveClient'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, shallowRef } from 'vue' // 引入 shallowRef
|
||||
import DirectClient from '@/data/DanmakuClients/DirectClient'
|
||||
import OpenLiveClient from '@/data/DanmakuClients/OpenLiveClient'
|
||||
|
||||
// 定义支持的事件名称类型
|
||||
type EventName = 'danmaku' | 'gift' | 'sc' | 'guard' | 'enter' | 'scDel' | 'follow';
|
||||
type EventNameWithAll = EventName | 'all';
|
||||
type EventName = 'danmaku' | 'gift' | 'sc' | 'guard' | 'enter' | 'scDel' | 'follow'
|
||||
type EventNameWithAll = EventName | 'all'
|
||||
// 定义监听器函数类型
|
||||
type Listener = (arg1: any, arg2: any) => void;
|
||||
type EventListener = (arg1: EventModel, arg2: any) => void;
|
||||
type Listener = (arg1: any, arg2: any) => void
|
||||
type EventListener = (arg1: EventModel, arg2: any) => void
|
||||
// --- 修正点: 确保 AllEventListener 定义符合要求 ---
|
||||
// AllEventListener 明确只接受一个参数
|
||||
type AllEventListener = (arg1: any) => void;
|
||||
type AllEventListener = (arg1: any) => void
|
||||
|
||||
// --- 修正点: 定义一个统一的监听器类型,用于内部实现签名 ---
|
||||
type GenericListener = Listener | AllEventListener;
|
||||
type GenericListener = Listener | AllEventListener
|
||||
|
||||
export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
// 使用 shallowRef 存储 danmakuClient 实例, 性能稍好
|
||||
const danmakuClient = shallowRef<BaseDanmakuClient | undefined>(new OpenLiveClient()); // 默认实例化一个 OpenLiveClient
|
||||
const danmakuClient = shallowRef<BaseDanmakuClient | undefined>(new OpenLiveClient()) // 默认实例化一个 OpenLiveClient
|
||||
|
||||
// 连接状态: 'waiting'-等待初始化, 'connecting'-连接中, 'connected'-已连接
|
||||
const state = ref<'waiting' | 'connecting' | 'connected'>('waiting');
|
||||
const state = ref<'waiting' | 'connecting' | 'connected'>('waiting')
|
||||
// 计算属性, 判断是否已连接
|
||||
const connected = computed(() => state.value === 'connected');
|
||||
const connected = computed(() => state.value === 'connected')
|
||||
// 存储开放平台认证信息 (如果使用 OpenLiveClient)
|
||||
const authInfo = ref<OpenLiveInfo>();
|
||||
const authInfo = ref<OpenLiveInfo>()
|
||||
|
||||
// 初始化锁, 防止并发初始化
|
||||
let isInitializing = false;
|
||||
let isInitializing = false
|
||||
|
||||
/**
|
||||
* @description 注册事件监听器 (特定于 OpenLiveClient 的原始事件 或 调用 onEvent)
|
||||
@@ -39,23 +41,23 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
* @remarks 对于 OpenLiveClient, 直接操作其内部 events; 对于其他客户端, 调用 onEvent.
|
||||
*/
|
||||
// --- 修正点: 保持重载签名不变 ---
|
||||
function onEvent(eventName: 'all', listener: AllEventListener): void;
|
||||
function onEvent(eventName: EventName, listener: Listener): void;
|
||||
function onEvent(eventName: 'all', listener: AllEventListener): void
|
||||
function onEvent(eventName: EventName, listener: Listener): void
|
||||
// --- 修正点: 实现签名使用联合类型,并在内部进行类型断言 ---
|
||||
function onEvent(eventName: keyof BaseDanmakuClient['eventsAsModel'], listener: GenericListener): void {
|
||||
if (!danmakuClient.value) {
|
||||
console.warn("[DanmakuClient] 尝试在客户端初始化之前调用 'onEvent'。");
|
||||
return;
|
||||
console.warn('[DanmakuClient] 尝试在客户端初始化之前调用 \'onEvent\'。')
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (eventName === 'all') {
|
||||
// 对于 'all' 事件, 直接使用 AllEventListener 类型
|
||||
danmakuClient.value.eventsAsModel[eventName].push(listener as AllEventListener);
|
||||
danmakuClient.value.eventsAsModel[eventName].push(listener as AllEventListener)
|
||||
} else {
|
||||
danmakuClient.value.eventsAsModel[eventName].push(listener);
|
||||
danmakuClient.value.eventsAsModel[eventName].push(listener)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[DanmakuClient] 注册事件监听器: ${eventName} 失败: ${error}`);
|
||||
console.error(`[DanmakuClient] 注册事件监听器: ${eventName} 失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,45 +68,44 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
* @remarks 监听器存储在 Store 中, 会在客户端重连后自动重新附加.
|
||||
*/
|
||||
// --- 修正点: 保持重载签名不变 ---
|
||||
function on(eventName: 'all', listener: AllEventListener): void;
|
||||
function on(eventName: EventName, listener: Listener): void;
|
||||
function on(eventName: 'all', listener: AllEventListener): void
|
||||
function on(eventName: EventName, listener: Listener): void
|
||||
// --- 修正点: 实现签名使用联合类型,并在内部进行类型断言 ---
|
||||
function on(eventName: EventNameWithAll, listener: GenericListener): void {
|
||||
if (!danmakuClient.value) {
|
||||
console.warn("[DanmakuClient] 尝试在客户端初始化之前调用 'on'。");
|
||||
return;
|
||||
console.warn('[DanmakuClient] 尝试在客户端初始化之前调用 \'on\'。')
|
||||
return
|
||||
}
|
||||
danmakuClient.value.eventsRaw[eventName].push(listener);
|
||||
danmakuClient.value.eventsRaw[eventName].push(listener)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 移除事件监听器 (模型化数据, 从 Store 中移除)
|
||||
* @param eventName 事件名称 ('danmaku', 'gift', 'sc', 'guard', 'all')
|
||||
* @param listener 要移除的回调函数
|
||||
*/
|
||||
// --- 修正点: 保持重载签名不变 ---
|
||||
function offEvent(eventName: 'all', listener: AllEventListener): void;
|
||||
function offEvent(eventName: EventName, listener: Listener): void;
|
||||
function offEvent(eventName: 'all', listener: AllEventListener): void
|
||||
function offEvent(eventName: EventName, listener: Listener): void
|
||||
// --- 修正点: 实现签名使用联合类型,并在内部进行类型断言 ---
|
||||
function offEvent(eventName: keyof BaseDanmakuClient['eventsRaw'], listener: GenericListener): void {
|
||||
if (!danmakuClient.value) {
|
||||
console.warn("[DanmakuClient] 尝试在客户端初始化之前调用 'offEvent'。");
|
||||
return;
|
||||
console.warn('[DanmakuClient] 尝试在客户端初始化之前调用 \'offEvent\'。')
|
||||
return
|
||||
}
|
||||
if (eventName === 'all') {
|
||||
// 对于 'all' 事件, 直接使用 AllEventListener 类型
|
||||
const modelListeners = danmakuClient.value.eventsAsModel[eventName] as AllEventListener[];
|
||||
const index = modelListeners.indexOf(listener as AllEventListener);
|
||||
const modelListeners = danmakuClient.value.eventsAsModel[eventName] as AllEventListener[]
|
||||
const index = modelListeners.indexOf(listener as AllEventListener)
|
||||
if (index > -1) {
|
||||
modelListeners.splice(index, 1);
|
||||
modelListeners.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
const index = danmakuClient.value.eventsAsModel[eventName].indexOf(listener);
|
||||
const index = danmakuClient.value.eventsAsModel[eventName].indexOf(listener)
|
||||
if (index > -1) {
|
||||
danmakuClient.value.eventsAsModel[eventName].splice(index, 1);
|
||||
danmakuClient.value.eventsAsModel[eventName].splice(index, 1)
|
||||
} else {
|
||||
console.warn(`[DanmakuClient] 试图移除未注册的监听器: ${listener}`);
|
||||
console.warn(`[DanmakuClient] 试图移除未注册的监听器: ${listener}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,17 +116,17 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
* @param listener 要移除的回调函数
|
||||
*/
|
||||
// --- 修正点: 保持重载签名不变 ---
|
||||
function off(eventName: 'all', listener: AllEventListener): void;
|
||||
function off(eventName: EventName, listener: Listener): void;
|
||||
function off(eventName: 'all', listener: AllEventListener): void
|
||||
function off(eventName: EventName, listener: Listener): void
|
||||
// --- 修正点: 实现签名使用联合类型,并在内部进行类型断言 ---
|
||||
function off(eventName: EventNameWithAll, listener: GenericListener): void {
|
||||
if (!danmakuClient.value) {
|
||||
console.warn("[DanmakuClient] 尝试在客户端初始化之前调用 'off'。");
|
||||
return;
|
||||
console.warn('[DanmakuClient] 尝试在客户端初始化之前调用 \'off\'。')
|
||||
return
|
||||
}
|
||||
const index = danmakuClient.value.eventsRaw[eventName].indexOf(listener);
|
||||
const index = danmakuClient.value.eventsRaw[eventName].indexOf(listener)
|
||||
if (index > -1) {
|
||||
danmakuClient.value.eventsRaw[eventName].splice(index, 1);
|
||||
danmakuClient.value.eventsRaw[eventName].splice(index, 1)
|
||||
}
|
||||
// 直接从 eventsRaw 中移除监听器
|
||||
}
|
||||
@@ -136,7 +137,7 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
* @returns
|
||||
*/
|
||||
async function initOpenlive(auth?: AuthInfo) {
|
||||
return initClient(new OpenLiveClient(auth));
|
||||
return initClient(new OpenLiveClient(auth))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,31 +146,29 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
* @returns
|
||||
*/
|
||||
async function initDirect(auth: DirectClientAuthInfo) {
|
||||
return initClient(new DirectClient(auth));
|
||||
return initClient(new DirectClient(auth))
|
||||
}
|
||||
|
||||
|
||||
// 辅助函数: 从客户端的 eventsAsModel 移除单个监听器
|
||||
// --- 修正点: 修正 detachListenerFromClient 的签名和实现以处理联合类型 ---
|
||||
function detachListenerFromClient(client: BaseDanmakuClient, eventName: EventNameWithAll, listener: GenericListener): void {
|
||||
if (client.eventsAsModel[eventName]) {
|
||||
if (eventName === 'all') {
|
||||
const modelListeners = client.eventsAsModel[eventName] as AllEventListener[];
|
||||
const index = modelListeners.indexOf(listener as AllEventListener);
|
||||
const modelListeners = client.eventsAsModel[eventName] as AllEventListener[]
|
||||
const index = modelListeners.indexOf(listener as AllEventListener)
|
||||
if (index > -1) {
|
||||
modelListeners.splice(index, 1);
|
||||
modelListeners.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
const modelListeners = client.eventsAsModel[eventName] as Listener[];
|
||||
const index = modelListeners.indexOf(listener as Listener);
|
||||
const modelListeners = client.eventsAsModel[eventName] as Listener[]
|
||||
const index = modelListeners.indexOf(listener as Listener)
|
||||
if (index > -1) {
|
||||
modelListeners.splice(index, 1);
|
||||
modelListeners.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 通用客户端初始化逻辑
|
||||
* @param client 要初始化的客户端实例
|
||||
@@ -179,122 +178,120 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
// 防止重复初始化或在非等待状态下初始化
|
||||
if (isInitializing) {
|
||||
while (isInitializing) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100)); // 等待初始化完成
|
||||
await new Promise(resolve => setTimeout(resolve, 100)) // 等待初始化完成
|
||||
}
|
||||
return useDanmakuClient(); // 如果已连接,则视为“成功”
|
||||
return useDanmakuClient() // 如果已连接,则视为“成功”
|
||||
}
|
||||
if (state.value === 'connected') {
|
||||
return useDanmakuClient(); // 如果已连接,则视为“成功”
|
||||
return useDanmakuClient() // 如果已连接,则视为“成功”
|
||||
}
|
||||
|
||||
isInitializing = true;
|
||||
state.value = 'connecting';
|
||||
console.log('[DanmakuClient] 开始初始化...');
|
||||
isInitializing = true
|
||||
state.value = 'connecting'
|
||||
console.log('[DanmakuClient] 开始初始化...')
|
||||
|
||||
|
||||
let oldEventsAsModel = danmakuClient.value?.eventsAsModel;
|
||||
let oldEventsRaw = danmakuClient.value?.eventsRaw;
|
||||
let oldEventsAsModel = danmakuClient.value?.eventsAsModel
|
||||
let oldEventsRaw = danmakuClient.value?.eventsRaw
|
||||
if (!oldEventsAsModel || Object.keys(oldEventsAsModel).length === 0) {
|
||||
oldEventsAsModel = client.createEmptyEventModelListeners();
|
||||
oldEventsAsModel = client.createEmptyEventModelListeners()
|
||||
}
|
||||
if (!oldEventsRaw || Object.keys(oldEventsRaw).length === 0) {
|
||||
oldEventsRaw = client.createEmptyRawEventlisteners();
|
||||
oldEventsRaw = client.createEmptyRawEventlisteners()
|
||||
}
|
||||
|
||||
// 先停止并清理旧客户端 (如果存在)
|
||||
if (danmakuClient.value) {
|
||||
console.log('[DanmakuClient] 正在处理旧的客户端实例...');
|
||||
console.log('[DanmakuClient] 正在处理旧的客户端实例...')
|
||||
if (danmakuClient.value.state === 'connected') {
|
||||
await disposeClientInstance(danmakuClient.value);
|
||||
await disposeClientInstance(danmakuClient.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置新的客户端实例
|
||||
danmakuClient.value = client;
|
||||
danmakuClient.value = client
|
||||
// 确保新客户端有空的监听器容器 (BaseDanmakuClient 应负责初始化)
|
||||
danmakuClient.value.eventsAsModel = oldEventsAsModel;
|
||||
danmakuClient.value.eventsRaw = oldEventsRaw;
|
||||
danmakuClient.value.eventsAsModel = oldEventsAsModel
|
||||
danmakuClient.value.eventsRaw = oldEventsRaw
|
||||
// 通常在 client 实例化或 Start 时处理,或者在 attachListenersToClient 中确保存在
|
||||
|
||||
|
||||
let connectSuccess = false;
|
||||
const maxRetries = 5; // Example: Limit retries
|
||||
let retryCount = 0;
|
||||
let connectSuccess = false
|
||||
const maxRetries = 5 // Example: Limit retries
|
||||
let retryCount = 0
|
||||
|
||||
const attemptConnect = async () => {
|
||||
if (!danmakuClient.value) return false; // Guard against client being disposed during wait
|
||||
if (!danmakuClient.value) return false // Guard against client being disposed during wait
|
||||
try {
|
||||
console.log(`[DanmakuClient] 尝试连接 (第 ${retryCount + 1} 次)...`);
|
||||
const result = await danmakuClient.value.Start(); // 启动连接
|
||||
console.log(`[DanmakuClient] 尝试连接 (第 ${retryCount + 1} 次)...`)
|
||||
const result = await danmakuClient.value.Start() // 启动连接
|
||||
if (result.success) {
|
||||
// 连接成功
|
||||
authInfo.value = danmakuClient.value instanceof OpenLiveClient ? danmakuClient.value.roomAuthInfo : undefined;
|
||||
state.value = 'connected';
|
||||
authInfo.value = danmakuClient.value instanceof OpenLiveClient ? danmakuClient.value.roomAuthInfo : undefined
|
||||
state.value = 'connected'
|
||||
// 将 Store 中存储的监听器 (来自 onEvent) 附加到新连接的客户端的 eventsAsModel
|
||||
console.log('[DanmakuClient] 初始化成功');
|
||||
connectSuccess = true;
|
||||
return true; // 连接成功, 退出重试循环
|
||||
console.log('[DanmakuClient] 初始化成功')
|
||||
connectSuccess = true
|
||||
return true // 连接成功, 退出重试循环
|
||||
} else {
|
||||
// 连接失败
|
||||
console.error(`[DanmakuClient] 连接尝试失败: ${result.message}`);
|
||||
return false; // 继续重试
|
||||
console.error(`[DanmakuClient] 连接尝试失败: ${result.message}`)
|
||||
return false // 继续重试
|
||||
}
|
||||
} catch (error) {
|
||||
// 捕获 Start() 可能抛出的异常
|
||||
console.error(`[DanmakuClient] 连接尝试期间发生异常:`, error);
|
||||
return false; // 继续重试
|
||||
console.error(`[DanmakuClient] 连接尝试期间发生异常:`, error)
|
||||
return false // 继续重试
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 循环尝试连接, 直到成功或达到重试次数
|
||||
while (!connectSuccess && retryCount < maxRetries) {
|
||||
if (state.value !== 'connecting') { // 检查状态是否在循环开始时改变
|
||||
console.log('[DanmakuClient] 初始化被外部中止。');
|
||||
isInitializing = false;
|
||||
//return false; // 初始化被中止
|
||||
break;
|
||||
console.log('[DanmakuClient] 初始化被外部中止。')
|
||||
isInitializing = false
|
||||
// return false; // 初始化被中止
|
||||
break
|
||||
}
|
||||
|
||||
if (!(await attemptConnect())) {
|
||||
retryCount++;
|
||||
retryCount++
|
||||
if (retryCount < maxRetries && state.value === 'connecting') {
|
||||
console.log(`[DanmakuClient] 5 秒后重试连接... (${retryCount}/${maxRetries})`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
console.log(`[DanmakuClient] 5 秒后重试连接... (${retryCount}/${maxRetries})`)
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
// 再次检查在等待期间状态是否改变
|
||||
if (state.value !== 'connecting') {
|
||||
console.log('[DanmakuClient] 在重试等待期间初始化被中止。');
|
||||
isInitializing = false;
|
||||
//return false; // 初始化被中止
|
||||
break;
|
||||
console.log('[DanmakuClient] 在重试等待期间初始化被中止。')
|
||||
isInitializing = false
|
||||
// return false; // 初始化被中止
|
||||
break
|
||||
}
|
||||
} else if (state.value === 'connecting') {
|
||||
console.error(`[DanmakuClient] 已达到最大连接重试次数 (${maxRetries})。初始化失败。`);
|
||||
console.error(`[DanmakuClient] 已达到最大连接重试次数 (${maxRetries})。初始化失败。`)
|
||||
// 连接失败,重置状态
|
||||
await dispose(); // 清理资源
|
||||
await dispose() // 清理资源
|
||||
// state.value = 'waiting'; // dispose 会设置状态
|
||||
// isInitializing = false; // dispose 会设置
|
||||
// return false; // 返回失败状态
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isInitializing = false; // 无论成功失败,初始化过程结束
|
||||
isInitializing = false // 无论成功失败,初始化过程结束
|
||||
// 返回最终的连接状态
|
||||
return useDanmakuClient();
|
||||
return useDanmakuClient()
|
||||
}
|
||||
|
||||
// 封装停止和清理客户端实例的逻辑
|
||||
async function disposeClientInstance(client: BaseDanmakuClient) {
|
||||
try {
|
||||
console.log('[DanmakuClient] 正在停止客户端实例...');
|
||||
client.Stop(); // 停止客户端连接和内部处理
|
||||
console.log('[DanmakuClient] 正在停止客户端实例...')
|
||||
client.Stop() // 停止客户端连接和内部处理
|
||||
// 可能需要添加额外的清理逻辑,例如移除所有监听器
|
||||
// client.eventsAsModel = client.createEmptyEventModelListeners(); // 清空监听器
|
||||
// client.eventsRaw = client.createEmptyRawEventlisteners(); // 清空监听器
|
||||
console.log('[DanmakuClient] 客户端实例已停止。');
|
||||
console.log('[DanmakuClient] 客户端实例已停止。')
|
||||
} catch (error) {
|
||||
console.error('[DanmakuClient] 停止客户端时出错:', error);
|
||||
console.error('[DanmakuClient] 停止客户端时出错:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,31 +299,31 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||
* @description 停止并清理当前客户端连接和资源
|
||||
*/
|
||||
async function dispose() {
|
||||
console.log('[DanmakuClient] 正在停止并清理客户端...');
|
||||
isInitializing = false; // 允许在 dispose 后重新初始化
|
||||
console.log('[DanmakuClient] 正在停止并清理客户端...')
|
||||
isInitializing = false // 允许在 dispose 后重新初始化
|
||||
|
||||
if (danmakuClient.value) {
|
||||
await disposeClientInstance(danmakuClient.value);
|
||||
//danmakuClient.value = undefined; // 保留, 用户再次获取event
|
||||
await disposeClientInstance(danmakuClient.value)
|
||||
// danmakuClient.value = undefined; // 保留, 用户再次获取event
|
||||
}
|
||||
state.value = 'waiting'; // 重置状态为等待
|
||||
authInfo.value = undefined; // 清理认证信息
|
||||
state.value = 'waiting' // 重置状态为等待
|
||||
authInfo.value = undefined // 清理认证信息
|
||||
// isInitializing = false; // 在函数开始处已设置
|
||||
console.log('[DanmakuClient] 已处理。');
|
||||
console.log('[DanmakuClient] 已处理。')
|
||||
// 注意: Store 中 listeners.value (来自 onEvent) 默认不清空, 以便重连后恢复
|
||||
}
|
||||
|
||||
return {
|
||||
danmakuClient, // 当前弹幕客户端实例 (shallowRef)
|
||||
state, // 连接状态 ('waiting', 'connecting', 'connected')
|
||||
authInfo, // OpenLive 认证信息 (ref)
|
||||
connected, // 是否已连接 (computed)
|
||||
onEvent, // 注册事件监听器 (模型化数据, 存储于 Store)
|
||||
offEvent, // 移除事件监听器 (模型化数据, 从 Store 移除)
|
||||
on, // 注册事件监听器 (直接操作 client.eventsRaw)
|
||||
off, // 移除事件监听器 (直接操作 client.eventsRaw 或调用 offEvent)
|
||||
initOpenlive, // 初始化 OpenLive 客户端
|
||||
initDirect, // 初始化 Direct 客户端
|
||||
dispose, // 停止并清理客户端
|
||||
};
|
||||
});
|
||||
state, // 连接状态 ('waiting', 'connecting', 'connected')
|
||||
authInfo, // OpenLive 认证信息 (ref)
|
||||
connected, // 是否已连接 (computed)
|
||||
onEvent, // 注册事件监听器 (模型化数据, 存储于 Store)
|
||||
offEvent, // 移除事件监听器 (模型化数据, 从 Store 移除)
|
||||
on, // 注册事件监听器 (直接操作 client.eventsRaw)
|
||||
off, // 移除事件监听器 (直接操作 client.eventsRaw 或调用 offEvent)
|
||||
initOpenlive, // 初始化 OpenLive 客户端
|
||||
initDirect, // 初始化 Direct 客户端
|
||||
dispose, // 停止并清理客户端
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||
import type {
|
||||
ForumCommentModel,
|
||||
ForumCommentSortTypes,
|
||||
ForumModel,
|
||||
@@ -8,12 +9,11 @@ import {
|
||||
ForumTopicModel,
|
||||
ForumTopicSortTypes,
|
||||
} from '@/api/models/forum'
|
||||
import { QueryGetAPI, QueryGetPaginationAPI, QueryPostAPI } from '@/api/query'
|
||||
import { FORUM_API_URL } from '@/data/constants'
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
import { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { QueryGetAPI, QueryGetPaginationAPI, QueryPostAPI } from '@/api/query'
|
||||
import { FORUM_API_URL } from '@/data/constants'
|
||||
|
||||
export const useForumStore = defineStore('forum', () => {
|
||||
const { message } = createDiscreteApi(['message'])
|
||||
@@ -28,15 +28,15 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function GetForumInfo(owner: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI<ForumModel>(FORUM_API_URL + 'get-forum', { owner: owner })
|
||||
const data = await QueryGetAPI<ForumModel>(`${FORUM_API_URL}get-forum`, { owner })
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
} else if (data.code != 404) {
|
||||
message?.error('无法获取数据: ' + data.message)
|
||||
message?.error(`无法获取数据: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -45,15 +45,15 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function GetManagedForums() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI<ForumModel[]>(FORUM_API_URL + 'get-managed-forums')
|
||||
const data = await QueryGetAPI<ForumModel[]>(`${FORUM_API_URL}get-managed-forums`)
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
} else {
|
||||
message?.error('无法获取数据: ' + data.message)
|
||||
message?.error(`无法获取数据: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -69,12 +69,12 @@ export const useForumStore = defineStore('forum', () => {
|
||||
) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetPaginationAPI<ForumTopicBaseModel[]>(FORUM_API_URL + 'get-topics', {
|
||||
const data = await QueryGetPaginationAPI<ForumTopicBaseModel[]>(`${FORUM_API_URL}get-topics`, {
|
||||
owner,
|
||||
pageSize: ps,
|
||||
page: pn,
|
||||
sort,
|
||||
section: section,
|
||||
section,
|
||||
})
|
||||
if (data.code == 200) {
|
||||
return {
|
||||
@@ -83,13 +83,13 @@ export const useForumStore = defineStore('forum', () => {
|
||||
more: data.more,
|
||||
}
|
||||
} else {
|
||||
message?.error('无法获取数据: ' + data.message)
|
||||
console.error('无法获取数据: ' + data.message)
|
||||
message?.error(`无法获取数据: ${data.message}`)
|
||||
console.error(`无法获取数据: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
console.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
console.error(`无法获取数据: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -98,17 +98,17 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function GetTopicDetail(topic: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI<ForumTopicModel>(FORUM_API_URL + 'get-topic', { topic: topic })
|
||||
const data = await QueryGetAPI<ForumTopicModel>(`${FORUM_API_URL}get-topic`, { topic })
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
} else {
|
||||
message?.error('无法获取数据: ' + data.message)
|
||||
console.error('无法获取数据: ' + data.message)
|
||||
message?.error(`无法获取数据: ${data.message}`)
|
||||
console.error(`无法获取数据: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
console.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
console.error(`无法获取数据: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -117,7 +117,7 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function GetComments(topic: number, pn: number, ps: number, sort: ForumCommentSortTypes) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetPaginationAPI<ForumCommentModel[]>(FORUM_API_URL + 'get-comments', {
|
||||
const data = await QueryGetPaginationAPI<ForumCommentModel[]>(`${FORUM_API_URL}get-comments`, {
|
||||
topic,
|
||||
pageSize: ps,
|
||||
page: pn,
|
||||
@@ -126,13 +126,13 @@ export const useForumStore = defineStore('forum', () => {
|
||||
if (data.code == 200) {
|
||||
return data
|
||||
} else {
|
||||
console.error('无法获取数据: ' + data.message)
|
||||
message?.error('无法获取数据: ' + data.message)
|
||||
console.error(`无法获取数据: ${data.message}`)
|
||||
message?.error(`无法获取数据: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('无法获取数据: ' + err)
|
||||
message?.error('无法获取数据: ' + err)
|
||||
console.error(`无法获取数据: ${err}`)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -141,18 +141,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function ApplyToForum(owner: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI<ForumModel>(FORUM_API_URL + 'apply', { owner: owner })
|
||||
const data = await QueryGetAPI<ForumModel>(`${FORUM_API_URL}apply`, { owner })
|
||||
if (data.code == 200) {
|
||||
message?.success('已提交申请, 等待管理员审核')
|
||||
return true
|
||||
} else {
|
||||
message?.error('无法获取数据: ' + data.message)
|
||||
console.error('无法获取数据: ' + data.message)
|
||||
message?.error(`无法获取数据: ${data.message}`)
|
||||
console.error(`无法获取数据: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
console.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
console.error(`无法获取数据: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -162,58 +162,58 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function PostTopic(topic: ForumPostTopicModel, token: string) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryPostAPI<ForumTopicModel>(FORUM_API_URL + 'post-topic', topic, [['Turnstile', token]])
|
||||
const data = await QueryPostAPI<ForumTopicModel>(`${FORUM_API_URL}post-topic`, topic, [['Turnstile', token]])
|
||||
if (data.code == 200) {
|
||||
message?.success('发布成功')
|
||||
return data.data
|
||||
} else {
|
||||
message?.error('发布失败: ' + data.message)
|
||||
console.error('发布失败: ' + data.message)
|
||||
message?.error(`发布失败: ${data.message}`)
|
||||
console.error(`发布失败: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('发布失败: ' + err)
|
||||
console.error('发布失败: ' + err)
|
||||
message?.error(`发布失败: ${err}`)
|
||||
console.error(`发布失败: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
async function PostComment(model: { topic: number; content: string }, token: string) {
|
||||
async function PostComment(model: { topic: number, content: string }, token: string) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryPostAPI<ForumCommentModel>(FORUM_API_URL + 'post-comment', model, [['Turnstile', token]])
|
||||
const data = await QueryPostAPI<ForumCommentModel>(`${FORUM_API_URL}post-comment`, model, [['Turnstile', token]])
|
||||
if (data.code == 200) {
|
||||
message?.success('评论成功')
|
||||
return data.data
|
||||
} else {
|
||||
message?.error('评论失败: ' + data.message)
|
||||
console.error('评论失败: ' + data.message)
|
||||
message?.error(`评论失败: ${data.message}`)
|
||||
console.error(`评论失败: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('评论失败: ' + err)
|
||||
console.error('评论失败: ' + err)
|
||||
message?.error(`评论失败: ${err}`)
|
||||
console.error(`评论失败: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
async function PostReply(model: { comment: number; content: string; replyTo?: number }, token: string) {
|
||||
async function PostReply(model: { comment: number, content: string, replyTo?: number }, token: string) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryPostAPI<ForumCommentModel>(FORUM_API_URL + 'post-reply', model, [['Turnstile', token]])
|
||||
const data = await QueryPostAPI<ForumCommentModel>(`${FORUM_API_URL}post-reply`, model, [['Turnstile', token]])
|
||||
if (data.code == 200) {
|
||||
message?.success('评论成功')
|
||||
return data.data
|
||||
} else {
|
||||
message?.error('评论失败: ' + data.message)
|
||||
console.error('评论失败: ' + data.message)
|
||||
message?.error(`评论失败: ${data.message}`)
|
||||
console.error(`评论失败: ${data.message}`)
|
||||
return undefined
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('评论失败: ' + err)
|
||||
console.error('评论失败: ' + err)
|
||||
message?.error(`评论失败: ${err}`)
|
||||
console.error(`评论失败: ${err}`)
|
||||
return undefined
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -222,18 +222,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function LikeTopic(topic: number, like: boolean) {
|
||||
try {
|
||||
isLikeLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'like-topic', { topic: topic, like: like })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}like-topic`, { topic, like })
|
||||
if (data.code == 200) {
|
||||
//message?.success('已点赞')
|
||||
// message?.success('已点赞')
|
||||
return true
|
||||
} else {
|
||||
message?.error('点赞失败: ' + data.message)
|
||||
console.error('点赞失败: ' + data.message)
|
||||
message?.error(`点赞失败: ${data.message}`)
|
||||
console.error(`点赞失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('点赞失败: ' + err)
|
||||
console.error('点赞失败: ' + err)
|
||||
message?.error(`点赞失败: ${err}`)
|
||||
console.error(`点赞失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLikeLoading.value = false
|
||||
@@ -242,18 +242,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function LikeComment(comment: number, like: boolean) {
|
||||
try {
|
||||
isLikeLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'like-comment', { comment: comment, like: like })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}like-comment`, { comment, like })
|
||||
if (data.code == 200) {
|
||||
//message?.success('已点赞')
|
||||
// message?.success('已点赞')
|
||||
return true
|
||||
} else {
|
||||
message?.error('点赞失败: ' + data.message)
|
||||
console.error('点赞失败: ' + data.message)
|
||||
message?.error(`点赞失败: ${data.message}`)
|
||||
console.error(`点赞失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('点赞失败: ' + err)
|
||||
console.error('点赞失败: ' + err)
|
||||
message?.error(`点赞失败: ${err}`)
|
||||
console.error(`点赞失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLikeLoading.value = false
|
||||
@@ -277,18 +277,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function SetTopicTop(topic: number, top: boolean) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'manage/set-topic-top', { topic: topic, top: top })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}manage/set-topic-top`, { topic, top })
|
||||
if (data.code == 200) {
|
||||
message?.success('完成')
|
||||
return true
|
||||
} else {
|
||||
message?.error('操作失败: ' + data.message)
|
||||
console.error('操作失败: ' + data.message)
|
||||
message?.error(`操作失败: ${data.message}`)
|
||||
console.error(`操作失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('操作失败: ' + err)
|
||||
console.error('操作失败: ' + err)
|
||||
message?.error(`操作失败: ${err}`)
|
||||
console.error(`操作失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -297,18 +297,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function DelTopic(topic: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'manage/delete-topic', { topic: topic })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}manage/delete-topic`, { topic })
|
||||
if (data.code == 200) {
|
||||
message?.success('删除成功')
|
||||
return true
|
||||
} else {
|
||||
message?.error('删除失败: ' + data.message)
|
||||
console.error('删除失败: ' + data.message)
|
||||
message?.error(`删除失败: ${data.message}`)
|
||||
console.error(`删除失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('删除失败: ' + err)
|
||||
console.error('删除失败: ' + err)
|
||||
message?.error(`删除失败: ${err}`)
|
||||
console.error(`删除失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -317,18 +317,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function DelComment(comment: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'delete-comment', { comment: comment })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}delete-comment`, { comment })
|
||||
if (data.code == 200) {
|
||||
message?.success('删除成功')
|
||||
return true
|
||||
} else {
|
||||
message?.error('删除失败: ' + data.message)
|
||||
console.error('删除失败: ' + data.message)
|
||||
message?.error(`删除失败: ${data.message}`)
|
||||
console.error(`删除失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('删除失败: ' + err)
|
||||
console.error('删除失败: ' + err)
|
||||
message?.error(`删除失败: ${err}`)
|
||||
console.error(`删除失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -337,18 +337,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function DelReply(reply: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'manage/delete-reply', { reply: reply })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}manage/delete-reply`, { reply })
|
||||
if (data.code == 200) {
|
||||
message?.success('删除成功')
|
||||
return true
|
||||
} else {
|
||||
message?.error('删除失败: ' + data.message)
|
||||
console.error('删除失败: ' + data.message)
|
||||
message?.error(`删除失败: ${data.message}`)
|
||||
console.error(`删除失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('删除失败: ' + err)
|
||||
console.error('删除失败: ' + err)
|
||||
message?.error(`删除失败: ${err}`)
|
||||
console.error(`删除失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -357,18 +357,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function RestoreComment(comment: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'manage/restore-comment', { comment: comment })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}manage/restore-comment`, { comment })
|
||||
if (data.code == 200) {
|
||||
message?.success('恢复成功')
|
||||
return true
|
||||
} else {
|
||||
message?.error('恢复失败: ' + data.message)
|
||||
console.error('恢复失败: ' + data.message)
|
||||
message?.error(`恢复失败: ${data.message}`)
|
||||
console.error(`恢复失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('恢复失败: ' + err)
|
||||
console.error('恢复失败: ' + err)
|
||||
message?.error(`恢复失败: ${err}`)
|
||||
console.error(`恢复失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -377,18 +377,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function RestoreTopic(topic: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'manage/restore-topic', { topic })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}manage/restore-topic`, { topic })
|
||||
if (data.code == 200) {
|
||||
message?.success('恢复成功')
|
||||
return true
|
||||
} else {
|
||||
message?.error('恢复失败: ' + data.message)
|
||||
console.error('恢复失败: ' + data.message)
|
||||
message?.error(`恢复失败: ${data.message}`)
|
||||
console.error(`恢复失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('恢复失败: ' + err)
|
||||
console.error('恢复失败: ' + err)
|
||||
message?.error(`恢复失败: ${err}`)
|
||||
console.error(`恢复失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -397,18 +397,18 @@ export const useForumStore = defineStore('forum', () => {
|
||||
async function ConfirmApply(owner: number, id: number) {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await QueryGetAPI(FORUM_API_URL + 'manage/confirm-apply', { forum: owner, id: id })
|
||||
const data = await QueryGetAPI(`${FORUM_API_URL}manage/confirm-apply`, { forum: owner, id })
|
||||
if (data.code == 200) {
|
||||
message?.success('已通过申请')
|
||||
return true
|
||||
} else {
|
||||
message?.error('确认失败: ' + data.message)
|
||||
console.error('确认失败: ' + data.message)
|
||||
message?.error(`确认失败: ${data.message}`)
|
||||
console.error(`确认失败: ${data.message}`)
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('确认失败: ' + err)
|
||||
console.error('确认失败: ' + err)
|
||||
message?.error(`确认失败: ${err}`)
|
||||
console.error(`确认失败: ${err}`)
|
||||
return false
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
|
||||
@@ -1,59 +1,57 @@
|
||||
// src/composables/useGamepad.ts
|
||||
import { ref, onMounted, onUnmounted, reactive, readonly, Ref, DeepReadonly, computed, watch } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useGamepad } from '@vueuse/core';
|
||||
import type {
|
||||
GamepadConnectionInfo,
|
||||
RawGamepadState,
|
||||
NormalizedGamepadState,
|
||||
LogicalButton,
|
||||
ButtonInputState,
|
||||
StickInputState
|
||||
} from '@/types/gamepad'; // 使用 @ 指向 src 目录
|
||||
import { LogicalButtonsList } from '@/types/gamepad';
|
||||
GamepadConnectionInfo,
|
||||
LogicalButton,
|
||||
NormalizedGamepadState,
|
||||
} from '@/types/gamepad' // 使用 @ 指向 src 目录
|
||||
import { useGamepad } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
// src/composables/useGamepad.ts
|
||||
import { computed, reactive, readonly, ref, watch } from 'vue'
|
||||
import { LogicalButtonsList } from '@/types/gamepad'
|
||||
|
||||
// 定义事件处理函数类型
|
||||
type GamepadEventHandler = (gamepadInfo: GamepadConnectionInfo, index: number) => void;
|
||||
type GamepadEventHandler = (gamepadInfo: GamepadConnectionInfo, index: number) => void
|
||||
|
||||
// 标准按钮映射,根据标准布局将gamepad API的按钮索引映射到逻辑按钮
|
||||
const standardButtonMap: Partial<Record<number, LogicalButton>> = {
|
||||
0: 'ACTION_DOWN', // Xbox A / PS Cross / Nintendo B
|
||||
1: 'ACTION_RIGHT', // Xbox B / PS Circle / Nintendo A
|
||||
2: 'ACTION_LEFT', // Xbox X / PS Square / Nintendo Y
|
||||
3: 'ACTION_UP', // Xbox Y / PS Triangle / Nintendo X
|
||||
0: 'ACTION_DOWN', // Xbox A / PS Cross / Nintendo B
|
||||
1: 'ACTION_RIGHT', // Xbox B / PS Circle / Nintendo A
|
||||
2: 'ACTION_LEFT', // Xbox X / PS Square / Nintendo Y
|
||||
3: 'ACTION_UP', // Xbox Y / PS Triangle / Nintendo X
|
||||
4: 'LEFT_SHOULDER_1', // LB / L1
|
||||
5: 'RIGHT_SHOULDER_1', // RB / R1
|
||||
6: 'LEFT_SHOULDER_2', // LT / L2 (触发器)
|
||||
6: 'LEFT_SHOULDER_2', // LT / L2 (触发器)
|
||||
7: 'RIGHT_SHOULDER_2', // RT / R2 (触发器)
|
||||
8: 'SELECT', // Xbox View / PS Select / Nintendo -
|
||||
9: 'START', // Xbox Menu / PS Start / Nintendo +
|
||||
8: 'SELECT', // Xbox View / PS Select / Nintendo -
|
||||
9: 'START', // Xbox Menu / PS Start / Nintendo +
|
||||
10: 'LEFT_STICK_PRESS', // 左摇杆按下
|
||||
11: 'RIGHT_STICK_PRESS', // 右摇杆按下
|
||||
12: 'DPAD_UP', // 方向键上
|
||||
13: 'DPAD_DOWN', // 方向键下
|
||||
14: 'DPAD_LEFT', // 方向键左
|
||||
15: 'DPAD_RIGHT', // 方向键右
|
||||
16: 'HOME', // Xbox Home / PS Home / Nintendo Home
|
||||
17: 'PS_TOUCHPAD', // PS触摸板按下
|
||||
12: 'DPAD_UP', // 方向键上
|
||||
13: 'DPAD_DOWN', // 方向键下
|
||||
14: 'DPAD_LEFT', // 方向键左
|
||||
15: 'DPAD_RIGHT', // 方向键右
|
||||
16: 'HOME', // Xbox Home / PS Home / Nintendo Home
|
||||
17: 'PS_TOUCHPAD', // PS触摸板按下
|
||||
// 18可能对应任天堂的截图按钮
|
||||
18: 'NINTENDO_CAPTURE'
|
||||
};
|
||||
18: 'NINTENDO_CAPTURE',
|
||||
}
|
||||
|
||||
export const useGamepadStore = defineStore('gamepad', () => {
|
||||
const { gamepads, onConnected, onDisconnected } = useGamepad();
|
||||
const { gamepads, onConnected, onDisconnected } = useGamepad()
|
||||
|
||||
const connectedGamepadInfo = ref<GamepadConnectionInfo | null>(null);
|
||||
const activeGamepadIndex = ref<number | null>(null);
|
||||
const connectedGamepadInfo = ref<GamepadConnectionInfo | null>(null)
|
||||
const activeGamepadIndex = ref<number | null>(null)
|
||||
|
||||
// 存储自定义事件处理器
|
||||
const connectedHandlers: Set<GamepadEventHandler> = new Set();
|
||||
const disconnectedHandlers: Set<GamepadEventHandler> = new Set();
|
||||
const connectedHandlers: Set<GamepadEventHandler> = new Set()
|
||||
const disconnectedHandlers: Set<GamepadEventHandler> = new Set()
|
||||
|
||||
// 初始化所有按钮状态
|
||||
const initialButtonStates = LogicalButtonsList.reduce((acc, key) => {
|
||||
acc[key] = { pressed: false, value: 0 };
|
||||
return acc;
|
||||
}, {} as Record<LogicalButton, ButtonInputState>);
|
||||
acc[key] = { pressed: false, value: 0 }
|
||||
return acc
|
||||
}, {} as Record<LogicalButton, ButtonInputState>)
|
||||
|
||||
// 手柄状态,包含按钮和摇杆
|
||||
const normalizedGamepadState = reactive<NormalizedGamepadState>({
|
||||
@@ -62,123 +60,123 @@ export const useGamepadStore = defineStore('gamepad', () => {
|
||||
LEFT_STICK: { x: 0, y: 0 },
|
||||
RIGHT_STICK: { x: 0, y: 0 },
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
// 计算属性:手柄是否已连接
|
||||
const isGamepadConnected = computed(() => activeGamepadIndex.value !== null && !!gamepads.value[activeGamepadIndex.value]?.connected);
|
||||
const isGamepadConnected = computed(() => activeGamepadIndex.value !== null && !!gamepads.value[activeGamepadIndex.value]?.connected)
|
||||
|
||||
// 重置手柄状态
|
||||
const resetNormalizedState = () => {
|
||||
Object.keys(normalizedGamepadState.buttons).forEach(key => {
|
||||
const buttonKey = key as LogicalButton;
|
||||
if (normalizedGamepadState.buttons[buttonKey]) {
|
||||
normalizedGamepadState.buttons[buttonKey]!.pressed = false;
|
||||
normalizedGamepadState.buttons[buttonKey]!.value = 0;
|
||||
}
|
||||
});
|
||||
normalizedGamepadState.sticks.LEFT_STICK = { x: 0, y: 0 };
|
||||
normalizedGamepadState.sticks.RIGHT_STICK = { x: 0, y: 0 };
|
||||
};
|
||||
Object.keys(normalizedGamepadState.buttons).forEach((key) => {
|
||||
const buttonKey = key as LogicalButton
|
||||
if (normalizedGamepadState.buttons[buttonKey]) {
|
||||
normalizedGamepadState.buttons[buttonKey].pressed = false
|
||||
normalizedGamepadState.buttons[buttonKey].value = 0
|
||||
}
|
||||
})
|
||||
normalizedGamepadState.sticks.LEFT_STICK = { x: 0, y: 0 }
|
||||
normalizedGamepadState.sticks.RIGHT_STICK = { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
// 更新手柄状态
|
||||
const updateNormalizedState = (gamepad: Gamepad | undefined) => {
|
||||
if (!gamepad || !gamepad.connected) {
|
||||
resetNormalizedState();
|
||||
return;
|
||||
resetNormalizedState()
|
||||
return
|
||||
}
|
||||
|
||||
// 更新按钮状态
|
||||
gamepad.buttons.forEach((button, index) => {
|
||||
const logicalKey = standardButtonMap[index];
|
||||
const logicalKey = standardButtonMap[index]
|
||||
if (logicalKey && normalizedGamepadState.buttons[logicalKey]) {
|
||||
normalizedGamepadState.buttons[logicalKey]!.pressed = button.pressed;
|
||||
normalizedGamepadState.buttons[logicalKey]!.value = button.value;
|
||||
normalizedGamepadState.buttons[logicalKey].pressed = button.pressed
|
||||
normalizedGamepadState.buttons[logicalKey].value = button.value
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 更新摇杆状态
|
||||
normalizedGamepadState.sticks.LEFT_STICK.x = gamepad.axes[0] ?? 0;
|
||||
normalizedGamepadState.sticks.LEFT_STICK.y = gamepad.axes[1] ?? 0;
|
||||
normalizedGamepadState.sticks.RIGHT_STICK.x = gamepad.axes[2] ?? 0;
|
||||
normalizedGamepadState.sticks.RIGHT_STICK.y = gamepad.axes[3] ?? 0;
|
||||
};
|
||||
normalizedGamepadState.sticks.LEFT_STICK.x = gamepad.axes[0] ?? 0
|
||||
normalizedGamepadState.sticks.LEFT_STICK.y = gamepad.axes[1] ?? 0
|
||||
normalizedGamepadState.sticks.RIGHT_STICK.x = gamepad.axes[2] ?? 0
|
||||
normalizedGamepadState.sticks.RIGHT_STICK.y = gamepad.axes[3] ?? 0
|
||||
}
|
||||
|
||||
// 手柄连接事件处理
|
||||
onConnected((index: number) => {
|
||||
const gamepad = navigator.getGamepads()[index];
|
||||
if (!gamepad) return;
|
||||
const gamepad = navigator.getGamepads()[index]
|
||||
if (!gamepad) return
|
||||
|
||||
console.log('手柄已连接:', gamepad.id, '索引:', index);
|
||||
console.log('手柄已连接:', gamepad.id, '索引:', index)
|
||||
// 如果当前没有活动的,或者连接的是同一个,则激活
|
||||
if (activeGamepadIndex.value === null || activeGamepadIndex.value === index) {
|
||||
activeGamepadIndex.value = index;
|
||||
activeGamepadIndex.value = index
|
||||
connectedGamepadInfo.value = {
|
||||
id: gamepad.id,
|
||||
mapping: gamepad.mapping
|
||||
};
|
||||
mapping: gamepad.mapping,
|
||||
}
|
||||
|
||||
// 触发外部注册的连接事件处理器
|
||||
if (connectedGamepadInfo.value) {
|
||||
connectedHandlers.forEach(handler => {
|
||||
connectedHandlers.forEach((handler) => {
|
||||
try {
|
||||
handler(connectedGamepadInfo.value!, index);
|
||||
handler(connectedGamepadInfo.value!, index)
|
||||
} catch (err) {
|
||||
console.error('手柄连接事件处理器执行错误:', err);
|
||||
console.error('手柄连接事件处理器执行错误:', err)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 如果已有活动手柄,而新连接的手柄不是当前活动的,则忽略,或按需处理(例如允许切换)
|
||||
console.log(`另一个手柄 (索引: ${activeGamepadIndex.value}) 已经处于活动状态`);
|
||||
console.log(`另一个手柄 (索引: ${activeGamepadIndex.value}) 已经处于活动状态`)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 手柄断开连接事件处理
|
||||
onDisconnected((index: number) => {
|
||||
const gamepadCache = gamepads.value[index];
|
||||
if (!gamepadCache) return;
|
||||
const gamepadCache = gamepads.value[index]
|
||||
if (!gamepadCache) return
|
||||
|
||||
console.log('手柄已断开连接:', gamepadCache.id);
|
||||
console.log('手柄已断开连接:', gamepadCache.id)
|
||||
|
||||
// 保存断开连接前的信息,用于触发事件
|
||||
const disconnectedInfo = connectedGamepadInfo.value ? { ...connectedGamepadInfo.value } : null;
|
||||
const disconnectedInfo = connectedGamepadInfo.value ? { ...connectedGamepadInfo.value } : null
|
||||
|
||||
if (activeGamepadIndex.value === index) {
|
||||
activeGamepadIndex.value = null;
|
||||
connectedGamepadInfo.value = null;
|
||||
resetNormalizedState();
|
||||
activeGamepadIndex.value = null
|
||||
connectedGamepadInfo.value = null
|
||||
resetNormalizedState()
|
||||
|
||||
// 触发外部注册的断开连接事件处理器
|
||||
if (disconnectedInfo) {
|
||||
disconnectedHandlers.forEach(handler => {
|
||||
disconnectedHandlers.forEach((handler) => {
|
||||
try {
|
||||
handler(disconnectedInfo, index);
|
||||
handler(disconnectedInfo, index)
|
||||
} catch (err) {
|
||||
console.error('手柄断开连接事件处理器执行错误:', err);
|
||||
console.error('手柄断开连接事件处理器执行错误:', err)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 尝试连接其他已连接的手柄 (VueUse 的 gamepads 数组会自动更新)
|
||||
const nextGamepad = gamepads.value.find(gp => gp && gp.connected);
|
||||
const nextGamepad = gamepads.value.find(gp => gp && gp.connected)
|
||||
if (nextGamepad) {
|
||||
activeGamepadIndex.value = nextGamepad.index;
|
||||
activeGamepadIndex.value = nextGamepad.index
|
||||
connectedGamepadInfo.value = {
|
||||
id: nextGamepad.id,
|
||||
mapping: nextGamepad.mapping
|
||||
};
|
||||
mapping: nextGamepad.mapping,
|
||||
}
|
||||
|
||||
// 如果自动切换到其他手柄,也触发连接事件
|
||||
connectedHandlers.forEach(handler => {
|
||||
connectedHandlers.forEach((handler) => {
|
||||
try {
|
||||
handler(connectedGamepadInfo.value!, nextGamepad.index);
|
||||
handler(connectedGamepadInfo.value!, nextGamepad.index)
|
||||
} catch (err) {
|
||||
console.error('手柄连接事件处理器执行错误:', err);
|
||||
console.error('手柄连接事件处理器执行错误:', err)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 监视 VueUse 的 gamepads 数组中的活动手柄
|
||||
// VueUse 内部使用 rAF 来更新 gamepads 数组中的状态
|
||||
@@ -187,38 +185,38 @@ export const useGamepadStore = defineStore('gamepad', () => {
|
||||
// 确保 activeGamepadIndex.value 不为 null,并且对应的 gamepad 存在
|
||||
return activeGamepadIndex.value !== null && gamepads.value[activeGamepadIndex.value]
|
||||
? gamepads.value[activeGamepadIndex.value]
|
||||
: undefined;
|
||||
: undefined
|
||||
},
|
||||
(activePad) => {
|
||||
updateNormalizedState(activePad);
|
||||
updateNormalizedState(activePad)
|
||||
},
|
||||
{ deep: true, immediate: true } // immediate: true 保证初始状态也被处理
|
||||
);
|
||||
{ deep: true, immediate: true }, // immediate: true 保证初始状态也被处理
|
||||
)
|
||||
|
||||
// 对外提供的连接事件注册方法
|
||||
const onGamepadConnected = (handler: GamepadEventHandler) => {
|
||||
connectedHandlers.add(handler);
|
||||
connectedHandlers.add(handler)
|
||||
|
||||
// 如果当前已有连接的手柄,立即触发一次事件
|
||||
if (isGamepadConnected.value && connectedGamepadInfo.value && activeGamepadIndex.value !== null) {
|
||||
handler(connectedGamepadInfo.value, activeGamepadIndex.value);
|
||||
handler(connectedGamepadInfo.value, activeGamepadIndex.value)
|
||||
}
|
||||
|
||||
// 返回取消注册的函数
|
||||
return () => {
|
||||
connectedHandlers.delete(handler);
|
||||
};
|
||||
};
|
||||
connectedHandlers.delete(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// 对外提供的断开连接事件注册方法
|
||||
const onGamepadDisconnected = (handler: GamepadEventHandler) => {
|
||||
disconnectedHandlers.add(handler);
|
||||
disconnectedHandlers.add(handler)
|
||||
|
||||
// 返回取消注册的函数
|
||||
return () => {
|
||||
disconnectedHandlers.delete(handler);
|
||||
};
|
||||
};
|
||||
disconnectedHandlers.delete(handler)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
connectedGamepadInfo: readonly(connectedGamepadInfo),
|
||||
@@ -226,5 +224,5 @@ export const useGamepadStore = defineStore('gamepad', () => {
|
||||
isGamepadConnected: readonly(isGamepadConnected),
|
||||
onConnected: onGamepadConnected,
|
||||
onDisconnected: onGamepadDisconnected,
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LoadingBarApi } from 'naive-ui'
|
||||
import { defineStore } from 'pinia'
|
||||
import { LoadingBarApi } from 'naive-ui'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useLoadingBarStore = defineStore('provider', () => {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { DanmakuUserInfo, SongFrom, SongsInfo } from '@/api/api-models'
|
||||
import { createNaiveUIApi, theme } from '@/Utils'
|
||||
import type { DanmakuUserInfo, SongsInfo } from '@/api/api-models'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { ConfigProviderProps, createDiscreteApi, zhCN } from 'naive-ui'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { SongFrom } from '@/api/api-models'
|
||||
|
||||
export type WaitMusicInfo = {
|
||||
export interface WaitMusicInfo {
|
||||
from: DanmakuUserInfo
|
||||
music: SongsInfo
|
||||
}
|
||||
export type Music = {
|
||||
export interface Music {
|
||||
id: number
|
||||
title: string
|
||||
artist: string
|
||||
@@ -17,7 +16,7 @@ export type Music = {
|
||||
pic: string
|
||||
lrc: string
|
||||
}
|
||||
export type MusicRequestSettings = {
|
||||
export interface MusicRequestSettings {
|
||||
playMusicWhenFree: boolean
|
||||
|
||||
repeat: 'repeat-one' | 'repeat-all' | 'no-repeat'
|
||||
@@ -37,11 +36,11 @@ export type MusicRequestSettings = {
|
||||
export const useMusicRequestProvider = defineStore('MusicRequest', () => {
|
||||
const waitingMusics = useStorage<WaitMusicInfo[]>(
|
||||
'Setting.MusicRequest.Waiting',
|
||||
[]
|
||||
[],
|
||||
)
|
||||
const originMusics = ref<SongsInfo[]>([])
|
||||
const aplayerMusics = computed(() =>
|
||||
originMusics.value.map((m) => songToMusic(m))
|
||||
originMusics.value.map(m => songToMusic(m)),
|
||||
)
|
||||
const currentMusic = ref<Music>({
|
||||
id: -1,
|
||||
@@ -49,7 +48,7 @@ export const useMusicRequestProvider = defineStore('MusicRequest', () => {
|
||||
artist: '',
|
||||
src: '',
|
||||
pic: '',
|
||||
lrc: ''
|
||||
lrc: '',
|
||||
} as Music)
|
||||
const currentOriginMusic = ref<WaitMusicInfo>()
|
||||
const isPlayingOrderMusic = ref(false)
|
||||
@@ -66,28 +65,28 @@ export const useMusicRequestProvider = defineStore('MusicRequest', () => {
|
||||
orderMusicFirst: true,
|
||||
platform: 'netease',
|
||||
|
||||
blacklist: []
|
||||
blacklist: [],
|
||||
})
|
||||
|
||||
const message = window.$message
|
||||
const message = window.$message
|
||||
|
||||
function addWaitingMusic(info: WaitMusicInfo) {
|
||||
if (
|
||||
(settings.value.orderMusicFirst && !isPlayingOrderMusic.value) ||
|
||||
aplayerRef.value?.audio.paused == true
|
||||
(settings.value.orderMusicFirst && !isPlayingOrderMusic.value)
|
||||
|| aplayerRef.value?.audio.paused == true
|
||||
) {
|
||||
playMusic(info.music)
|
||||
isPlayingOrderMusic.value = true
|
||||
console.log(
|
||||
`正在播放 [${info.from.name}] 点的 ${info.music.name} - ${info.music.author?.join('/')}`
|
||||
`正在播放 [${info.from.name}] 点的 ${info.music.name} - ${info.music.author?.join('/')}`,
|
||||
)
|
||||
message.success(
|
||||
`正在播放 [${info.from.name}] 点的 ${info.music.name} - ${info.music.author?.join('/')}`
|
||||
`正在播放 [${info.from.name}] 点的 ${info.music.name} - ${info.music.author?.join('/')}`,
|
||||
)
|
||||
} else {
|
||||
waitingMusics.value.push(info)
|
||||
message.success(
|
||||
`[${info.from.name}] 点了一首 ${info.music.name} - ${info.music.author?.join('/')}`
|
||||
`[${info.from.name}] 点了一首 ${info.music.name} - ${info.music.author?.join('/')}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -128,7 +127,7 @@ const message = window.$message
|
||||
}
|
||||
}
|
||||
function playMusic(music: SongsInfo) {
|
||||
//pauseMusic()
|
||||
// pauseMusic()
|
||||
currentMusic.value = songToMusic(music)
|
||||
aplayerRef.value?.thenPlay()
|
||||
}
|
||||
@@ -147,16 +146,16 @@ const message = window.$message
|
||||
? `https://music.163.com/song/media/outer/url?id=${s.id}.mp3`
|
||||
: s.url,
|
||||
pic: s.cover ?? '',
|
||||
lrc: ''
|
||||
lrc: '',
|
||||
} as Music
|
||||
}
|
||||
function setSinkId() {
|
||||
try {
|
||||
aplayerRef.value?.audio.setSinkId(settings.value.deviceId ?? 'default')
|
||||
console.log('设置音频输出设备为 ' + (settings.value.deviceId ?? '默认'))
|
||||
console.log(`设置音频输出设备为 ${settings.value.deviceId ?? '默认'}`)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
message.error('设置音频输出设备失败: ' + err)
|
||||
message.error(`设置音频输出设备失败: ${err}`)
|
||||
}
|
||||
}
|
||||
function nextMusic() {
|
||||
@@ -184,6 +183,6 @@ const message = window.$message
|
||||
onMusicPlay,
|
||||
pauseMusic,
|
||||
nextMusic,
|
||||
aplayerRef
|
||||
aplayerRef,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { NOTIFACTION_API_URL } from '@/data/constants'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { NOTIFACTION_API_URL } from '@/data/constants'
|
||||
|
||||
export type NotificationData = {
|
||||
export interface NotificationData {
|
||||
title: string
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export const useNotificationStore = defineStore('notification', () => {
|
||||
return // 暂时没写这部分相关逻辑
|
||||
try {
|
||||
const result = await QueryGetAPI<NotificationData[]>(
|
||||
NOTIFACTION_API_URL + 'get-unread'
|
||||
`${NOTIFACTION_API_URL}get-unread`,
|
||||
)
|
||||
if (result.code == 200) {
|
||||
unread.value = result.data
|
||||
@@ -40,6 +40,6 @@ export const useNotificationStore = defineStore('notification', () => {
|
||||
}
|
||||
return {
|
||||
init,
|
||||
unread
|
||||
unread,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useAccount } from '@/api/account'
|
||||
import { ConsumptionTypes, IDeductionSetting, UserConsumptionSetting } from '@/api/models/consumption'
|
||||
import { QueryPostAPIWithParams } from '@/api/query'
|
||||
import { ACCOUNT_API_URL } from '@/data/constants'
|
||||
import type { IDeductionSetting, UserConsumptionSetting } from '@/api/models/consumption'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { useAccount } from '@/api/account'
|
||||
import { ConsumptionTypes } from '@/api/models/consumption'
|
||||
import { QueryPostAPIWithParams } from '@/api/query'
|
||||
import { ACCOUNT_API_URL } from '@/data/constants'
|
||||
|
||||
export const useConsumptionSettingStore = defineStore(
|
||||
'consumptionSetting',
|
||||
@@ -15,20 +16,20 @@ export const useConsumptionSettingStore = defineStore(
|
||||
const consumptionTypeMap = {
|
||||
[ConsumptionTypes.DanmakuStorage]: {
|
||||
name: '弹幕存储',
|
||||
key: 'danmakuStorage'
|
||||
}
|
||||
key: 'danmakuStorage',
|
||||
},
|
||||
}
|
||||
|
||||
async function UpdateConsumptionSetting(
|
||||
type: ConsumptionTypes,
|
||||
value: unknown
|
||||
value: unknown,
|
||||
) {
|
||||
return await QueryPostAPIWithParams(
|
||||
ACCOUNT_API_URL + 'update-consumption-setting',
|
||||
return QueryPostAPIWithParams(
|
||||
`${ACCOUNT_API_URL}update-consumption-setting`,
|
||||
{
|
||||
type: type
|
||||
type,
|
||||
},
|
||||
value
|
||||
value,
|
||||
)
|
||||
}
|
||||
function GetSetting(type: ConsumptionTypes) {
|
||||
@@ -37,5 +38,5 @@ export const useConsumptionSettingStore = defineStore(
|
||||
}
|
||||
|
||||
return { consumptionSetting, consumptionTypeMap, UpdateConsumptionSetting, GetSetting }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { ResponsePointGoodModel } from "@/api/api-models";
|
||||
import { QueryGetAPI } from "@/api/query";
|
||||
import { POINT_API_URL } from "@/data/constants";
|
||||
import { MessageApiInjection } from "naive-ui/es/message/src/MessageProvider";
|
||||
import { defineStore } from "pinia";
|
||||
import { useBiliAuth } from "./useBiliAuth";
|
||||
import { GuidUtils } from "@/Utils";
|
||||
import type { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||
import type { ResponsePointGoodModel } from '@/api/api-models'
|
||||
import { defineStore } from 'pinia'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { GuidUtils } from '@/Utils'
|
||||
import { useBiliAuth } from './useBiliAuth'
|
||||
|
||||
export const usePointStore = defineStore('point', () => {
|
||||
const useAuth = useBiliAuth()
|
||||
|
||||
async function GetSpecificPoint(id: number) {
|
||||
try {
|
||||
const data = await useAuth.QueryBiliAuthGetAPI<number>(POINT_API_URL + 'user/get-point', { id: id })
|
||||
const data = await useAuth.QueryBiliAuthGetAPI<number>(`${POINT_API_URL}user/get-point`, { id })
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
} else {
|
||||
console.error('[point] 无法获取在指定直播间拥有的积分: ' + data.message)
|
||||
console.error(`[point] 无法获取在指定直播间拥有的积分: ${data.message}`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[point] 无法获取在指定直播间拥有的积分: ' + err)
|
||||
console.error(`[point] 无法获取在指定直播间拥有的积分: ${err}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -27,18 +27,18 @@ export const usePointStore = defineStore('point', () => {
|
||||
return []
|
||||
}
|
||||
try {
|
||||
const resp = await QueryGetAPI<ResponsePointGoodModel[]>(POINT_API_URL + 'get-goods', {
|
||||
id: id,
|
||||
const resp = await QueryGetAPI<ResponsePointGoodModel[]>(`${POINT_API_URL}get-goods`, {
|
||||
id,
|
||||
})
|
||||
if (resp.code == 200) {
|
||||
return resp.data
|
||||
} else {
|
||||
message?.error('无法获取数据: ' + resp.message)
|
||||
console.error('无法获取数据: ' + resp.message)
|
||||
message?.error(`无法获取数据: ${resp.message}`)
|
||||
console.error(`无法获取数据: ${resp.message}`)
|
||||
}
|
||||
} catch (err) {
|
||||
message?.error('无法获取数据: ' + err)
|
||||
console.error('无法获取数据: ' + err)
|
||||
message?.error(`无法获取数据: ${err}`)
|
||||
console.error(`无法获取数据: ${err}`)
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -52,44 +52,46 @@ export const usePointStore = defineStore('point', () => {
|
||||
*/
|
||||
async function addPoints(userId: string, count: number, reason: string, remark?: string) {
|
||||
if (count === 0) {
|
||||
console.warn('[point] 积分变动数量不能为0');
|
||||
return null;
|
||||
console.warn('[point] 积分变动数量不能为0')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// 根据用户ID构建参数
|
||||
const params: Record<string, any> = GuidUtils.isGuidFromUserId(userId) ? {
|
||||
uId: GuidUtils.guidToLong(userId),
|
||||
count: count,
|
||||
reason: reason || '',
|
||||
} : {
|
||||
oid: userId,
|
||||
count: count,
|
||||
reason: reason || '',
|
||||
};
|
||||
const params: Record<string, any> = GuidUtils.isGuidFromUserId(userId)
|
||||
? {
|
||||
uId: GuidUtils.guidToLong(userId),
|
||||
count,
|
||||
reason: reason || '',
|
||||
}
|
||||
: {
|
||||
oid: userId,
|
||||
count,
|
||||
reason: reason || '',
|
||||
}
|
||||
|
||||
if (remark) {
|
||||
params.remark = remark;
|
||||
params.remark = remark
|
||||
}
|
||||
|
||||
const data = await QueryGetAPI<number>(POINT_API_URL + 'give-point', params);
|
||||
const data = await QueryGetAPI<number>(`${POINT_API_URL}give-point`, params)
|
||||
|
||||
if (data.code === 200) {
|
||||
console.log(`[point] 用户 ${userId} 积分${count > 0 ? '增加' : '减少'} ${Math.abs(count)} 成功,当前积分:${data.data}`);
|
||||
return data.data; // 返回修改后的积分值
|
||||
console.log(`[point] 用户 ${userId} 积分${count > 0 ? '增加' : '减少'} ${Math.abs(count)} 成功,当前积分:${data.data}`)
|
||||
return data.data // 返回修改后的积分值
|
||||
} else {
|
||||
console.error('[point] 积分操作失败:', data.message);
|
||||
return null;
|
||||
console.error('[point] 积分操作失败:', data.message)
|
||||
return null
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[point] 积分操作出错:', err);
|
||||
return null;
|
||||
console.error('[point] 积分操作出错:', err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
GetSpecificPoint,
|
||||
GetGoods,
|
||||
addPoints
|
||||
addPoints,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useAccount } from '@/api/account'
|
||||
import { QAInfo, ViolationTypes } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { ACCOUNT_API_URL, QUESTION_API_URL } from '@/data/constants'
|
||||
import type { QAInfo } from '@/api/api-models'
|
||||
import { List } from 'linqts'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useAccount } from '@/api/account'
|
||||
import { ViolationTypes } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { ACCOUNT_API_URL, QUESTION_API_URL } from '@/data/constants'
|
||||
|
||||
export type QATagInfo = {
|
||||
export interface QATagInfo {
|
||||
name: string
|
||||
createAt: number
|
||||
visiable: boolean
|
||||
}
|
||||
//SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION, EMOTIONAL
|
||||
// SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION, EMOTIONAL
|
||||
|
||||
export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
const isLoading = ref(false)
|
||||
@@ -25,7 +25,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
const sendQuestions = ref<QAInfo[]>([])
|
||||
const trashQuestions = computed(() => {
|
||||
return recieveQuestions.value.filter(
|
||||
(q) => q.reviewResult && q.reviewResult.isApproved == false
|
||||
q => q.reviewResult && q.reviewResult.isApproved == false,
|
||||
)
|
||||
})
|
||||
const tags = ref<QATagInfo[]>([])
|
||||
@@ -37,60 +37,60 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
|
||||
const recieveQuestionsFiltered = computed(() => {
|
||||
const result = recieveQuestions.value.filter((q) => {
|
||||
/*if (q.id == displayQuestion.value?.id) {
|
||||
/* if (q.id == displayQuestion.value?.id) {
|
||||
return false
|
||||
}*/
|
||||
} */
|
||||
return (
|
||||
(!q.reviewResult || q.reviewResult.isApproved == true) &&
|
||||
(q.isFavorite || !onlyFavorite.value) &&
|
||||
(q.isPublic || !onlyPublic.value) &&
|
||||
(!q.isReaded || !onlyUnread.value) &&
|
||||
(!displayTag.value || q.tag == displayTag.value)
|
||||
(!q.reviewResult || q.reviewResult.isApproved == true)
|
||||
&& (q.isFavorite || !onlyFavorite.value)
|
||||
&& (q.isPublic || !onlyPublic.value)
|
||||
&& (!q.isReaded || !onlyUnread.value)
|
||||
&& (!displayTag.value || q.tag == displayTag.value)
|
||||
)
|
||||
})
|
||||
return result
|
||||
//displayQuestion排在最前面
|
||||
//return displayQuestion.value ? [displayQuestion.value, ...result] : result
|
||||
// displayQuestion排在最前面
|
||||
// return displayQuestion.value ? [displayQuestion.value, ...result] : result
|
||||
})
|
||||
const currentQuestion = ref<QAInfo>()
|
||||
const displayQuestion = ref<QAInfo>()
|
||||
const displayTag = ref<string>()
|
||||
|
||||
let isRevieveGetted = false
|
||||
//const isSendGetted = false
|
||||
// const isSendGetted = false
|
||||
|
||||
const message = window.$message
|
||||
|
||||
async function GetRecieveQAInfo() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<{ questions: QAInfo[]; reviewCount: number }>(
|
||||
QUESTION_API_URL + 'get-recieve'
|
||||
await QueryGetAPI<{ questions: QAInfo[], reviewCount: number }>(
|
||||
`${QUESTION_API_URL}get-recieve`,
|
||||
)
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
if (data.data.questions.length > 0) {
|
||||
recieveQuestions.value = new List(data.data.questions)
|
||||
.OrderBy((d) => d.isReaded)
|
||||
.ThenByDescending((d) => d.sendAt)
|
||||
.OrderBy(d => d.isReaded)
|
||||
.ThenByDescending(d => d.sendAt)
|
||||
.ToArray()
|
||||
reviewing.value = data.data.reviewCount
|
||||
|
||||
const displayId =
|
||||
accountInfo.value?.settings.questionDisplay.currentQuestion
|
||||
const displayId
|
||||
= accountInfo.value?.settings.questionDisplay.currentQuestion
|
||||
if (displayId && displayQuestion.value?.id != displayId) {
|
||||
displayQuestion.value = recieveQuestions.value.find(
|
||||
(q) => q.id == displayId
|
||||
q => q.id == displayId,
|
||||
)
|
||||
}
|
||||
}
|
||||
//message.success('共收取 ' + data.data.length + ' 条提问')
|
||||
// message.success('共收取 ' + data.data.length + ' 条提问')
|
||||
isRevieveGetted = true
|
||||
} else {
|
||||
message.error(data.message)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('发生错误: ' + err)
|
||||
message.error(`发生错误: ${err}`)
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
@@ -98,44 +98,44 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
}
|
||||
async function GetSendQAInfo() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<QAInfo[]>(QUESTION_API_URL + 'get-send')
|
||||
await QueryGetAPI<QAInfo[]>(`${QUESTION_API_URL}get-send`)
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
sendQuestions.value = data.data
|
||||
//message.success('共发送 ' + data.data.length + ' 条提问')
|
||||
// message.success('共发送 ' + data.data.length + ' 条提问')
|
||||
} else {
|
||||
message.error(data.message)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('发生错误: ' + err)
|
||||
message.error(`发生错误: ${err}`)
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
async function DelQA(id: number) {
|
||||
await QueryGetAPI(QUESTION_API_URL + 'del', {
|
||||
id: id
|
||||
await QueryGetAPI(`${QUESTION_API_URL}del`, {
|
||||
id,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('删除成功')
|
||||
recieveQuestions.value = recieveQuestions.value.filter(
|
||||
(q) => q.id != id
|
||||
q => q.id != id,
|
||||
)
|
||||
} else {
|
||||
message.error(data.message)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('发生错误: ' + err)
|
||||
message.error(`发生错误: ${err}`)
|
||||
})
|
||||
}
|
||||
async function GetTags() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<QATagInfo[]>(QUESTION_API_URL + 'get-tags', {
|
||||
id: accountInfo.value?.id
|
||||
await QueryGetAPI<QATagInfo[]>(`${QUESTION_API_URL}get-tags`, {
|
||||
id: accountInfo.value?.id,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
@@ -145,14 +145,14 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('发生错误: ' + err)
|
||||
message.error(`发生错误: ${err}`)
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
function getViolationString(violation: ViolationTypes) {
|
||||
//SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION
|
||||
// SENSITIVE_TERM, HATE, VIOLENCE, PORNOGRAPHY, POLITICS, ADVERTISING, AGGRESSION
|
||||
switch (violation) {
|
||||
case ViolationTypes.SENSITIVE_TERM:
|
||||
return '敏感词'
|
||||
@@ -175,23 +175,23 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
message.warning('请输入标签')
|
||||
return
|
||||
}
|
||||
if (tags.value.find((t) => t.name == tag)) {
|
||||
if (tags.value.find(t => t.name == tag)) {
|
||||
message.warning('标签已存在')
|
||||
return
|
||||
}
|
||||
await QueryGetAPI(QUESTION_API_URL + 'add-tag', {
|
||||
tag: tag
|
||||
await QueryGetAPI(`${QUESTION_API_URL}add-tag`, {
|
||||
tag,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('添加成功')
|
||||
GetTags()
|
||||
} else {
|
||||
message.error('添加失败: ' + data.message)
|
||||
message.error(`添加失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('添加失败: ' + err)
|
||||
message.error(`添加失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function delTag(tag: string) {
|
||||
@@ -199,23 +199,23 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
message.warning('请输入标签')
|
||||
return
|
||||
}
|
||||
if (!tags.value.find((t) => t.name == tag)) {
|
||||
if (!tags.value.find(t => t.name == tag)) {
|
||||
message.warning('标签不存在')
|
||||
return
|
||||
}
|
||||
await QueryGetAPI(QUESTION_API_URL + 'del-tag', {
|
||||
tag: tag
|
||||
await QueryGetAPI(`${QUESTION_API_URL}del-tag`, {
|
||||
tag,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('删除成功')
|
||||
GetTags()
|
||||
} else {
|
||||
message.error('删除失败: ' + data.message)
|
||||
message.error(`删除失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('删除失败: ' + err)
|
||||
message.error(`删除失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function updateTagVisiable(tag: string, visiable: boolean) {
|
||||
@@ -223,85 +223,85 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
message.warning('请输入标签')
|
||||
return
|
||||
}
|
||||
if (!tags.value.find((t) => t.name == tag)) {
|
||||
if (!tags.value.find(t => t.name == tag)) {
|
||||
message.warning('标签不存在')
|
||||
return
|
||||
}
|
||||
await QueryGetAPI(QUESTION_API_URL + 'update-tag-visiable', {
|
||||
tag: tag,
|
||||
visiable: visiable
|
||||
await QueryGetAPI(`${QUESTION_API_URL}update-tag-visiable`, {
|
||||
tag,
|
||||
visiable,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('修改成功')
|
||||
GetTags()
|
||||
} else {
|
||||
message.error('修改失败: ' + data.message)
|
||||
message.error(`修改失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败: ' + err)
|
||||
message.error(`修改失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function reply(id: number, msg: string) {
|
||||
isRepling.value = true
|
||||
await QueryPostAPI<QAInfo>(QUESTION_API_URL + 'reply', {
|
||||
await QueryPostAPI<QAInfo>(`${QUESTION_API_URL}reply`, {
|
||||
Id: id,
|
||||
Message: msg
|
||||
Message: msg,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
const index = recieveQuestions.value.findIndex((q) => q.id == id)
|
||||
const index = recieveQuestions.value.findIndex(q => q.id == id)
|
||||
if (index > -1) {
|
||||
recieveQuestions.value[index] = data.data
|
||||
}
|
||||
message.success('回复成功')
|
||||
currentQuestion.value = undefined
|
||||
//replyModalVisiable.value = false
|
||||
// replyModalVisiable.value = false
|
||||
} else {
|
||||
message.error('发送失败: ' + data.message)
|
||||
message.error(`发送失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('发送失败: ' + err)
|
||||
message.error(`发送失败: ${err}`)
|
||||
})
|
||||
.finally(() => {
|
||||
isRepling.value = false
|
||||
})
|
||||
}
|
||||
async function read(question: QAInfo, read: boolean) {
|
||||
await QueryGetAPI(QUESTION_API_URL + 'read', {
|
||||
await QueryGetAPI(`${QUESTION_API_URL}read`, {
|
||||
id: question.id,
|
||||
read: read ? 'true' : 'false'
|
||||
read: read ? 'true' : 'false',
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
question.isReaded = read
|
||||
if (read && displayQuestion.value?.id == question.id) {
|
||||
setCurrentQuestion(question) //取消设为当前展示的问题
|
||||
setCurrentQuestion(question) // 取消设为当前展示的问题
|
||||
}
|
||||
} else {
|
||||
message.error('修改失败: ' + data.message)
|
||||
message.error(`修改失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败: ' + err)
|
||||
message.error(`修改失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function favorite(question: QAInfo, fav: boolean) {
|
||||
await QueryGetAPI(QUESTION_API_URL + 'favorite', {
|
||||
await QueryGetAPI(`${QUESTION_API_URL}favorite`, {
|
||||
id: question.id,
|
||||
favorite: fav
|
||||
favorite: fav,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
question.isFavorite = fav
|
||||
} else {
|
||||
message.error('修改失败: ' + data.message)
|
||||
message.error(`修改失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败: ' + err)
|
||||
message.error(`修改失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function approve(question: QAInfo, approve: boolean) {
|
||||
@@ -309,15 +309,15 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
message.error('暂时不支持取消审核')
|
||||
return
|
||||
}
|
||||
await QueryGetAPI(QUESTION_API_URL + 'approve', {
|
||||
await QueryGetAPI(`${QUESTION_API_URL}approve`, {
|
||||
id: question.id,
|
||||
approve: approve ? 'true' : 'false'
|
||||
approve: approve ? 'true' : 'false',
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
question.reviewResult = undefined
|
||||
const trashIndex = trashQuestions.value.findIndex(
|
||||
(q) => q.id == question.id
|
||||
q => q.id == question.id,
|
||||
)
|
||||
if (trashIndex > -1) {
|
||||
trashQuestions.value.splice(trashIndex, 1)
|
||||
@@ -325,60 +325,60 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
recieveQuestions.value.unshift(question)
|
||||
message.success('已标记为审核通过')
|
||||
} else {
|
||||
message.error('修改失败: ' + data.message)
|
||||
message.error(`修改失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败: ' + err)
|
||||
message.error(`修改失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function setPublic(pub: boolean) {
|
||||
isChangingPublic.value = true
|
||||
await QueryGetAPI(QUESTION_API_URL + 'public', {
|
||||
await QueryGetAPI(`${QUESTION_API_URL}public`, {
|
||||
id: currentQuestion.value?.id,
|
||||
public: pub
|
||||
public: pub,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
if (currentQuestion.value) currentQuestion.value.isPublic = pub
|
||||
message.success('已修改公开状态')
|
||||
} else {
|
||||
message.error('修改失败: ' + data.message)
|
||||
message.error(`修改失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('修改失败: ' + err)
|
||||
message.error(`修改失败: ${err}`)
|
||||
})
|
||||
.finally(() => {
|
||||
isChangingPublic.value = false
|
||||
})
|
||||
}
|
||||
async function blacklist(question: QAInfo) {
|
||||
await QueryGetAPI(ACCOUNT_API_URL + 'black-list/add', {
|
||||
id: question.sender.id
|
||||
await QueryGetAPI(`${ACCOUNT_API_URL}black-list/add`, {
|
||||
id: question.sender.id,
|
||||
})
|
||||
.then(async (data) => {
|
||||
if (data.code == 200) {
|
||||
await QueryGetAPI(QUESTION_API_URL + 'del', {
|
||||
id: question.id
|
||||
await QueryGetAPI(`${QUESTION_API_URL}del`, {
|
||||
id: question.id,
|
||||
}).then((data) => {
|
||||
if (data.code == 200) {
|
||||
message.success('已拉黑 ' + question.sender.name)
|
||||
message.success(`已拉黑 ${question.sender.name}`)
|
||||
} else {
|
||||
message.error('修改失败: ' + data.message)
|
||||
message.error(`修改失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
message.error('拉黑失败: ' + data.message)
|
||||
message.error(`拉黑失败: ${data.message}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('拉黑失败: ' + err)
|
||||
message.error(`拉黑失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function markAsNormal(question: QAInfo) {
|
||||
await QueryGetAPI(QUESTION_API_URL + 'mark-as-normal', {
|
||||
id: question.id
|
||||
await QueryGetAPI(`${QUESTION_API_URL}mark-as-normal`, {
|
||||
id: question.id,
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code == 200) {
|
||||
@@ -387,7 +387,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error('标记失败: ' + err)
|
||||
message.error(`标记失败: ${err}`)
|
||||
})
|
||||
}
|
||||
async function setCurrentQuestion(item: QAInfo | undefined) {
|
||||
@@ -399,20 +399,20 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
}
|
||||
try {
|
||||
const data = await QueryGetAPI(
|
||||
QUESTION_API_URL + 'set-current',
|
||||
`${QUESTION_API_URL}set-current`,
|
||||
isCurrent || !item
|
||||
? null
|
||||
: {
|
||||
id: item.id
|
||||
}
|
||||
id: item.id,
|
||||
},
|
||||
)
|
||||
if (data.code == 200) {
|
||||
//message.success('设置成功')
|
||||
// message.success('设置成功')
|
||||
} else {
|
||||
message.error('设置失败: ' + data.message)
|
||||
message.error(`设置失败: ${data.message}`)
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('设置失败:' + err)
|
||||
message.error(`设置失败:${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +447,6 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
||||
blacklist,
|
||||
markAsNormal,
|
||||
setCurrentQuestion,
|
||||
getViolationString
|
||||
getViolationString,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { cookie, useAccount } from '@/api/account'
|
||||
import {
|
||||
import type {
|
||||
BaseRTCClient,
|
||||
MasterRTCClient,
|
||||
SlaveRTCClient
|
||||
} from '@/data/RTCClient'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cookie, useAccount } from '@/api/account'
|
||||
import {
|
||||
MasterRTCClient,
|
||||
SlaveRTCClient,
|
||||
} from '@/data/RTCClient'
|
||||
|
||||
export const useWebRTC = defineStore('WebRTC', () => {
|
||||
const client = ref<BaseRTCClient>()
|
||||
@@ -42,7 +43,7 @@ export const useWebRTC = defineStore('WebRTC', () => {
|
||||
return
|
||||
}
|
||||
while (!accountInfo.value.id) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
}
|
||||
if (client.value) {
|
||||
return client.value
|
||||
@@ -50,12 +51,12 @@ export const useWebRTC = defineStore('WebRTC', () => {
|
||||
if (type == 'master') {
|
||||
client.value = new MasterRTCClient(
|
||||
accountInfo.value.id.toString(),
|
||||
accountInfo.value.token
|
||||
accountInfo.value.token,
|
||||
)
|
||||
} else {
|
||||
client.value = new SlaveRTCClient(
|
||||
accountInfo.value.id?.toString(),
|
||||
accountInfo.value.token
|
||||
accountInfo.value.token,
|
||||
)
|
||||
}
|
||||
await client.value.Init()
|
||||
@@ -63,7 +64,7 @@ export const useWebRTC = defineStore('WebRTC', () => {
|
||||
} else {
|
||||
return useWebRTC()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
return useWebRTC()
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { useAccount } from '@/api/account'
|
||||
import { BASE_HUB_URL } from '@/data/constants'
|
||||
import {
|
||||
HttpTransportType,
|
||||
HubConnectionBuilder,
|
||||
LogLevel
|
||||
LogLevel,
|
||||
} from '@microsoft/signalr'
|
||||
import type { HubConnection } from '@microsoft/signalr'
|
||||
import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useAccount } from '@/api/account'
|
||||
import { BASE_HUB_URL } from '@/data/constants'
|
||||
|
||||
export const useVTsuruHub = defineStore('VTsuruHub', () => {
|
||||
const signalRClient = ref<signalR.HubConnection>()
|
||||
const signalRClient = ref<HubConnection>()
|
||||
const isInited = ref(false)
|
||||
const isIniting = ref(false)
|
||||
const accountInfo = useAccount()
|
||||
@@ -18,14 +19,18 @@ export const useVTsuruHub = defineStore('VTsuruHub', () => {
|
||||
async function connectSignalR() {
|
||||
if (isIniting.value) return
|
||||
isIniting.value = true
|
||||
while (!accountInfo.value.id || accountInfo.value.id < 1)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
//console.log('[Components-Event] 正在连接到 VTsuru 服务器...')
|
||||
|
||||
let currentAccount = accountInfo.value
|
||||
while (!currentAccount || !currentAccount.id || currentAccount.id < 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
currentAccount = accountInfo.value
|
||||
}
|
||||
// console.log('[Components-Event] 正在连接到 VTsuru 服务器...')
|
||||
const connection = new HubConnectionBuilder()
|
||||
.withUrl(BASE_HUB_URL + 'main?token=' + accountInfo.value.token, {
|
||||
.withUrl(`${BASE_HUB_URL}main?token=${currentAccount.token}`, {
|
||||
skipNegotiation: true,
|
||||
transport: HttpTransportType.WebSockets,
|
||||
logger: LogLevel.Error
|
||||
logger: LogLevel.Error,
|
||||
})
|
||||
.withAutomaticReconnect([0, 2000, 10000, 30000])
|
||||
.withHubProtocol(new MessagePackHubProtocol())
|
||||
@@ -34,7 +39,7 @@ export const useVTsuruHub = defineStore('VTsuruHub', () => {
|
||||
connection.send('Finished')
|
||||
})
|
||||
connection.on('Disconnect', (reason: unknown) => {
|
||||
console.log('[Hub] 被 VTsuru 服务器断开连接: ' + reason)
|
||||
console.log(`[Hub] 被 VTsuru 服务器断开连接: ${reason}`)
|
||||
})
|
||||
|
||||
connection.onclose(reconnect)
|
||||
@@ -45,7 +50,7 @@ export const useVTsuruHub = defineStore('VTsuruHub', () => {
|
||||
isInited.value = true
|
||||
return true
|
||||
} catch (e) {
|
||||
console.log('[Hub] 无法连接到 VTsuru 服务器: ' + e)
|
||||
console.log(`[Hub] 无法连接到 VTsuru 服务器: ${e}`)
|
||||
return false
|
||||
} finally {
|
||||
isIniting.value = false
|
||||
@@ -62,31 +67,31 @@ export const useVTsuruHub = defineStore('VTsuruHub', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function send(methodName: string, ...args: any[]) {
|
||||
async function send(methodName: string, ...args: unknown[]) {
|
||||
if (!isInited.value) {
|
||||
await connectSignalR()
|
||||
}
|
||||
signalRClient.value?.send(methodName, ...args)
|
||||
}
|
||||
async function invoke<T>(methodName: string, ...args: any[]) {
|
||||
async function invoke<T>(methodName: string, ...args: unknown[]) {
|
||||
if (!isInited.value) {
|
||||
await connectSignalR()
|
||||
}
|
||||
return signalRClient.value?.invoke<T>(methodName, ...args)
|
||||
}
|
||||
async function on(eventName: string, listener: (args: any) => any) {
|
||||
async function on(eventName: string, listener: (...args: unknown[]) => void) {
|
||||
if (!isInited.value) {
|
||||
await connectSignalR()
|
||||
}
|
||||
signalRClient.value?.on(eventName, listener)
|
||||
}
|
||||
async function off(eventName: string, listener: (args: any) => any) {
|
||||
async function off(eventName: string, listener: (...args: unknown[]) => void) {
|
||||
if (!isInited.value) {
|
||||
await connectSignalR()
|
||||
}
|
||||
signalRClient.value?.off(eventName, listener)
|
||||
}
|
||||
async function onreconnected(listener: (id: any) => any) {
|
||||
async function onreconnected(listener: (connectionId?: string) => void) {
|
||||
if (!isInited.value) {
|
||||
await connectSignalR()
|
||||
}
|
||||
|
||||
@@ -1,65 +1,64 @@
|
||||
import { cookie, useAccount } from '@/api/account';
|
||||
import { getEventType, recordEvent, streamingInfo } from '@/client/data/info';
|
||||
import { QueryBiliAPI } from '@/client/data/utils';
|
||||
import { BASE_HUB_URL, isDev, isTauri } from '@/data/constants';
|
||||
import { DirectClientAuthInfo } from '@/data/DanmakuClients/DirectClient';
|
||||
import * as signalR from '@microsoft/signalr';
|
||||
import * as msgpack from '@microsoft/signalr-protocol-msgpack';
|
||||
import { ZstdCodec, ZstdInit } from '@oneidentity/zstd-js/wasm';
|
||||
import { platform, version } from '@tauri-apps/plugin-os';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref, shallowRef } from 'vue'; // shallowRef 用于非深度响应对象
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useWebRTC } from './useRTC';
|
||||
import type { ZstdCodec } from '@oneidentity/zstd-js/wasm'
|
||||
import type { DirectClientAuthInfo } from '@/data/DanmakuClients/DirectClient'
|
||||
import * as signalR from '@microsoft/signalr'
|
||||
import * as msgpack from '@microsoft/signalr-protocol-msgpack'
|
||||
import { encode } from '@msgpack/msgpack'
|
||||
import { ZstdInit } from '@oneidentity/zstd-js/wasm'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { platform, version } from '@tauri-apps/plugin-os'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, shallowRef } from 'vue' // shallowRef 用于非深度响应对象
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cookie, useAccount } from '@/api/account'
|
||||
import { getEventType, recordEvent, streamingInfo } from '@/client/data/info'
|
||||
import { onReceivedNotification } from '@/client/data/notification'
|
||||
|
||||
import { onReceivedNotification } from '@/client/data/notification';
|
||||
import { encode } from "@msgpack/msgpack";
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { useDanmakuClient } from './useDanmakuClient';
|
||||
import { QueryBiliAPI } from '@/client/data/utils'
|
||||
import { BASE_HUB_URL, isDev, isTauri } from '@/data/constants'
|
||||
import { useDanmakuClient } from './useDanmakuClient'
|
||||
import { useWebRTC } from './useRTC'
|
||||
|
||||
export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||
const route = useRoute();
|
||||
const account = useAccount();
|
||||
const rtc = useWebRTC();
|
||||
const webfetcherType = ref<'openlive' | 'direct'>('openlive'); // 弹幕客户端类型
|
||||
const route = useRoute()
|
||||
const account = useAccount()
|
||||
const rtc = useWebRTC()
|
||||
const webfetcherType = ref<'openlive' | 'direct'>('openlive') // 弹幕客户端类型
|
||||
// --- 连接与状态 ---
|
||||
const state = ref<'disconnected' | 'connecting' | 'connected'>('disconnected'); // SignalR 连接状态
|
||||
const startedAt = ref<Date>(); // 本次启动时间
|
||||
const signalRClient = shallowRef<signalR.HubConnection>(); // SignalR 客户端实例 (浅响应)
|
||||
const signalRId = ref<string>(); // SignalR 连接 ID
|
||||
const client = useDanmakuClient();
|
||||
let timer: any; // 事件发送定时器
|
||||
let disconnectedByServer = false;
|
||||
let isFromClient = false; // 是否由Tauri客户端启动
|
||||
|
||||
|
||||
const state = ref<'disconnected' | 'connecting' | 'connected'>('disconnected') // SignalR 连接状态
|
||||
const startedAt = ref<Date>() // 本次启动时间
|
||||
const signalRClient = shallowRef<signalR.HubConnection>() // SignalR 客户端实例 (浅响应)
|
||||
const signalRId = ref<string>() // SignalR 连接 ID
|
||||
const client = useDanmakuClient()
|
||||
let timer: any // 事件发送定时器
|
||||
let disconnectedByServer = false
|
||||
let isFromClient = false // 是否由Tauri客户端启动
|
||||
|
||||
// --- 新增: 详细状态与信息 ---
|
||||
/** 弹幕客户端内部状态 */
|
||||
const danmakuClientState = ref<'stopped' | 'connecting' | 'connected'>('stopped'); // 更详细的弹幕客户端状态
|
||||
const danmakuClientState = ref<'stopped' | 'connecting' | 'connected'>('stopped') // 更详细的弹幕客户端状态
|
||||
/** 弹幕服务器连接地址 */
|
||||
const danmakuServerUrl = ref<string>();
|
||||
const danmakuServerUrl = ref<string>()
|
||||
/** SignalR 连接 ID */
|
||||
const signalRConnectionId = ref<string>();
|
||||
const signalRConnectionId = ref<string>()
|
||||
// const heartbeatLatency = ref<number>(null); // 心跳延迟暂不实现,复杂度较高
|
||||
|
||||
// --- 事件处理 ---
|
||||
const events: string[] = []; // 待发送事件队列
|
||||
const events: string[] = [] // 待发送事件队列
|
||||
|
||||
// --- 新增: 会话统计 (在 Start 时重置) ---
|
||||
/** 本次会话处理的总事件数 */
|
||||
const sessionEventCount = ref(0);
|
||||
const sessionEventCount = ref(0)
|
||||
/** 本次会话各类型事件计数 */
|
||||
const sessionEventTypeCounts = ref<{ [key: string]: number; }>({});
|
||||
const sessionEventTypeCounts = ref<{ [key: string]: number }>({})
|
||||
/** 本次会话成功上传次数 */
|
||||
const successfulUploads = ref(0);
|
||||
const successfulUploads = ref(0)
|
||||
/** 本次会话失败上传次数 */
|
||||
const failedUploads = ref(0);
|
||||
const failedUploads = ref(0)
|
||||
/** 本次会话发送的总字节数 (压缩后) */
|
||||
const bytesSentSession = ref(0);
|
||||
let zstd: ZstdCodec | undefined = undefined; // Zstd 编码器实例 (如果需要压缩)
|
||||
const bytesSentSession = ref(0)
|
||||
let zstd: ZstdCodec | undefined // Zstd 编码器实例 (如果需要压缩)
|
||||
|
||||
const prefix = computed(() => isFromClient ? '[web-fetcher-iframe] ' : '[web-fetcher] ');
|
||||
const prefix = computed(() => isFromClient ? '[web-fetcher-iframe] ' : '[web-fetcher] ')
|
||||
|
||||
/**
|
||||
* 启动 WebFetcher 服务
|
||||
@@ -67,97 +66,98 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||
async function Start(
|
||||
type: 'openlive' | 'direct' = 'openlive',
|
||||
directAuthInfo?: DirectClientAuthInfo,
|
||||
_isFromClient: boolean = false
|
||||
): Promise<{ success: boolean; message: string; }> {
|
||||
_isFromClient: boolean = false,
|
||||
): Promise<{ success: boolean, message: string }> {
|
||||
if (state.value === 'connected' || state.value === 'connecting') {
|
||||
console.log(prefix.value + '已经启动,无需重复启动');
|
||||
return { success: true, message: '已启动' };
|
||||
console.log(`${prefix.value}已经启动,无需重复启动`)
|
||||
return { success: true, message: '已启动' }
|
||||
}
|
||||
try {
|
||||
zstd ??= await ZstdInit();
|
||||
|
||||
zstd ??= await ZstdInit()
|
||||
} catch (error) {
|
||||
console.error(prefix.value + '当前浏览器不支持zstd压缩, 回退到原始数据传输');
|
||||
console.error(`${prefix.value}当前浏览器不支持zstd压缩, 回退到原始数据传输`)
|
||||
}
|
||||
webfetcherType.value = type; // 设置弹幕客户端类型
|
||||
webfetcherType.value = type // 设置弹幕客户端类型
|
||||
// 重置会话统计数据
|
||||
resetSessionStats();
|
||||
startedAt.value = new Date();
|
||||
isFromClient = _isFromClient;
|
||||
state.value = 'connecting'; // 设置为连接中状态
|
||||
resetSessionStats()
|
||||
startedAt.value = new Date()
|
||||
isFromClient = _isFromClient
|
||||
state.value = 'connecting' // 设置为连接中状态
|
||||
|
||||
// 使用 navigator.locks 确保同一时间只有一个 Start 操作执行
|
||||
const result = await navigator.locks.request('webFetcherStartLock', async () => {
|
||||
console.log(prefix.value + '开始启动...');
|
||||
console.log(`${prefix.value}开始启动...`)
|
||||
while (!(await connectSignalR())) {
|
||||
console.log(prefix.value + '连接 SignalR 失败, 5秒后重试');
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
console.log(`${prefix.value}连接 SignalR 失败, 5秒后重试`)
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
// 如果用户手动停止,则退出重试循环
|
||||
if (state.value === 'disconnected') return { success: false, message: '用户手动停止' };
|
||||
if (state.value === 'disconnected') return { success: false, message: '用户手动停止' }
|
||||
}
|
||||
|
||||
let danmakuResult = await connectDanmakuClient(type, directAuthInfo);
|
||||
let danmakuResult = await connectDanmakuClient(type, directAuthInfo)
|
||||
while (!danmakuResult?.success) {
|
||||
console.log(prefix.value + '弹幕客户端启动失败, 5秒后重试');
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
console.log(`${prefix.value}弹幕客户端启动失败, 5秒后重试`)
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
// 如果用户手动停止,则退出重试循环
|
||||
if (state.value === 'disconnected') return { success: false, message: '用户手动停止' };
|
||||
danmakuResult = await connectDanmakuClient(type, directAuthInfo);
|
||||
if (state.value === 'disconnected') return { success: false, message: '用户手动停止' }
|
||||
danmakuResult = await connectDanmakuClient(type, directAuthInfo)
|
||||
}
|
||||
|
||||
// 只有在两个连接都成功后才设置为 connected
|
||||
state.value = 'connected';
|
||||
disconnectedByServer = false;
|
||||
console.log(prefix.value + '启动成功');
|
||||
return { success: true, message: '启动成功' };
|
||||
});
|
||||
state.value = 'connected'
|
||||
disconnectedByServer = false
|
||||
console.log(`${prefix.value}启动成功`)
|
||||
return { success: true, message: '启动成功' }
|
||||
})
|
||||
|
||||
// 如果启动过程中因为手动停止而失败,需要确保状态是 disconnected
|
||||
if (!result.success) {
|
||||
Stop(); // 确保清理资源
|
||||
return { success: false, message: result.message || '启动失败' };
|
||||
Stop() // 确保清理资源
|
||||
return { success: false, message: result.message || '启动失败' }
|
||||
}
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止 WebFetcher 服务
|
||||
*/
|
||||
function Stop() {
|
||||
if (state.value === 'disconnected') return;
|
||||
if (state.value === 'disconnected') return
|
||||
|
||||
console.log(prefix.value + '正在停止...');
|
||||
state.value = 'disconnected'; // 立即设置状态,防止重连逻辑触发
|
||||
console.log(`${prefix.value}正在停止...`)
|
||||
state.value = 'disconnected' // 立即设置状态,防止重连逻辑触发
|
||||
|
||||
// 清理定时器
|
||||
if (timer) { clearInterval(timer); timer = undefined; }
|
||||
if (timer) {
|
||||
clearInterval(timer); timer = undefined
|
||||
}
|
||||
|
||||
// 停止弹幕客户端
|
||||
client.dispose();
|
||||
danmakuClientState.value = 'stopped';
|
||||
danmakuServerUrl.value = undefined;
|
||||
client.dispose()
|
||||
danmakuClientState.value = 'stopped'
|
||||
danmakuServerUrl.value = undefined
|
||||
|
||||
// 停止 SignalR 连接
|
||||
signalRClient.value?.stop();
|
||||
signalRClient.value = undefined;
|
||||
signalRConnectionId.value = undefined;
|
||||
signalRClient.value?.stop()
|
||||
signalRClient.value = undefined
|
||||
signalRConnectionId.value = undefined
|
||||
|
||||
// 清理状态
|
||||
startedAt.value = undefined;
|
||||
events.length = 0; // 清空事件队列
|
||||
startedAt.value = undefined
|
||||
events.length = 0 // 清空事件队列
|
||||
// resetSessionStats(); // 会话统计在下次 Start 时重置
|
||||
|
||||
console.log(prefix.value + '已停止');
|
||||
console.log(`${prefix.value}已停止`)
|
||||
}
|
||||
|
||||
/** 重置会话统计数据 */
|
||||
function resetSessionStats() {
|
||||
sessionEventCount.value = 0;
|
||||
sessionEventTypeCounts.value = {};
|
||||
successfulUploads.value = 0;
|
||||
failedUploads.value = 0;
|
||||
bytesSentSession.value = 0;
|
||||
sessionEventCount.value = 0
|
||||
sessionEventTypeCounts.value = {}
|
||||
successfulUploads.value = 0
|
||||
failedUploads.value = 0
|
||||
bytesSentSession.value = 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,180 +165,177 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||
*/
|
||||
async function connectDanmakuClient(
|
||||
type: 'openlive' | 'direct',
|
||||
directConnectInfo?: DirectClientAuthInfo
|
||||
directConnectInfo?: DirectClientAuthInfo,
|
||||
) {
|
||||
if (client.state !== 'waiting') {
|
||||
console.log(prefix.value + '弹幕客户端已连接或正在连接');
|
||||
return { success: true, message: '弹幕客户端已启动' };
|
||||
console.log(`${prefix.value}弹幕客户端已连接或正在连接`)
|
||||
return { success: true, message: '弹幕客户端已启动' }
|
||||
}
|
||||
|
||||
console.log(prefix.value + '正在连接弹幕客户端...');
|
||||
danmakuClientState.value = 'connecting';
|
||||
console.log(`${prefix.value}正在连接弹幕客户端...`)
|
||||
danmakuClientState.value = 'connecting'
|
||||
|
||||
if (type === 'openlive') {
|
||||
await client.initOpenlive();
|
||||
await client.initOpenlive()
|
||||
} else {
|
||||
if (!directConnectInfo) {
|
||||
danmakuClientState.value = 'stopped';
|
||||
console.error(prefix.value + '未提供直连弹幕客户端认证信息');
|
||||
return { success: false, message: '未提供弹幕客户端认证信息' };
|
||||
danmakuClientState.value = 'stopped'
|
||||
console.error(`${prefix.value}未提供直连弹幕客户端认证信息`)
|
||||
return { success: false, message: '未提供弹幕客户端认证信息' }
|
||||
}
|
||||
await client.initDirect(directConnectInfo);
|
||||
await client.initDirect(directConnectInfo)
|
||||
}
|
||||
|
||||
// 监听所有事件,用于处理和转发
|
||||
client?.on('all', onGetDanmakus);
|
||||
client?.on('all', onGetDanmakus)
|
||||
|
||||
if (client.connected) {
|
||||
console.log(prefix.value + '弹幕客户端连接成功, 开始监听弹幕');
|
||||
danmakuClientState.value = 'connected'; // 明确设置状态
|
||||
danmakuServerUrl.value = client.danmakuClient!.serverUrl; // 获取服务器地址
|
||||
console.log(`${prefix.value}弹幕客户端连接成功, 开始监听弹幕`)
|
||||
danmakuClientState.value = 'connected' // 明确设置状态
|
||||
danmakuServerUrl.value = client.danmakuClient!.serverUrl // 获取服务器地址
|
||||
// 启动事件发送定时器 (如果之前没有启动)
|
||||
timer ??= setInterval(sendEvents, 2000); // 每 2 秒尝试发送一次事件
|
||||
return { success: true, message: '弹幕客户端已启动' };
|
||||
timer ??= setInterval(sendEvents, 2000) // 每 2 秒尝试发送一次事件
|
||||
return { success: true, message: '弹幕客户端已启动' }
|
||||
} else {
|
||||
console.error(prefix.value + '弹幕客户端启动失败');
|
||||
danmakuClientState.value = 'stopped';
|
||||
danmakuServerUrl.value = undefined;
|
||||
client.dispose(); // 启动失败,清理实例,下次会重建
|
||||
return { success: false, message: '弹幕客户端启动失败' };
|
||||
|
||||
console.error(`${prefix.value}弹幕客户端启动失败`)
|
||||
danmakuClientState.value = 'stopped'
|
||||
danmakuServerUrl.value = undefined
|
||||
client.dispose() // 启动失败,清理实例,下次会重建
|
||||
return { success: false, message: '弹幕客户端启动失败' }
|
||||
}
|
||||
}
|
||||
|
||||
let isConnectingSignalR = false;
|
||||
let isConnectingSignalR = false
|
||||
/**
|
||||
* 连接 SignalR 服务器
|
||||
*/
|
||||
async function connectSignalR(): Promise<boolean> {
|
||||
if (isConnectingSignalR) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
isConnectingSignalR = true;
|
||||
isConnectingSignalR = true
|
||||
if (signalRClient.value && signalRClient.value.state !== signalR.HubConnectionState.Disconnected) {
|
||||
console.log(prefix.value + "SignalR 已连接或正在连接");
|
||||
return true;
|
||||
console.log(`${prefix.value}SignalR 已连接或正在连接`)
|
||||
return true
|
||||
}
|
||||
signalRClient.value?.stop();
|
||||
signalRClient.value = undefined;
|
||||
signalRConnectionId.value = undefined;
|
||||
console.log(prefix.value + '正在连接到 vtsuru 服务器...');
|
||||
signalRClient.value?.stop()
|
||||
signalRClient.value = undefined
|
||||
signalRConnectionId.value = undefined
|
||||
console.log(`${prefix.value}正在连接到 vtsuru 服务器...`)
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(BASE_HUB_URL + 'web-fetcher?token=' + (route.query.token ?? account.value.token), { // 使用 account.token
|
||||
.withUrl(`${BASE_HUB_URL}web-fetcher?token=${route.query.token ?? account.value.token}`, { // 使用 account.token
|
||||
headers: { Authorization: `Bearer ${cookie.value?.cookie}` },
|
||||
skipNegotiation: true,
|
||||
transport: signalR.HttpTransportType.WebSockets
|
||||
transport: signalR.HttpTransportType.WebSockets,
|
||||
})
|
||||
.withAutomaticReconnect({
|
||||
nextRetryDelayInMilliseconds: retryContext => {
|
||||
return retryContext.elapsedMilliseconds < 60 * 1000 ? 10 * 1000 : 30 * 1000;
|
||||
}
|
||||
nextRetryDelayInMilliseconds: (retryContext) => {
|
||||
return retryContext.elapsedMilliseconds < 60 * 1000 ? 10 * 1000 : 30 * 1000
|
||||
},
|
||||
}) // 自动重连策略
|
||||
.withHubProtocol(new msgpack.MessagePackHubProtocol()) // 使用 MessagePack 协议
|
||||
.build();
|
||||
.build()
|
||||
|
||||
connection.on('Disconnect', (reason: unknown) => {
|
||||
console.log(prefix.value + '被服务器断开连接: ' + reason);
|
||||
disconnectedByServer = true; // 标记是服务器主动断开
|
||||
window.$message.error(`被服务器要求断开连接: ${reason}`);
|
||||
});
|
||||
console.log(`${prefix.value}被服务器断开连接: ${reason}`)
|
||||
disconnectedByServer = true // 标记是服务器主动断开
|
||||
window.$message.error(`被服务器要求断开连接: ${reason}`)
|
||||
})
|
||||
// --- 尝试启动连接 ---
|
||||
try {
|
||||
await connection.start();
|
||||
signalRConnectionId.value = connection.connectionId ?? undefined; // 保存连接ID
|
||||
signalRId.value = await sendSelfInfo(connection); // 发送客户端信息
|
||||
await connection.send('Finished'); // 通知服务器已准备好
|
||||
signalRClient.value = connection; // 保存实例
|
||||
await connection.start()
|
||||
signalRConnectionId.value = connection.connectionId ?? undefined // 保存连接ID
|
||||
signalRId.value = await sendSelfInfo(connection) // 发送客户端信息
|
||||
await connection.send('Finished') // 通知服务器已准备好
|
||||
signalRClient.value = connection // 保存实例
|
||||
|
||||
// --- SignalR 事件监听 ---
|
||||
connection.onreconnecting(error => {
|
||||
console.log(prefix.value + `与服务器断开,正在尝试重连... ${error?.message || ''}`);
|
||||
state.value = 'connecting'; // 更新状态为连接中
|
||||
signalRConnectionId.value = undefined; // 连接断开,ID失效
|
||||
});
|
||||
connection.onreconnecting((error) => {
|
||||
console.log(`${prefix.value}与服务器断开,正在尝试重连... ${error?.message || ''}`)
|
||||
state.value = 'connecting' // 更新状态为连接中
|
||||
signalRConnectionId.value = undefined // 连接断开,ID失效
|
||||
})
|
||||
|
||||
connection.onreconnected(async connectionId => {
|
||||
console.log(prefix.value + `与服务器重新连接成功! ConnectionId: ${connectionId}`);
|
||||
signalRConnectionId.value = connectionId ?? undefined;
|
||||
state.value = 'connected'; // 更新状态为已连接
|
||||
signalRId.value = connectionId ?? await sendSelfInfo(connection); // 更新连接ID
|
||||
connection.send('Reconnected').catch(err => console.error(prefix.value + "Send Reconnected failed: " + err));
|
||||
});
|
||||
connection.onreconnected(async (connectionId) => {
|
||||
console.log(`${prefix.value}与服务器重新连接成功! ConnectionId: ${connectionId}`)
|
||||
signalRConnectionId.value = connectionId ?? undefined
|
||||
state.value = 'connected' // 更新状态为已连接
|
||||
signalRId.value = connectionId ?? await sendSelfInfo(connection) // 更新连接ID
|
||||
connection.send('Reconnected').catch(err => console.error(`${prefix.value}Send Reconnected failed: ${err}`))
|
||||
})
|
||||
|
||||
connection.onclose(async (error) => {
|
||||
// 只有在不是由 Stop() 或服务器明确要求断开时才记录错误并尝试独立重连(虽然 withAutomaticReconnect 应该处理)
|
||||
if (state.value !== 'disconnected' && !disconnectedByServer) {
|
||||
console.error(prefix.value + `与服务器连接关闭: ${error?.message || '未知原因'}. 30秒后将自动重启`);
|
||||
//state.value = 'connecting'; // 标记为连接中,等待自动重连
|
||||
//signalRConnectionId.value = undefined;
|
||||
//await connection.start();
|
||||
console.error(`${prefix.value}与服务器连接关闭: ${error?.message || '未知原因'}. 30秒后将自动重启`)
|
||||
// state.value = 'connecting'; // 标记为连接中,等待自动重连
|
||||
// signalRConnectionId.value = undefined;
|
||||
// await connection.start();
|
||||
// 停止 SignalR 连接
|
||||
signalRClient.value?.stop();
|
||||
signalRClient.value = undefined;
|
||||
signalRConnectionId.value = undefined;
|
||||
signalRClient.value?.stop()
|
||||
signalRClient.value = undefined
|
||||
signalRConnectionId.value = undefined
|
||||
setTimeout(() => {
|
||||
console.log(prefix.value + '尝试重启...');
|
||||
connectSignalR(); // 30秒后尝试重启
|
||||
}, 30 * 1000); // 30秒后自动重启
|
||||
console.log(`${prefix.value}尝试重启...`)
|
||||
connectSignalR() // 30秒后尝试重启
|
||||
}, 30 * 1000) // 30秒后自动重启
|
||||
} else if (disconnectedByServer) {
|
||||
console.log(prefix.value + `连接已被服务器关闭.`);
|
||||
//Stop(); // 服务器要求断开,则彻底停止
|
||||
console.log(`${prefix.value}连接已被服务器关闭.`)
|
||||
// Stop(); // 服务器要求断开,则彻底停止
|
||||
} else {
|
||||
console.log(prefix.value + `连接已手动关闭.`);
|
||||
console.log(`${prefix.value}连接已手动关闭.`)
|
||||
}
|
||||
});
|
||||
connection.on('Request', async (url: string, method: string, body: string, useCookie: boolean) => onRequest(url, method, body, useCookie));
|
||||
connection.on('Notification', (type: string, data: any) => { onReceivedNotification(type, data); });
|
||||
})
|
||||
connection.on('Request', async (url: string, method: string, body: string, useCookie: boolean) => onRequest(url, method, body, useCookie))
|
||||
connection.on('Notification', (type: string, data: any) => {
|
||||
onReceivedNotification(type, data)
|
||||
})
|
||||
|
||||
console.log(prefix.value + '已连接到 vtsuru 服务器, ConnectionId: ' + signalRId.value); // 调试输出连接状态
|
||||
console.log(`${prefix.value}已连接到 vtsuru 服务器, ConnectionId: ${signalRId.value}`) // 调试输出连接状态
|
||||
// state.value = 'connected'; // 状态将在 Start 函数末尾统一设置
|
||||
return true;
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error(prefix.value + '无法连接到 vtsuru 服务器: ' + e);
|
||||
signalRConnectionId.value = undefined;
|
||||
signalRClient.value = undefined;
|
||||
console.error(`${prefix.value}无法连接到 vtsuru 服务器: ${e}`)
|
||||
signalRConnectionId.value = undefined
|
||||
signalRClient.value = undefined
|
||||
// state.value = 'disconnected'; // 保持 connecting 或由 Start 控制
|
||||
return false;
|
||||
return false
|
||||
} finally {
|
||||
isConnectingSignalR = false;
|
||||
isConnectingSignalR = false
|
||||
}
|
||||
}
|
||||
async function sendSelfInfo(client: signalR.HubConnection) {
|
||||
return client.invoke('SetSelfInfo',
|
||||
isFromClient ? `tauri ${platform()} ${version()}` : navigator.userAgent,
|
||||
isFromClient ? 'tauri' : 'web',
|
||||
isFromClient ? await getVersion() : '1.0.0',
|
||||
webfetcherType.value === 'direct');
|
||||
return client.invoke('SetSelfInfo', isFromClient ? `tauri ${platform()} ${version()}` : navigator.userAgent, isFromClient ? 'tauri' : 'web', isFromClient ? await getVersion() : '1.0.0', webfetcherType.value === 'direct')
|
||||
}
|
||||
interface ResponseFetchRequestData {
|
||||
Message: string
|
||||
Success: boolean
|
||||
Data: string
|
||||
}
|
||||
type ResponseFetchRequestData = {
|
||||
Message: string;
|
||||
Success: boolean;
|
||||
Data: string;
|
||||
};
|
||||
async function onRequest(url: string, method: string, body: string, useCookie: boolean) {
|
||||
if (!isTauri()) {
|
||||
console.error(prefix.value + '非Tauri环境下无法处理请求: ' + url);
|
||||
console.error(`${prefix.value}非Tauri环境下无法处理请求: ${url}`)
|
||||
return {
|
||||
Message: '非Tauri环境',
|
||||
Success: false,
|
||||
Data: ''
|
||||
};
|
||||
Data: '',
|
||||
}
|
||||
}
|
||||
const result = await QueryBiliAPI(url, method, body, useCookie);
|
||||
console.log(`${prefix.value}请求 (${method})${url}: `, result.statusText);
|
||||
const result = await QueryBiliAPI(url, method, body, useCookie)
|
||||
console.log(`${prefix.value}请求 (${method})${url}: `, result.statusText)
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
const data = await result.text()
|
||||
return {
|
||||
Message: '请求成功',
|
||||
Success: true,
|
||||
Data: data
|
||||
} as ResponseFetchRequestData;
|
||||
Data: data,
|
||||
} as ResponseFetchRequestData
|
||||
} else {
|
||||
return {
|
||||
Message: '请求失败: ' + result.statusText,
|
||||
Message: `请求失败: ${result.statusText}`,
|
||||
Success: false,
|
||||
Data: ''
|
||||
} as ResponseFetchRequestData;
|
||||
Data: '',
|
||||
} as ResponseFetchRequestData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,80 +363,81 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||
function onGetDanmakus(command: any) {
|
||||
if (isFromClient) {
|
||||
// 1. 解析事件类型
|
||||
const eventType = getEventType(command);
|
||||
const eventType = getEventType(command)
|
||||
|
||||
// 2. 记录到每日统计 (调用 statistics 模块)
|
||||
recordEvent(eventType);
|
||||
recordEvent(eventType)
|
||||
|
||||
// 3. 更新会话统计
|
||||
sessionEventCount.value++;
|
||||
sessionEventTypeCounts.value[eventType] = (sessionEventTypeCounts.value[eventType] || 0) + 1;
|
||||
sessionEventCount.value++
|
||||
sessionEventTypeCounts.value[eventType] = (sessionEventTypeCounts.value[eventType] || 0) + 1
|
||||
}
|
||||
// 4. 加入待发送队列 (确保是字符串)
|
||||
const eventString = typeof command === 'string' ? command : JSON.stringify(command);
|
||||
const eventString = typeof command === 'string' ? command : JSON.stringify(command)
|
||||
if (isDev) {
|
||||
//console.log(prefix.value + '收到弹幕事件: ' + eventString); // 开发模式下打印所有事件 (可选)
|
||||
// console.log(prefix.value + '收到弹幕事件: ' + eventString); // 开发模式下打印所有事件 (可选)
|
||||
}
|
||||
if (events.length >= 10000) {
|
||||
events.shift(); // 如果队列过长,移除最旧的事件
|
||||
events.shift() // 如果队列过长,移除最旧的事件
|
||||
}
|
||||
events.push(eventString);
|
||||
events.push(eventString)
|
||||
}
|
||||
let updateCount = 0;
|
||||
let updateCount = 0
|
||||
/**
|
||||
* 定期将队列中的事件发送到服务器
|
||||
*/
|
||||
async function sendEvents() {
|
||||
updateCount++;
|
||||
updateCount++
|
||||
// 确保 SignalR 已连接
|
||||
if (!signalRClient.value || signalRClient.value.state !== signalR.HubConnectionState.Connected) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
// 如果没有事件,则不发送
|
||||
if (events.length === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (updateCount % 60 == 0) {
|
||||
// 每60秒更新一次连接信息
|
||||
if (signalRClient.value) {
|
||||
await sendSelfInfo(signalRClient.value);
|
||||
await sendSelfInfo(signalRClient.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 批量处理事件,每次最多发送20条
|
||||
const batchSize = 30;
|
||||
const batch = events.slice(0, batchSize);
|
||||
const batchSize = 30
|
||||
const batch = events.slice(0, batchSize)
|
||||
|
||||
try {
|
||||
|
||||
let result: { Success: boolean; Message: string; } = { Success: false, Message: '' };
|
||||
let length = 0;
|
||||
let eventCharLength = batch.map(event => event.length).reduce((a, b) => a + b, 0); // 计算字符长度
|
||||
let result: { Success: boolean, Message: string } = { Success: false, Message: '' }
|
||||
let length = 0
|
||||
const eventCharLength = batch.map(event => event.length).reduce((a, b) => a + b, 0) // 计算字符长度
|
||||
if (zstd && eventCharLength > 100) {
|
||||
const data = zstd.ZstdSimple.compress(encode(batch), 11);
|
||||
length = data.length;
|
||||
result = await signalRClient.value.invoke<{ Success: boolean; Message: string; }>(
|
||||
'UploadEventsCompressedV2', data
|
||||
);
|
||||
}
|
||||
else {
|
||||
length = new TextEncoder().encode(batch.join()).length;
|
||||
result = await signalRClient.value.invoke<{ Success: boolean; Message: string; }>(
|
||||
'UploadEvents', batch, webfetcherType.value === 'direct' ? true : false
|
||||
);
|
||||
const data = zstd.ZstdSimple.compress(encode(batch), 11)
|
||||
length = data.length
|
||||
result = await signalRClient.value.invoke<{ Success: boolean, Message: string }>(
|
||||
'UploadEventsCompressedV2',
|
||||
data,
|
||||
)
|
||||
} else {
|
||||
length = new TextEncoder().encode(batch.join()).length
|
||||
result = await signalRClient.value.invoke<{ Success: boolean, Message: string }>(
|
||||
'UploadEvents',
|
||||
batch,
|
||||
webfetcherType.value === 'direct',
|
||||
)
|
||||
}
|
||||
|
||||
if (result?.Success) {
|
||||
events.splice(0, batch.length); // 从队列中移除已成功发送的事件
|
||||
successfulUploads.value++;
|
||||
bytesSentSession.value += length;
|
||||
events.splice(0, batch.length) // 从队列中移除已成功发送的事件
|
||||
successfulUploads.value++
|
||||
bytesSentSession.value += length
|
||||
} else {
|
||||
failedUploads.value++;
|
||||
console.error(prefix.value + '上传弹幕失败: ' + result?.Message);
|
||||
failedUploads.value++
|
||||
console.error(`${prefix.value}上传弹幕失败: ${result?.Message}`)
|
||||
}
|
||||
} catch (err) {
|
||||
failedUploads.value++;
|
||||
console.error(prefix.value + '发送事件时出错: ' + err);
|
||||
failedUploads.value++
|
||||
console.error(`${prefix.value}发送事件时出错: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +457,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||
// 连接详情
|
||||
danmakuClientState,
|
||||
danmakuServerUrl,
|
||||
//signalRConnectionId,
|
||||
// signalRConnectionId,
|
||||
// heartbeatLatency, // 暂不暴露
|
||||
|
||||
// 会话统计
|
||||
@@ -471,6 +469,6 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||
|
||||
// 实例 (谨慎暴露,主要用于调试或特定场景)
|
||||
signalRClient: computed(() => signalRClient.value), // 返回计算属性以防直接修改
|
||||
client
|
||||
};
|
||||
});
|
||||
client,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user