diff --git a/bun.lockb b/bun.lockb
index d968990..f5f509f 100644
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/index.html b/index.html
index e9d98f3..fa148af 100644
--- a/index.html
+++ b/index.html
@@ -15,6 +15,7 @@
+
diff --git a/package.json b/package.json
index 9f91083..987b18b 100644
--- a/package.json
+++ b/package.json
@@ -11,21 +11,22 @@
"dependencies": {
"@microsoft/signalr": "^8.0.7",
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
- "@types/node": "^22.8.2",
- "@typescript-eslint/eslint-plugin": "^8.12.1",
+ "@types/node": "^22.9.0",
+ "@typescript-eslint/eslint-plugin": "^8.13.0",
"@vicons/fluent": "^0.12.0",
+ "@vitejs/plugin-basic-ssl": "^1.1.0",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/cli": "^5.0.8",
- "@vueuse/core": "^11.1.0",
- "@vueuse/router": "^11.1.0",
+ "@vueuse/core": "^11.2.0",
+ "@vueuse/router": "^11.2.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"date-fns": "^4.1.0",
"easy-speech": "^2.4.0",
"echarts": "^5.5.1",
- "eslint": "^9.13.0",
+ "eslint": "^9.14.0",
"eslint-plugin-import": "^2.31.0",
- "eslint-plugin-oxlint": "^0.10.1",
+ "eslint-plugin-oxlint": "^0.11.0",
"eslint-plugin-prettier": "^5.2.1",
"fast-xml-parser": "^4.5.0",
"file-saver": "^2.0.5",
@@ -34,9 +35,10 @@
"linqts": "^2.0.0",
"mitt": "^3.0.1",
"music-metadata-browser": "^2.5.11",
- "pinia": "^2.2.4",
+ "peerjs": "^1.5.4",
+ "pinia": "^2.2.6",
"prettier": "^3.3.3",
- "qrcode.vue": "^3.5.1",
+ "qrcode.vue": "^3.6.0",
"queue-typescript": "^1.0.1",
"unplugin-vue-markdown": "^0.26.2",
"uuid": "^11.0.2",
@@ -50,16 +52,16 @@
"vue3-aplayer": "^1.7.3",
"vue3-marquee": "^4.2.2",
"vueuc": "^0.4.64",
- "worker-timers": "^8.0.10",
+ "worker-timers": "^8.0.11",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
- "@types/bun": "^1.1.12",
+ "@types/bun": "^1.1.13",
"@types/eslint": "^9.6.1",
"@types/obs-studio": "^2.17.2",
"@types/uuid": "^10.0.0",
- "@typescript-eslint/parser": "^8.12.1",
+ "@typescript-eslint/parser": "^8.13.0",
"@vicons/ionicons5": "^0.12.0",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"@vue/eslint-config-typescript": "^14.1.3",
diff --git a/plugins/vite-plugin-caddy.ts b/plugins/vite-plugin-caddy.ts
new file mode 100644
index 0000000..69df042
--- /dev/null
+++ b/plugins/vite-plugin-caddy.ts
@@ -0,0 +1,49 @@
+// src/index.ts
+import chalk from 'chalk'
+import { spawn } from 'child_process'
+
+// src/utils.ts
+import { execSync } from 'child_process'
+function validateCaddyIsInstalled() {
+ let caddyInstalled = false
+ try {
+ execSync('caddy version')
+ caddyInstalled = true
+ } catch {
+ caddyInstalled = false
+ console.error('caddy cli is not installed')
+ }
+ return caddyInstalled
+}
+
+// src/index.ts
+function viteCaddyTlsPlugin(url?:string) {
+ return {
+ name: 'vite:caddy-tls',
+ async configResolved({ command }) {
+ if (command !== 'serve') return
+ console.log('starting caddy plugin...')
+ validateCaddyIsInstalled()
+ const handle = spawn(
+ `caddy reverse-proxy ${url ? `--from ${url}` : ''} --to http://localhost:5173`,
+ {
+ shell: true
+ }
+ )
+ handle.stdout.on('data', (data) => {
+ console.log(`stdout: ${data}`)
+ })
+ handle.stderr.on('data', () => {})
+ //const servers = parseNamesFromCaddyFile(`${cwd}/Caddyfile`);
+ console.log()
+ console.log(
+ chalk.green('\u{1F512} Caddy is running to proxy your traffic on https')
+ )
+ console.log()
+ console.log(`\u{1F517} Access your local server `)
+ console.log(chalk.blue(`\u{1F30D} https://${url ?? 'localhost'}`))
+ console.log()
+ }
+ }
+}
+export { viteCaddyTlsPlugin as default }
diff --git a/src/api/account.ts b/src/api/account.ts
index 64fe061..f544c07 100644
--- a/src/api/account.ts
+++ b/src/api/account.ts
@@ -9,7 +9,6 @@ import { useRoute } from 'vue-router'
export const ACCOUNT = ref({} as AccountInfo)
export const isLoadingAccount = ref(true)
-const route = useRoute()
const { message } = createDiscreteApi(['message'])
const cookie = useLocalStorage('JWT_Token', '')
@@ -47,6 +46,7 @@ export async function GetSelfAccount() {
}
export function UpdateAccountLoop() {
setInterval(() => {
+ const route = useRoute()
if (ACCOUNT.value && route?.name != 'question-display') {
// 防止在问题详情页刷新
GetSelfAccount()
@@ -63,47 +63,68 @@ function refreshCookie() {
})
}
export async function SaveAccountSettings() {
- return await QueryPostAPI(ACCOUNT_API_URL + 'update-setting', ACCOUNT.value?.settings)
+ return await QueryPostAPI(
+ ACCOUNT_API_URL + 'update-setting',
+ ACCOUNT.value?.settings
+ )
}
export async function SaveEnableFunctions(functions: FunctionTypes[]) {
- return await QueryPostAPI(ACCOUNT_API_URL + 'update-enable-functions', functions)
+ return await QueryPostAPI(
+ ACCOUNT_API_URL + 'update-enable-functions',
+ functions
+ )
}
export async function SaveSetting(
- name: 'Queue' | 'Point' | 'Index' | 'General' | 'QuestionDisplay' | 'SongRequest' | 'QuestionBox' | 'SendEmail',
- setting: unknown,
+ name:
+ | 'Queue'
+ | 'Point'
+ | 'Index'
+ | 'General'
+ | 'QuestionDisplay'
+ | 'SongRequest'
+ | 'QuestionBox'
+ | 'SendEmail',
+ setting: unknown
) {
const result = await QueryPostAPIWithParams(
ACCOUNT_API_URL + 'update-single-setting',
{
- name,
+ name
},
- setting,
+ setting
)
return result.message
}
export async function UpdateFunctionEnable(func: FunctionTypes) {
if (ACCOUNT.value) {
- const oldValue = JSON.parse(JSON.stringify(ACCOUNT.value.settings.enableFunctions))
+ const oldValue = JSON.parse(
+ JSON.stringify(ACCOUNT.value.settings.enableFunctions)
+ )
if (ACCOUNT.value?.settings.enableFunctions.includes(func)) {
- ACCOUNT.value.settings.enableFunctions = ACCOUNT.value.settings.enableFunctions.filter((f) => f != func)
+ ACCOUNT.value.settings.enableFunctions =
+ ACCOUNT.value.settings.enableFunctions.filter((f) => f != func)
} else {
ACCOUNT.value.settings.enableFunctions.push(func)
}
await SaveEnableFunctions(ACCOUNT.value?.settings.enableFunctions)
.then((data) => {
if (data.code == 200) {
- message.success(`已${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}`)
+ message.success(
+ `已${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}`
+ )
} else {
if (ACCOUNT.value) {
ACCOUNT.value.settings.enableFunctions = oldValue
}
message.error(
- `${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}失败: ${data.message}`,
+ `${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}失败: ${data.message}`
)
}
})
.catch((err) => {
- message.error(`${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}失败: ${err}`)
+ message.error(
+ `${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}失败: ${err}`
+ )
})
}
}
@@ -111,74 +132,85 @@ export function useAccount() {
return ACCOUNT
}
-export async function Register(name: string, email: string, password: string, token: string): Promise> {
+export async function Register(
+ name: string,
+ email: string,
+ password: string,
+ token: string
+): Promise> {
return QueryPostAPI(`${ACCOUNT_API_URL}register`, {
name,
email,
password,
- token,
+ token
})
}
-export async function Login(nameOrEmail: string, password: string): Promise> {
+export async function Login(
+ nameOrEmail: string,
+ password: string
+): Promise> {
return QueryPostAPI(`${ACCOUNT_API_URL}login`, {
nameOrEmail,
- password,
+ password
})
}
export async function Self(): Promise> {
return QueryPostAPI(`${ACCOUNT_API_URL}self`)
}
-export async function AddBiliBlackList(id: number, name: string): Promise> {
+export async function AddBiliBlackList(
+ id: number,
+ name: string
+): Promise> {
return QueryGetAPI(`${ACCOUNT_API_URL}black-list/add-bili`, {
id: id,
- name: name,
+ name: name
})
}
export async function DelBiliBlackList(id: number): Promise> {
return QueryGetAPI(`${ACCOUNT_API_URL}black-list/del-bili`, {
- id: id,
+ id: id
})
}
export async function DelBlackList(id: number): Promise> {
return QueryGetAPI(`${ACCOUNT_API_URL}black-list/del`, {
- id: id,
+ id: id
})
}
export function downloadConfigDirect(name: string) {
return QueryGetAPI(VTSURU_API_URL + 'get-config', {
- name: name,
+ name: name
})
}
export async function DownloadConfig(name: string) {
try {
const resp = await QueryGetAPI(VTSURU_API_URL + 'get-config', {
- name: name,
+ name: name
})
if (resp.code == 200) {
console.log('已获取配置文件: ' + name)
return {
msg: undefined,
- data: JSON.parse(resp.data) as T,
+ data: JSON.parse(resp.data) as T
}
} else if (resp.code == 404) {
console.error(`未找到名为 ${name} 的配置文件`)
return {
msg: `未找到名为 ${name} 的配置文件, 需要先上传`,
- data: undefined,
+ data: undefined
}
} else {
console.error(`无法获取配置文件 [${name}]: ` + resp.message)
return {
msg: `无法获取配置文件 [${name}]: ` + resp.message,
- data: undefined,
+ data: undefined
}
}
} catch (err) {
console.error(`无法获取配置文件 [${name}]: ` + err)
return {
msg: `无法获取配置文件 [${name}]: ` + err,
- data: undefined,
+ data: undefined
}
}
}
@@ -186,7 +218,7 @@ export async function UploadConfig(name: string, data: unknown) {
try {
const resp = await QueryPostAPI(VTSURU_API_URL + 'set-config', {
name: name,
- json: JSON.stringify(data),
+ json: JSON.stringify(data)
})
if (resp.code == 200) {
console.log('已保存配置文件至服务器:' + name)
@@ -208,7 +240,10 @@ export async function EnableFunction(func: FunctionTypes) {
if (await updateFunctionEnable()) {
return true
} else {
- ACCOUNT.value.settings.enableFunctions.splice(ACCOUNT.value.settings.enableFunctions.indexOf(func), 1)
+ ACCOUNT.value.settings.enableFunctions.splice(
+ ACCOUNT.value.settings.enableFunctions.indexOf(func),
+ 1
+ )
return false
}
}
@@ -220,7 +255,10 @@ export async function DisableFunction(func: FunctionTypes) {
if (!ACCOUNT.value.settings.enableFunctions.includes(func)) {
return true
} else {
- ACCOUNT.value.settings.enableFunctions.splice(ACCOUNT.value.settings.enableFunctions.indexOf(func), 1)
+ ACCOUNT.value.settings.enableFunctions.splice(
+ ACCOUNT.value.settings.enableFunctions.indexOf(func),
+ 1
+ )
if (await updateFunctionEnable()) {
return true
} else {
@@ -234,7 +272,9 @@ export async function DisableFunction(func: FunctionTypes) {
async function updateFunctionEnable() {
if (ACCOUNT.value) {
try {
- const data = await SaveEnableFunctions(ACCOUNT.value.settings.enableFunctions)
+ const data = await SaveEnableFunctions(
+ ACCOUNT.value.settings.enableFunctions
+ )
if (data.code == 200) {
return true
} else {
diff --git a/src/data/RTCClient.ts b/src/data/RTCClient.ts
new file mode 100644
index 0000000..a1cc30a
--- /dev/null
+++ b/src/data/RTCClient.ts
@@ -0,0 +1,192 @@
+import { useAccount } from '@/api/account'
+import { useVTsuruHub } from '@/store/useVTsuruHub'
+import Peer, { DataConnection } from 'peerjs'
+import { Ref, ref } from 'vue'
+
+export interface ComponentsEventHubModel {
+ IsMaster: boolean
+ Token: string
+}
+export interface RTCData {
+ Key: string
+ Data: any
+}
+
+abstract class BaseRTCClient {
+ constructor(user: string, pass: string) {
+ this.user = user
+ this.pass = pass
+ }
+
+ protected user: string
+ protected pass: string
+ protected vhub = useVTsuruHub()
+
+ public isInited = false
+
+ public peer?: Peer
+
+ protected connections: DataConnection[] = []
+
+ protected events: {
+ [key: string]: ((args: unknown) => void)[]
+ } = {}
+
+ abstract type: 'master' | 'slave'
+
+ public on(eventName: string, listener: (args: unknown) => void) {
+ eventName = eventName.toLowerCase()
+ if (!this.events[eventName]) {
+ this.events[eventName] = []
+ }
+ this.events[eventName].push(listener)
+ }
+ public off(eventName: string, listener: (args: unknown) => void) {
+ if (this.events[eventName]) {
+ const index = this.events[eventName].indexOf(listener)
+ if (index > -1) {
+ this.events[eventName].splice(index, 1)
+ }
+ }
+ }
+ public send(eventName: string, data: unknown) {
+ this.connections.forEach((item) =>
+ item.send({
+ Key: eventName,
+ Data: data
+ })
+ )
+ }
+
+ protected connectRTC() {
+ //console.log('[Components-Event] 正在连接到 PeerJS 服务器...')
+ this.peer = new Peer({
+ host: 'peer.suki.club',
+ port: 443,
+ key: 'vtsuru',
+ secure: true,
+ config: {
+ iceServers: [
+ { urls: 'stun:turn.suki.club' },
+ {
+ urls: 'turn:turn.suki.club',
+ username: this.user,
+ credential: this.pass
+ }
+ ]
+ }
+ //debug: 3
+ })
+
+ this.peer?.on('open', async (id) => {
+ console.log('[Components-Event] 已连接到 PeerJS 服务器: ' + id)
+ this.vhub?.send('SetRTCToken', id, this.type == 'master')
+ })
+ this.peer?.on('error', (err) => {
+ console.error(err)
+ })
+ this.peer?.on('close', () => {
+ console.log('[Components-Event] PeerJS 连接已关闭')
+ })
+ this.peer?.on('disconnected', () => {
+ console.log('[Components-Event] PeerJS 连接已断开')
+ this.peer?.reconnect()
+ })
+ }
+ public processData(data: RTCData) {
+ //console.log(data)
+ if (data.Key == 'Heartbeat') return
+ if (this.events[data.Key.toLowerCase()]) {
+ this.events[data.Key].forEach((item) => item(data.Data))
+ }
+ }
+ public async getAllRTC() {
+ return (
+ (await this.vhub.invoke('GetOnlineRTC')) || []
+ )
+ }
+ protected onConnectionClose(id: string) {
+ this.connections = this.connections.filter((item) => item.peer != id)
+ console.log(
+ `[Components-Event] <${this.connections.length}> ${this.type == 'master' ? 'Slave' : 'Master'} 下线: ` +
+ id
+ )
+ }
+
+ public Init() {
+ if (!this.isInited) {
+ this.isInited = true
+ this.connectRTC()
+ }
+ this.vhub.on('RTCOffline', (id: string) => this.onConnectionClose(id))
+ return this
+ }
+}
+
+export class SlaveRTCClient extends BaseRTCClient {
+ constructor(user: string, pass: string) {
+ super(user, pass)
+ }
+ type: 'slave' = 'slave' as const
+ public async connectToAllMaster() {
+ const masters = (await this.getAllRTC()).filter(
+ (item) =>
+ item.IsMaster &&
+ item.Token != this.peer!.id &&
+ !this.connections.some((conn) => conn.peer == item.Token)
+ )
+ masters.forEach((item) => {
+ this.connectToMaster(item.Token)
+ //console.log('[Components-Event] 正在连接到现有 Master: ' + item.Token)
+ })
+ }
+ public connectToMaster(token: string) {
+ if (this.connections.some((conn) => conn.peer == token)) return
+ const c = this.peer?.connect(token)
+ c?.on('open', () => {
+ this.connections.push(c)
+ console.log(
+ `[Components-Event] <${this.connections.length}> ==> Master 连接已建立: ` +
+ token
+ )
+ })
+ c?.on('data', (data) => this.processData(data as RTCData))
+ c?.on('close', () => this.onConnectionClose(c.peer))
+ }
+ public Init() {
+ super.Init()
+ this.vhub?.on('MasterOnline', (data: string) => this.connectToMaster(data))
+ setTimeout(() => {
+ this.connectToAllMaster()
+ }, 500)
+ setInterval(() => {
+ this.connectToAllMaster()
+ }, 30000)
+
+ return this
+ }
+}
+export class MasterRTCClient extends BaseRTCClient {
+ constructor(user: string, pass: string) {
+ super(user, pass)
+ }
+ type: 'master' = 'master' as const
+ public connectRTC() {
+ super.connectRTC()
+ this.peer?.on('connection', (conn) => {
+ conn.on('open', () => {
+ this.connections.push(conn)
+ console.log(
+ `[Components-Event] <${this.connections.length}> Slave 上线: ` +
+ conn.peer
+ )
+ })
+ conn.on('data', (data) => this.processData(data as RTCData))
+ conn.on('error', (err) => console.error(err))
+ conn.on('close', () => this.onConnectionClose(conn.peer))
+ })
+ }
+ public Init() {
+ return super.Init()
+ }
+}
diff --git a/src/router/index.ts b/src/router/index.ts
index b174d02..076ffdb 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -97,7 +97,7 @@ const routes: Array = [
{
path: '/:pathMatch(.*)*',
name: 'notfound',
- component: import('@/views/NotfoundView.vue'),
+ component: () => import('@/views/NotfoundView.vue'),
meta: {
title: '页面不存在',
},
diff --git a/src/router/singlePage.ts b/src/router/singlePage.ts
index 97c37f7..f32e4a0 100644
--- a/src/router/singlePage.ts
+++ b/src/router/singlePage.ts
@@ -4,7 +4,15 @@ export default [
name: 'question-display',
component: () => import('@/views/single/QuestionDisplay.vue'),
meta: {
- title: '棉花糖展示页',
- },
+ title: '棉花糖展示页'
+ }
},
+ {
+ path: '/playground/test',
+ name: 'test',
+ component: () => import('@/views/TestView.vue'),
+ meta: {
+ title: '测试页'
+ }
+ }
]
diff --git a/src/store/useAuthStore.ts b/src/store/useAuthStore.ts
index 27879be..3212ba0 100644
--- a/src/store/useAuthStore.ts
+++ b/src/store/useAuthStore.ts
@@ -36,6 +36,7 @@ export const useAuthStore = defineStore('BiliAuth', () => {
async function getAuthInfo() {
try {
isLoading.value = true
+ if(!currentToken.value) return
await QueryBiliAuthGetAPI(BILI_AUTH_API_URL + 'info').then((data) => {
if (data.code == 200) {
biliAuth.value = data.data
diff --git a/src/store/useRTC.ts b/src/store/useRTC.ts
new file mode 100644
index 0000000..808fa5f
--- /dev/null
+++ b/src/store/useRTC.ts
@@ -0,0 +1,44 @@
+import { useAccount } from '@/api/account'
+import { MasterRTCClient, SlaveRTCClient } from '@/data/RTCClient'
+import { acceptHMRUpdate, defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useWebRTC = defineStore('WebRTC', () => {
+ const masterClient = ref()
+ const slaveClient = ref()
+ const accountInfo = useAccount()
+
+ function Init(type: 'master' | 'slave') {
+ if (type == 'master') {
+ if (masterClient.value) {
+ return masterClient
+ } else {
+ masterClient.value = new MasterRTCClient(
+ accountInfo.value.id.toString(),
+ accountInfo.value.token
+ )
+ masterClient.value.Init()
+ return masterClient
+ }
+ } else {
+ if (slaveClient.value) {
+ return slaveClient
+ } else {
+ slaveClient.value = new SlaveRTCClient(
+ accountInfo.value.id.toString(),
+ accountInfo.value.token
+ )
+ slaveClient.value.Init()
+ return slaveClient
+ }
+ }
+ }
+
+ return {
+ Init
+ }
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useWebRTC, import.meta.hot))
+}
diff --git a/src/store/useVTsuruHub.ts b/src/store/useVTsuruHub.ts
new file mode 100644
index 0000000..66afa7e
--- /dev/null
+++ b/src/store/useVTsuruHub.ts
@@ -0,0 +1,106 @@
+import { useAccount } from '@/api/account'
+import { BASE_HUB_URL } from '@/data/constants'
+import {
+ HttpTransportType,
+ HubConnectionBuilder,
+ LogLevel
+} from '@microsoft/signalr'
+import { MessagePackHubProtocol } from '@microsoft/signalr-protocol-msgpack'
+import { acceptHMRUpdate, defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useVTsuruHub = defineStore('VTsuruHub', () => {
+ const accountInfo = useAccount()
+ const signalRClient = ref()
+ const isInited = ref(false)
+ const isIniting = ref(false)
+
+ async function connectSignalR() {
+ if (isIniting.value) return
+ isIniting.value = true
+ //console.log('[Components-Event] 正在连接到 VTsuru 服务器...')
+ const connection = new HubConnectionBuilder()
+ .withUrl(BASE_HUB_URL + 'main?token=' + accountInfo.value.token, {
+ skipNegotiation: true,
+ transport: HttpTransportType.WebSockets,
+ logger: LogLevel.Error
+ })
+ .withAutomaticReconnect([0, 2000, 10000, 30000])
+ .withHubProtocol(new MessagePackHubProtocol())
+ .build()
+ connection.on('Finished', async () => {
+ connection.send('Finished')
+ })
+ connection.on('Disconnect', (reason: unknown) => {
+ console.log('[Hub] 被 VTsuru 服务器断开连接: ' + reason)
+ })
+
+ connection.onclose(reconnect)
+ try {
+ await connection.start()
+ console.log('[Hub] 已连接到 VTsuru 服务器')
+ signalRClient.value = connection
+ isInited.value = true
+ return true
+ } catch (e) {
+ console.log('[Hub] 无法连接到 VTsuru 服务器: ' + e)
+ return false
+ } finally {
+ isIniting.value = false
+ }
+ }
+ async function reconnect() {
+ try {
+ await signalRClient.value?.start()
+ signalRClient.value?.send('Reconnected')
+ console.log('[Hub] 已重新连接')
+ } catch (err) {
+ console.log(err)
+ setTimeout(reconnect, 5000) // 如果连接失败,则每5秒尝试一次重新启动连接
+ }
+ }
+
+ async function send(methodName: string, ...args: any[]) {
+ if (!isInited.value) {
+ await connectSignalR()
+ }
+ signalRClient.value?.send(methodName, ...args)
+ }
+ async function invoke(methodName: string, ...args: any[]) {
+ if (!isInited.value) {
+ await connectSignalR()
+ }
+ return signalRClient.value?.invoke(methodName, ...args)
+ }
+ async function on(eventName: string, listener: (args: any) => any) {
+ if (!isInited.value) {
+ await connectSignalR()
+ }
+ signalRClient.value?.on(eventName, listener)
+ }
+ async function off(eventName: string, listener: (args: any) => any) {
+ if (!isInited.value) {
+ await connectSignalR()
+ }
+ signalRClient.value?.off(eventName, listener)
+ }
+ async function onreconnected(listener: (id: any) => any) {
+ if (!isInited.value) {
+ await connectSignalR()
+ }
+ signalRClient.value?.onreconnected(listener)
+ }
+
+ function Init() {
+ if (!isInited.value) {
+ connectSignalR()
+ }
+ return useVTsuruHub()
+ }
+
+ return { signalRClient, Init, send, invoke, on, off, onreconnected }
+})
+
+if (import.meta.hot) {
+ import.meta.hot.accept(acceptHMRUpdate(useVTsuruHub, import.meta.hot))
+}
diff --git a/src/views/TestView.vue b/src/views/TestView.vue
new file mode 100644
index 0000000..8c38a58
--- /dev/null
+++ b/src/views/TestView.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+ master: {{ isMaster }}
+ {{ rtc?.peer?.id }}
+
+
+ 发送
+
+
+
\ No newline at end of file
diff --git a/src/views/manage/DashboardView.vue b/src/views/manage/DashboardView.vue
index 0d81f4b..997258b 100644
--- a/src/views/manage/DashboardView.vue
+++ b/src/views/manage/DashboardView.vue
@@ -486,6 +486,8 @@ onUnmounted(() => {
+
+
@@ -522,5 +524,4 @@ onUnmounted(() => {
确定
-
diff --git a/src/views/manage/Setting_PaymentView.vue b/src/views/manage/Setting_PaymentView.vue
index 4c3f880..a32ef8a 100644
--- a/src/views/manage/Setting_PaymentView.vue
+++ b/src/views/manage/Setting_PaymentView.vue
@@ -36,7 +36,10 @@ onMounted(() => {
-
+
+ WIP...
+
+
diff --git a/src/views/obs/MusicRequestOBS.vue b/src/views/obs/MusicRequestOBS.vue
index ccd992f..9401de1 100644
--- a/src/views/obs/MusicRequestOBS.vue
+++ b/src/views/obs/MusicRequestOBS.vue
@@ -2,6 +2,7 @@
import { DanmakuUserInfo, SongsInfo } from '@/api/api-models'
import { QueryGetAPI } from '@/api/query'
import { AVATAR_URL, MUSIC_REQUEST_API_URL } from '@/data/constants'
+import { useWebRTC } from '@/store/useRTC'
import { useElementSize } from '@vueuse/core'
import { NDivider, NEmpty, useMessage } from 'naive-ui'
import { computed, onMounted, onUnmounted, ref } from 'vue'
@@ -22,6 +23,7 @@ const route = useRoute()
const currentId = computed(() => {
return props.id ?? route.query.id
})
+const rtc = useWebRTC().Init('slave')
const listContainerRef = ref()
const footerRef = ref()
diff --git a/src/views/obs/QuestionDisplayOBS.vue b/src/views/obs/QuestionDisplayOBS.vue
index f7918c5..7eb03db 100644
--- a/src/views/obs/QuestionDisplayOBS.vue
+++ b/src/views/obs/QuestionDisplayOBS.vue
@@ -5,9 +5,11 @@ import { QUESTION_API_URL } from '@/data/constants'
import { useRouteQuery } from '@vueuse/router'
import { onMounted, onUnmounted, ref } from 'vue'
import QuestionDisplayCard from '../manage/QuestionDisplayCard.vue'
+import { useWebRTC } from '@/store/useRTC'
const hash = ref('')
const token = useRouteQuery('token')
+const rtc = useWebRTC().Init('slave')
const question = ref()
const setting = ref({} as Setting_QuestionDisplay)
diff --git a/src/views/obs/QueueOBS.vue b/src/views/obs/QueueOBS.vue
index 7a2ed1b..fb873fd 100644
--- a/src/views/obs/QueueOBS.vue
+++ b/src/views/obs/QueueOBS.vue
@@ -18,6 +18,7 @@ import { Vue3Marquee } from 'vue3-marquee'
import { NCard, NDivider, NEmpty, NSpace, NText, useMessage } from 'naive-ui'
import { List } from 'linqts'
import { isSameDay } from 'date-fns'
+import { useWebRTC } from '@/store/useRTC'
const props = defineProps<{
id?: number
@@ -28,6 +29,7 @@ const route = useRoute()
const currentId = computed(() => {
return props.id ?? route.query.id
})
+const rtc = useWebRTC().Init('slave')
const listContainerRef = ref()
const footerRef = ref()
diff --git a/vite.config.mts b/vite.config.mts
index 66c80a8..109137d 100644
--- a/vite.config.mts
+++ b/vite.config.mts
@@ -5,31 +5,34 @@ import path from 'path'
import { defineConfig } from 'vite'
import svgLoader from 'vite-svg-loader'
import Markdown from 'unplugin-vue-markdown/vite'
+import caddyTls from './plugins/vite-plugin-caddy'
export default defineConfig({
plugins: [
vue({
script: {
propsDestructure: true,
- defineModel: true,
+ defineModel: true
},
- include: [/\.vue$/, /\.md$/],
+ include: [/\.vue$/, /\.md$/]
}),
svgLoader(),
vueJsx(),
Markdown({
/* options */
}),
+ caddyTls()
],
resolve: {
alias: {
- '@': path.resolve(__dirname, 'src'),
- },
+ '@': path.resolve(__dirname, 'src')
+ }
},
define: {
'process.env': {},
+ global: 'window'
},
optimizeDeps: {
- include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router'],
- },
+ include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router']
+ }
})