1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2025-12-07 21:13:11 +00:00
Classworks/src/pages/debug-socket.vue
copilot-swe-agent[bot] 7049c35f2a revert: remove unrelated formatting changes, keep only the fix for duplicate deletePersistentNotification method
Co-authored-by: Sunwuyuan <88357633+Sunwuyuan@users.noreply.github.com>
2025-11-30 10:16:43 +00:00

413 lines
11 KiB
Vue

<template>
<v-container>
<v-row>
<v-col
cols="12"
md="6"
>
<v-card
border
class="mb-4"
>
<v-card-title>连接信息</v-card-title>
<v-card-text>
<v-list density="compact">
<v-list-item>
<v-list-item-title>Server URL</v-list-item-title>
<v-list-item-subtitle>{{ serverUrl }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>当前 KV Token</v-list-item-title>
<v-list-item-subtitle>{{ currentToken || '(未配置)' }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>连接状态</v-list-item-title>
<v-list-item-subtitle>
<v-chip
:color="connected ? 'success' : 'error'"
class="mr-2"
size="small"
>
{{ connected ? 'connected' : 'disconnected' }}
</v-chip>
<span v-if="socketId">id: {{ socketId }}</span>
</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>已加入 Token</v-list-item-title>
<v-list-item-subtitle>{{ joinedToken || '-' }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>当前数据键</v-list-item-title>
<v-list-item-subtitle>{{ currentDataKey }}</v-list-item-subtitle>
</v-list-item>
</v-list>
<v-divider class="my-4"/>
<v-row>
<v-col
cols="12"
md="8"
>
<v-text-field
v-model="manualToken"
clearable
label="手动加入 Token (留空使用配置的 Token)"
/>
</v-col>
<v-col
class="d-flex align-center"
cols="12"
md="4"
>
<v-btn
class="mr-2"
color="primary"
@click="handleJoinToken(manualToken || currentToken)"
>
加入
</v-btn>
<v-btn
:disabled="!joinedToken"
class="mr-2"
color="warning"
@click="handleLeaveToken(joinedToken)"
>
离开当前
</v-btn>
<v-btn
color="error"
variant="tonal"
@click="handleLeaveAll"
>
离开全部
</v-btn>
</v-col>
</v-row>
<v-divider class="my-4"/>
<v-row>
<v-col cols="12">
<v-card border color="primary" variant="tonal">
<v-card-title class="text-subtitle-1">聊天室消息</v-card-title>
<v-card-text>
<v-textarea
v-model="chatInput"
auto-grow
clearable
label="发送到当前已加入的设备频道"
rows="2"
/>
<div class="d-flex">
<v-spacer/>
<v-btn
:disabled="!canSendChat"
color="primary"
@click="sendChat"
>
发送聊天
</v-btn>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-btn
color="secondary"
variant="tonal"
@click="reconnect"
>
重新连接
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-card border>
<v-card-title>在线设备</v-card-title>
<v-card-text>
<v-btn
class="mb-3"
color="primary"
@click="fetchOnline"
>
刷新在线列表
</v-btn>
<v-list
v-if="onlineDevices.length"
density="compact"
>
<v-list-item
v-for="dev in onlineDevices"
:key="dev.uuid"
>
<template #prepend>
<v-avatar
:color="dev.connections > 0 ? 'success' : 'grey'"
size="24"
/>
</template>
<v-list-item-title>{{ dev.name || '(未命名)' }}</v-list-item-title>
<v-list-item-subtitle>{{ dev.uuid }} · 连接数 {{ dev.connections }}</v-list-item-subtitle>
<template #append>
<v-btn
size="small"
variant="text"
@click="handleSelectDevice(dev)"
>
选择
</v-btn>
</template>
</v-list-item>
</v-list>
<div
v-else
class="text-grey"
>
暂无数据
</div>
</v-card-text>
</v-card>
</v-col>
<v-col
cols="12"
md="6"
>
<v-card border>
<v-card-title class="d-flex align-center">
事件日志
<v-spacer/>
<v-btn
color="error"
size="small"
variant="text"
@click="clearLogs"
>
清空
</v-btn>
</v-card-title>
<v-card-text>
<v-list density="compact">
<v-list-item
v-for="(log, idx) in logs"
:key="idx"
>
<v-list-item-title>
<span class="text-caption text-grey">{{ log.time }}</span>
<span class="ml-2">{{ log.event }}</span>
</v-list-item-title>
<v-list-item-text>
<pre
class="mb-2"
style="white-space: pre-wrap"
>{{ log.payload }}</pre>
</v-list-item-text>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script setup>
import {ref, onMounted, onBeforeUnmount, computed} from 'vue'
import {getSetting} from '@/utils/settings'
import {
getSocket,
on as socketOn,
joinToken,
leaveToken,
leaveAll,
getServerUrl
} from '@/utils/socketClient'
import {sendChatMessage, DeviceEventTypes, formatDeviceInfo} from '@/utils/deviceEvents'
const currentToken = ref(getSetting('server.kvToken') || '')
const manualToken = ref('')
const joinedToken = ref('')
const connected = ref(false)
const socketId = ref('')
const logs = ref([])
const onlineDevices = ref([])
const chatInput = ref('')
const serverUrl = computed(() => getServerUrl())
const currentDataKey = computed(() => {
const now = new Date()
const y = now.getFullYear()
const m = String(now.getMonth() + 1).padStart(2, '0')
const d = String(now.getDate()).padStart(2, '0')
return `classworks-data-${y}${m}${d}`
})
function pushLog(event, payload) {
const time = new Date().toLocaleTimeString()
logs.value.unshift({
time,
event,
payload: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2)
})
if (logs.value.length > 200) logs.value.pop()
}
function wireSocketBaseEvents() {
const s = getSocket()
connected.value = !!s.connected
socketId.value = s.id || ''
s.on('connect', () => {
connected.value = true
socketId.value = s.id || ''
pushLog('connect', {id: s.id})
// re-join with token if set
if (joinedToken.value) joinToken(joinedToken.value)
})
s.on('disconnect', (reason) => {
connected.value = false
pushLog('disconnect', {reason})
})
s.on('connect_error', (err) => pushLog('connect_error', {message: err?.message}))
s.on('reconnect_attempt', (n) => pushLog('reconnect_attempt', {attempt: n}))
s.on('reconnect', (n) => pushLog('reconnect', {attempt: n}))
}
function wireBusinessEvents() {
// key changes
socketOn('kv-key-changed', (msg) => {
pushLog('kv-key-changed', msg)
})
// device joined count broadcast
socketOn('device-joined', (msg) => {
pushLog('device-joined', msg)
})
// join success
socketOn('joined', (msg) => {
pushLog('joined', msg)
})
// join error
socketOn('join-error', (msg) => {
pushLog('join-error', msg)
})
// chat message (旧接口)
socketOn('chat:message', (msg) => {
pushLog('chat:message', msg)
})
// device events (新通用事件接口)
socketOn('device-event', (eventData) => {
pushLog('device-event', eventData)
})
}
function handleJoinToken(token) {
try {
if (!token) {
pushLog('join-error', 'Token 为空')
return
}
joinToken(token)
joinedToken.value = token
pushLog('join-token', {token})
} catch (e) {
pushLog('join-token-error', String(e))
}
}
function handleLeaveToken(token) {
try {
leaveToken(token)
if (joinedToken.value === token) joinedToken.value = ''
pushLog('leave-token', {token})
} catch (e) {
pushLog('leave-token-error', String(e))
}
}
function handleLeaveAll() {
try {
leaveAll()
joinedToken.value = ''
pushLog('leave-all', {})
} catch (e) {
pushLog('leave-all-error', String(e))
}
}
function reconnect() {
try {
const s = getSocket()
s.connect()
} catch (e) {
pushLog('reconnect-error', String(e))
}
}
const canSendChat = computed(() => {
const text = chatInput.value?.trim() || ''
return !!(text && (joinedToken.value || currentToken.value))
})
function sendChat() {
try {
const text = (chatInput.value || '').trim()
if (!text) return
// 使用新的通用事件接口发送聊天消息
sendChatMessage(text)
pushLog('send-event', {type: DeviceEventTypes.CHAT, content: {text}})
chatInput.value = ''
} catch (e) {
pushLog('chat:error', String(e))
}
}
function handleSelectDevice(dev) {
// For now, just show a message that we need the token
pushLog('select-device', {
message: '请输入该设备对应的 KV Token 以加入频道',
device: dev
})
}
async function fetchOnline() {
try {
const resp = await fetch(`${serverUrl.value}/devices/online`)
const data = await resp.json()
onlineDevices.value = Array.isArray(data?.devices) ? data.devices : []
pushLog('fetch-online', {count: onlineDevices.value.length})
} catch (e) {
pushLog('fetch-online-error', String(e))
}
}
function clearLogs() {
logs.value = []
}
onMounted(() => {
// init socket + base events
getSocket()
wireSocketBaseEvents()
wireBusinessEvents()
// auto join with current token if present
if (currentToken.value) {
handleJoinToken(currentToken.value)
}
// prime online list
fetchOnline()
})
onBeforeUnmount(() => {
try {
if (joinedToken.value) leaveToken(joinedToken.value)
} catch (e) {
void e
}
})
</script>