mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
add rtc feature, set payment page to wip state
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
<link rel="preconnect" href="https://rsms.me/" />
|
<link rel="preconnect" href="https://rsms.me/" />
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||||
|
<script src="https://unpkg.com/peerjs@latest/dist/peerjs.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -11,21 +11,22 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/signalr": "^8.0.7",
|
"@microsoft/signalr": "^8.0.7",
|
||||||
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
|
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
|
||||||
"@types/node": "^22.8.2",
|
"@types/node": "^22.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.12.1",
|
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
||||||
"@vicons/fluent": "^0.12.0",
|
"@vicons/fluent": "^0.12.0",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^1.1.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vue/cli": "^5.0.8",
|
"@vue/cli": "^5.0.8",
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^11.2.0",
|
||||||
"@vueuse/router": "^11.1.0",
|
"@vueuse/router": "^11.2.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"easy-speech": "^2.4.0",
|
"easy-speech": "^2.4.0",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-plugin-import": "^2.31.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",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"fast-xml-parser": "^4.5.0",
|
"fast-xml-parser": "^4.5.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
@@ -34,9 +35,10 @@
|
|||||||
"linqts": "^2.0.0",
|
"linqts": "^2.0.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"music-metadata-browser": "^2.5.11",
|
"music-metadata-browser": "^2.5.11",
|
||||||
"pinia": "^2.2.4",
|
"peerjs": "^1.5.4",
|
||||||
|
"pinia": "^2.2.6",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"qrcode.vue": "^3.5.1",
|
"qrcode.vue": "^3.6.0",
|
||||||
"queue-typescript": "^1.0.1",
|
"queue-typescript": "^1.0.1",
|
||||||
"unplugin-vue-markdown": "^0.26.2",
|
"unplugin-vue-markdown": "^0.26.2",
|
||||||
"uuid": "^11.0.2",
|
"uuid": "^11.0.2",
|
||||||
@@ -50,16 +52,16 @@
|
|||||||
"vue3-aplayer": "^1.7.3",
|
"vue3-aplayer": "^1.7.3",
|
||||||
"vue3-marquee": "^4.2.2",
|
"vue3-marquee": "^4.2.2",
|
||||||
"vueuc": "^0.4.64",
|
"vueuc": "^0.4.64",
|
||||||
"worker-timers": "^8.0.10",
|
"worker-timers": "^8.0.11",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@types/bun": "^1.1.12",
|
"@types/bun": "^1.1.13",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/obs-studio": "^2.17.2",
|
"@types/obs-studio": "^2.17.2",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@typescript-eslint/parser": "^8.12.1",
|
"@typescript-eslint/parser": "^8.13.0",
|
||||||
"@vicons/ionicons5": "^0.12.0",
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
"@vue/eslint-config-typescript": "^14.1.3",
|
"@vue/eslint-config-typescript": "^14.1.3",
|
||||||
|
|||||||
49
plugins/vite-plugin-caddy.ts
Normal file
49
plugins/vite-plugin-caddy.ts
Normal file
@@ -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 }
|
||||||
@@ -9,7 +9,6 @@ import { useRoute } from 'vue-router'
|
|||||||
|
|
||||||
export const ACCOUNT = ref<AccountInfo>({} as AccountInfo)
|
export const ACCOUNT = ref<AccountInfo>({} as AccountInfo)
|
||||||
export const isLoadingAccount = ref(true)
|
export const isLoadingAccount = ref(true)
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const { message } = createDiscreteApi(['message'])
|
const { message } = createDiscreteApi(['message'])
|
||||||
const cookie = useLocalStorage('JWT_Token', '')
|
const cookie = useLocalStorage('JWT_Token', '')
|
||||||
@@ -47,6 +46,7 @@ export async function GetSelfAccount() {
|
|||||||
}
|
}
|
||||||
export function UpdateAccountLoop() {
|
export function UpdateAccountLoop() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
const route = useRoute()
|
||||||
if (ACCOUNT.value && route?.name != 'question-display') {
|
if (ACCOUNT.value && route?.name != 'question-display') {
|
||||||
// 防止在问题详情页刷新
|
// 防止在问题详情页刷新
|
||||||
GetSelfAccount()
|
GetSelfAccount()
|
||||||
@@ -63,47 +63,68 @@ function refreshCookie() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function SaveAccountSettings() {
|
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[]) {
|
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(
|
export async function SaveSetting(
|
||||||
name: 'Queue' | 'Point' | 'Index' | 'General' | 'QuestionDisplay' | 'SongRequest' | 'QuestionBox' | 'SendEmail',
|
name:
|
||||||
setting: unknown,
|
| 'Queue'
|
||||||
|
| 'Point'
|
||||||
|
| 'Index'
|
||||||
|
| 'General'
|
||||||
|
| 'QuestionDisplay'
|
||||||
|
| 'SongRequest'
|
||||||
|
| 'QuestionBox'
|
||||||
|
| 'SendEmail',
|
||||||
|
setting: unknown
|
||||||
) {
|
) {
|
||||||
const result = await QueryPostAPIWithParams(
|
const result = await QueryPostAPIWithParams(
|
||||||
ACCOUNT_API_URL + 'update-single-setting',
|
ACCOUNT_API_URL + 'update-single-setting',
|
||||||
{
|
{
|
||||||
name,
|
name
|
||||||
},
|
},
|
||||||
setting,
|
setting
|
||||||
)
|
)
|
||||||
return result.message
|
return result.message
|
||||||
}
|
}
|
||||||
export async function UpdateFunctionEnable(func: FunctionTypes) {
|
export async function UpdateFunctionEnable(func: FunctionTypes) {
|
||||||
if (ACCOUNT.value) {
|
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)) {
|
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 {
|
} else {
|
||||||
ACCOUNT.value.settings.enableFunctions.push(func)
|
ACCOUNT.value.settings.enableFunctions.push(func)
|
||||||
}
|
}
|
||||||
await SaveEnableFunctions(ACCOUNT.value?.settings.enableFunctions)
|
await SaveEnableFunctions(ACCOUNT.value?.settings.enableFunctions)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success(`已${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}`)
|
message.success(
|
||||||
|
`已${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}`
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (ACCOUNT.value) {
|
if (ACCOUNT.value) {
|
||||||
ACCOUNT.value.settings.enableFunctions = oldValue
|
ACCOUNT.value.settings.enableFunctions = oldValue
|
||||||
}
|
}
|
||||||
message.error(
|
message.error(
|
||||||
`${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}失败: ${data.message}`,
|
`${ACCOUNT.value?.settings.enableFunctions.includes(func) ? '启用' : '禁用'}失败: ${data.message}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.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
|
return ACCOUNT
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function Register(name: string, email: string, password: string, token: string): Promise<APIRoot<string>> {
|
export async function Register(
|
||||||
|
name: string,
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
token: string
|
||||||
|
): Promise<APIRoot<string>> {
|
||||||
return QueryPostAPI<string>(`${ACCOUNT_API_URL}register`, {
|
return QueryPostAPI<string>(`${ACCOUNT_API_URL}register`, {
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
token,
|
token
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function Login(nameOrEmail: string, password: string): Promise<APIRoot<string>> {
|
export async function Login(
|
||||||
|
nameOrEmail: string,
|
||||||
|
password: string
|
||||||
|
): Promise<APIRoot<string>> {
|
||||||
return QueryPostAPI<string>(`${ACCOUNT_API_URL}login`, {
|
return QueryPostAPI<string>(`${ACCOUNT_API_URL}login`, {
|
||||||
nameOrEmail,
|
nameOrEmail,
|
||||||
password,
|
password
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function Self(): Promise<APIRoot<AccountInfo>> {
|
export async function Self(): Promise<APIRoot<AccountInfo>> {
|
||||||
return QueryPostAPI<AccountInfo>(`${ACCOUNT_API_URL}self`)
|
return QueryPostAPI<AccountInfo>(`${ACCOUNT_API_URL}self`)
|
||||||
}
|
}
|
||||||
export async function AddBiliBlackList(id: number, name: string): Promise<APIRoot<unknown>> {
|
export async function AddBiliBlackList(
|
||||||
|
id: number,
|
||||||
|
name: string
|
||||||
|
): Promise<APIRoot<unknown>> {
|
||||||
return QueryGetAPI<AccountInfo>(`${ACCOUNT_API_URL}black-list/add-bili`, {
|
return QueryGetAPI<AccountInfo>(`${ACCOUNT_API_URL}black-list/add-bili`, {
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function DelBiliBlackList(id: number): Promise<APIRoot<unknown>> {
|
export async function DelBiliBlackList(id: number): Promise<APIRoot<unknown>> {
|
||||||
return QueryGetAPI<AccountInfo>(`${ACCOUNT_API_URL}black-list/del-bili`, {
|
return QueryGetAPI<AccountInfo>(`${ACCOUNT_API_URL}black-list/del-bili`, {
|
||||||
id: id,
|
id: id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function DelBlackList(id: number): Promise<APIRoot<unknown>> {
|
export async function DelBlackList(id: number): Promise<APIRoot<unknown>> {
|
||||||
return QueryGetAPI<AccountInfo>(`${ACCOUNT_API_URL}black-list/del`, {
|
return QueryGetAPI<AccountInfo>(`${ACCOUNT_API_URL}black-list/del`, {
|
||||||
id: id,
|
id: id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function downloadConfigDirect(name: string) {
|
export function downloadConfigDirect(name: string) {
|
||||||
return QueryGetAPI<string>(VTSURU_API_URL + 'get-config', {
|
return QueryGetAPI<string>(VTSURU_API_URL + 'get-config', {
|
||||||
name: name,
|
name: name
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function DownloadConfig<T>(name: string) {
|
export async function DownloadConfig<T>(name: string) {
|
||||||
try {
|
try {
|
||||||
const resp = await QueryGetAPI<string>(VTSURU_API_URL + 'get-config', {
|
const resp = await QueryGetAPI<string>(VTSURU_API_URL + 'get-config', {
|
||||||
name: name,
|
name: name
|
||||||
})
|
})
|
||||||
if (resp.code == 200) {
|
if (resp.code == 200) {
|
||||||
console.log('已获取配置文件: ' + name)
|
console.log('已获取配置文件: ' + name)
|
||||||
return {
|
return {
|
||||||
msg: undefined,
|
msg: undefined,
|
||||||
data: JSON.parse(resp.data) as T,
|
data: JSON.parse(resp.data) as T
|
||||||
}
|
}
|
||||||
} else if (resp.code == 404) {
|
} else if (resp.code == 404) {
|
||||||
console.error(`未找到名为 ${name} 的配置文件`)
|
console.error(`未找到名为 ${name} 的配置文件`)
|
||||||
return {
|
return {
|
||||||
msg: `未找到名为 ${name} 的配置文件, 需要先上传`,
|
msg: `未找到名为 ${name} 的配置文件, 需要先上传`,
|
||||||
data: undefined,
|
data: undefined
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(`无法获取配置文件 [${name}]: ` + resp.message)
|
console.error(`无法获取配置文件 [${name}]: ` + resp.message)
|
||||||
return {
|
return {
|
||||||
msg: `无法获取配置文件 [${name}]: ` + resp.message,
|
msg: `无法获取配置文件 [${name}]: ` + resp.message,
|
||||||
data: undefined,
|
data: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`无法获取配置文件 [${name}]: ` + err)
|
console.error(`无法获取配置文件 [${name}]: ` + err)
|
||||||
return {
|
return {
|
||||||
msg: `无法获取配置文件 [${name}]: ` + err,
|
msg: `无法获取配置文件 [${name}]: ` + err,
|
||||||
data: undefined,
|
data: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +218,7 @@ export async function UploadConfig(name: string, data: unknown) {
|
|||||||
try {
|
try {
|
||||||
const resp = await QueryPostAPI(VTSURU_API_URL + 'set-config', {
|
const resp = await QueryPostAPI(VTSURU_API_URL + 'set-config', {
|
||||||
name: name,
|
name: name,
|
||||||
json: JSON.stringify(data),
|
json: JSON.stringify(data)
|
||||||
})
|
})
|
||||||
if (resp.code == 200) {
|
if (resp.code == 200) {
|
||||||
console.log('已保存配置文件至服务器:' + name)
|
console.log('已保存配置文件至服务器:' + name)
|
||||||
@@ -208,7 +240,10 @@ export async function EnableFunction(func: FunctionTypes) {
|
|||||||
if (await updateFunctionEnable()) {
|
if (await updateFunctionEnable()) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +255,10 @@ export async function DisableFunction(func: FunctionTypes) {
|
|||||||
if (!ACCOUNT.value.settings.enableFunctions.includes(func)) {
|
if (!ACCOUNT.value.settings.enableFunctions.includes(func)) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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()) {
|
if (await updateFunctionEnable()) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@@ -234,7 +272,9 @@ export async function DisableFunction(func: FunctionTypes) {
|
|||||||
async function updateFunctionEnable() {
|
async function updateFunctionEnable() {
|
||||||
if (ACCOUNT.value) {
|
if (ACCOUNT.value) {
|
||||||
try {
|
try {
|
||||||
const data = await SaveEnableFunctions(ACCOUNT.value.settings.enableFunctions)
|
const data = await SaveEnableFunctions(
|
||||||
|
ACCOUNT.value.settings.enableFunctions
|
||||||
|
)
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
192
src/data/RTCClient.ts
Normal file
192
src/data/RTCClient.ts
Normal file
@@ -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<ComponentsEventHubModel[]>('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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,7 +97,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'notfound',
|
name: 'notfound',
|
||||||
component: import('@/views/NotfoundView.vue'),
|
component: () => import('@/views/NotfoundView.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '页面不存在',
|
title: '页面不存在',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,15 @@ export default [
|
|||||||
name: 'question-display',
|
name: 'question-display',
|
||||||
component: () => import('@/views/single/QuestionDisplay.vue'),
|
component: () => import('@/views/single/QuestionDisplay.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '棉花糖展示页',
|
title: '棉花糖展示页'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/playground/test',
|
||||||
|
name: 'test',
|
||||||
|
component: () => import('@/views/TestView.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '测试页'
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const useAuthStore = defineStore('BiliAuth', () => {
|
|||||||
async function getAuthInfo() {
|
async function getAuthInfo() {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
if(!currentToken.value) return
|
||||||
await QueryBiliAuthGetAPI<BiliAuthModel>(BILI_AUTH_API_URL + 'info').then((data) => {
|
await QueryBiliAuthGetAPI<BiliAuthModel>(BILI_AUTH_API_URL + 'info').then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
biliAuth.value = data.data
|
biliAuth.value = data.data
|
||||||
|
|||||||
44
src/store/useRTC.ts
Normal file
44
src/store/useRTC.ts
Normal file
@@ -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<MasterRTCClient>()
|
||||||
|
const slaveClient = ref<SlaveRTCClient>()
|
||||||
|
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))
|
||||||
|
}
|
||||||
106
src/store/useVTsuruHub.ts
Normal file
106
src/store/useVTsuruHub.ts
Normal file
@@ -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<signalR.HubConnection>()
|
||||||
|
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<T>(methodName: string, ...args: any[]) {
|
||||||
|
if (!isInited.value) {
|
||||||
|
await connectSignalR()
|
||||||
|
}
|
||||||
|
return signalRClient.value?.invoke<T>(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))
|
||||||
|
}
|
||||||
36
src/views/TestView.vue
Normal file
36
src/views/TestView.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useAccount } from '@/api/account';
|
||||||
|
import { MasterRTCClient, SlaveRTCClient } from '@/data/RTCClient';
|
||||||
|
import { useWebRTC } from '@/store/useRTC';
|
||||||
|
import { NButton, NInput, NSpin } from 'naive-ui';
|
||||||
|
import { LogLevel, Peer } from 'peerjs';
|
||||||
|
import { computed, onMounted, Ref, ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const target = ref('');
|
||||||
|
const accountInfo = useAccount()
|
||||||
|
const route = useRoute()
|
||||||
|
const inputMsg = ref('')
|
||||||
|
|
||||||
|
const isMaster = computed(() => {
|
||||||
|
return route.query.slave == null || route.query.slave == undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
let rtc: Ref<MasterRTCClient | undefined, MasterRTCClient | undefined> | Ref<SlaveRTCClient | undefined, SlaveRTCClient | undefined>
|
||||||
|
|
||||||
|
function mount() {
|
||||||
|
rtc = useWebRTC().Init(isMaster.value ? 'master' : 'slave')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NSpin show v-if="!accountInfo.id" />
|
||||||
|
<div v-else @vue:mounted="mount">
|
||||||
|
master: {{ isMaster }}
|
||||||
|
{{ rtc?.peer?.id }}
|
||||||
|
<template v-if="isMaster">
|
||||||
|
<NInput v-model:value="inputMsg" />
|
||||||
|
<NButton @click="rtc.send('test', inputMsg)"> 发送 </NButton>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -486,6 +486,8 @@ onUnmounted(() => {
|
|||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
|
||||||
|
<VueTurnstile ref="turnstile" :site-key="TURNSTILE_KEY" v-model="token" theme="auto" style="text-align: center" />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NButton @click="accountInfo?.isBiliVerified ? ChangeBili() : BindBili()" type="success"
|
<NButton @click="accountInfo?.isBiliVerified ? ChangeBili() : BindBili()" type="success"
|
||||||
:loading="!token || isLoading">
|
:loading="!token || isLoading">
|
||||||
@@ -522,5 +524,4 @@ onUnmounted(() => {
|
|||||||
<NButton @click="BindBiliAuth()" type="success" :loading="isLoading" :disabled="!biliAuthText"> 确定 </NButton>
|
<NButton @click="BindBiliAuth()" type="success" :loading="isLoading" :disabled="!biliAuthText"> 确定 </NButton>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
<VueTurnstile ref="turnstile" :site-key="TURNSTILE_KEY" v-model="token" theme="auto" style="text-align: center" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NTabs animated type="line">
|
<div v-if="true">
|
||||||
|
WIP...
|
||||||
|
</div>
|
||||||
|
<NTabs v-else animated type="line">
|
||||||
<NTabPane name="弹幕储存" tab="弹幕储存">
|
<NTabPane name="弹幕储存" tab="弹幕储存">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
<component :is="tabDisplay(ConsumptionTypes.DanmakuStorage)" />
|
<component :is="tabDisplay(ConsumptionTypes.DanmakuStorage)" />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { DanmakuUserInfo, SongsInfo } from '@/api/api-models'
|
import { DanmakuUserInfo, SongsInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { AVATAR_URL, MUSIC_REQUEST_API_URL } from '@/data/constants'
|
import { AVATAR_URL, MUSIC_REQUEST_API_URL } from '@/data/constants'
|
||||||
|
import { useWebRTC } from '@/store/useRTC'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
import { NDivider, NEmpty, useMessage } from 'naive-ui'
|
import { NDivider, NEmpty, useMessage } from 'naive-ui'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
@@ -22,6 +23,7 @@ const route = useRoute()
|
|||||||
const currentId = computed(() => {
|
const currentId = computed(() => {
|
||||||
return props.id ?? route.query.id
|
return props.id ?? route.query.id
|
||||||
})
|
})
|
||||||
|
const rtc = useWebRTC().Init('slave')
|
||||||
|
|
||||||
const listContainerRef = ref()
|
const listContainerRef = ref()
|
||||||
const footerRef = ref()
|
const footerRef = ref()
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { QUESTION_API_URL } from '@/data/constants'
|
|||||||
import { useRouteQuery } from '@vueuse/router'
|
import { useRouteQuery } from '@vueuse/router'
|
||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import QuestionDisplayCard from '../manage/QuestionDisplayCard.vue'
|
import QuestionDisplayCard from '../manage/QuestionDisplayCard.vue'
|
||||||
|
import { useWebRTC } from '@/store/useRTC'
|
||||||
|
|
||||||
const hash = ref('')
|
const hash = ref('')
|
||||||
const token = useRouteQuery('token')
|
const token = useRouteQuery('token')
|
||||||
|
const rtc = useWebRTC().Init('slave')
|
||||||
|
|
||||||
const question = ref<QAInfo>()
|
const question = ref<QAInfo>()
|
||||||
const setting = ref<Setting_QuestionDisplay>({} as Setting_QuestionDisplay)
|
const setting = ref<Setting_QuestionDisplay>({} as Setting_QuestionDisplay)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { Vue3Marquee } from 'vue3-marquee'
|
|||||||
import { NCard, NDivider, NEmpty, NSpace, NText, useMessage } from 'naive-ui'
|
import { NCard, NDivider, NEmpty, NSpace, NText, useMessage } from 'naive-ui'
|
||||||
import { List } from 'linqts'
|
import { List } from 'linqts'
|
||||||
import { isSameDay } from 'date-fns'
|
import { isSameDay } from 'date-fns'
|
||||||
|
import { useWebRTC } from '@/store/useRTC'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id?: number
|
id?: number
|
||||||
@@ -28,6 +29,7 @@ const route = useRoute()
|
|||||||
const currentId = computed(() => {
|
const currentId = computed(() => {
|
||||||
return props.id ?? route.query.id
|
return props.id ?? route.query.id
|
||||||
})
|
})
|
||||||
|
const rtc = useWebRTC().Init('slave')
|
||||||
|
|
||||||
const listContainerRef = ref()
|
const listContainerRef = ref()
|
||||||
const footerRef = ref()
|
const footerRef = ref()
|
||||||
|
|||||||
@@ -5,31 +5,34 @@ import path from 'path'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import svgLoader from 'vite-svg-loader'
|
import svgLoader from 'vite-svg-loader'
|
||||||
import Markdown from 'unplugin-vue-markdown/vite'
|
import Markdown from 'unplugin-vue-markdown/vite'
|
||||||
|
import caddyTls from './plugins/vite-plugin-caddy'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue({
|
vue({
|
||||||
script: {
|
script: {
|
||||||
propsDestructure: true,
|
propsDestructure: true,
|
||||||
defineModel: true,
|
defineModel: true
|
||||||
},
|
},
|
||||||
include: [/\.vue$/, /\.md$/],
|
include: [/\.vue$/, /\.md$/]
|
||||||
}),
|
}),
|
||||||
svgLoader(),
|
svgLoader(),
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
Markdown({
|
Markdown({
|
||||||
/* options */
|
/* options */
|
||||||
}),
|
}),
|
||||||
|
caddyTls()
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
'@': path.resolve(__dirname, 'src')
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
'process.env': {},
|
'process.env': {},
|
||||||
|
global: 'window'
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router'],
|
include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router']
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user