优化结构

This commit is contained in:
Yvan
2026-08-28 18:22:16 +08:00
parent 8d290f2954
commit ac09f38940
6 changed files with 224 additions and 10 deletions
@@ -167,6 +167,14 @@
<span class="save-state">
{{ selected.configured ? '配置已创建' : '尚未保存配置' }}
</span>
<el-button
v-if="isWebSocket(selected)"
:icon="DocumentCopy"
:disabled="isBusy(selected)"
@click="openConnectionInfo"
>
连接信息
</el-button>
<el-button
:icon="Connection"
:loading="isTesting(selected)"
@@ -189,6 +197,38 @@
<el-empty v-else description="暂无通信集成配置" />
</div>
<el-dialog v-model="connectionInfoVisible" title="WebSocket 连接信息" width="560px">
<el-form label-position="top">
<el-form-item label="连接地址">
<el-input :model-value="connectionInfo.url" readonly>
<template #append>
<el-button :icon="DocumentCopy" @click="copyText(connectionInfo.url)">复制</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="JavaScript 示例">
<el-input :model-value="connectionInfo.example" type="textarea" :rows="4" readonly />
<el-button class="copy-example" :icon="DocumentCopy" @click="copyText(connectionInfo.example)">复制示例</el-button>
</el-form-item>
</el-form>
<el-alert
:type="connectionTestAlertType"
:closable="false"
show-icon
:title="connectionTestMessage"
/>
<template #footer>
<el-button
:icon="Connection"
:loading="connectionTestStatus === 'testing'"
@click="testConnectionInfo"
>
测试此地址
</el-button>
<el-button type="primary" @click="connectionInfoVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
@@ -199,9 +239,11 @@ import {
ChatLineRound,
Check,
Connection,
DocumentCopy,
Promotion,
Refresh
} from '@element-plus/icons-vue'
import { useUserStore } from '@/pinia/modules/user'
import {
getIntegrationConfigs,
saveIntegrationConfig,
@@ -261,6 +303,9 @@ const loading = ref(false)
const pending = reactive({})
const errors = reactive({})
const listDrafts = reactive({})
const connectionInfoVisible = ref(false)
const connectionTestStatus = ref('idle')
const userStore = useUserStore()
const selected = computed(
() =>
@@ -271,6 +316,45 @@ const selected = computed(
const integrationKey = (item) => `${item.kind}/${item.provider}`
const providerMeta = (item) => TARGETS[integrationKey(item)] || TARGETS[TARGET_ORDER[0]]
const isWebSocket = (item) => integrationKey(item) === 'websocket/melody'
const websocketOrigin = () => {
if (import.meta.env.DEV && import.meta.env.VITE_SERVER_PORT) {
const configuredBase = String(import.meta.env.VITE_BASE_PATH || window.location.origin)
const backend = new URL(configuredBase, window.location.origin)
if (
['127.0.0.1', 'localhost'].includes(backend.hostname) &&
!['127.0.0.1', 'localhost'].includes(window.location.hostname)
) {
backend.hostname = window.location.hostname
}
backend.port = String(import.meta.env.VITE_SERVER_PORT)
backend.protocol = backend.protocol === 'https:' ? 'wss:' : 'ws:'
return `${backend.protocol}//${backend.host}`
}
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
return `${protocol}//${window.location.host}`
}
const connectionInfo = computed(() => {
const path = String(selected.value?.config?.path || '/ws').trim() || '/ws'
const normalizedPath = path.startsWith('/') ? path : `/${path}`
const token = String(userStore.token || '').trim()
const url = `${websocketOrigin()}${normalizedPath}${token ? `?token=${encodeURIComponent(token)}` : ''}`
return {
url,
example: `const socket = new WebSocket(${JSON.stringify(url)})\n\nsocket.onopen = () => console.log('connected')\nsocket.onmessage = (event) => console.log(event.data)`
}
})
const connectionTestMessage = computed(() => {
if (connectionTestStatus.value === 'testing') return '等待服务器握手包...'
if (connectionTestStatus.value === 'success') return '握手成功,WebSocket 已连接。'
if (connectionTestStatus.value === 'error') return '握手失败,请查看 websocket/websocket.log。'
return '点击“测试此地址”验证浏览器到服务器的真实握手。'
})
const connectionTestAlertType = computed(() => {
if (connectionTestStatus.value === 'success') return 'success'
if (connectionTestStatus.value === 'error') return 'error'
return 'info'
})
const operationKey = (item) => integrationKey(item)
const errorKey = (item, fieldKey) => `${integrationKey(item)}:${fieldKey}`
const listKey = (item, fieldKey) => `${integrationKey(item)}:${fieldKey}`
@@ -518,6 +602,50 @@ const testSelected = async () => {
}
}
const openConnectionInfo = () => {
connectionTestStatus.value = 'idle'
connectionInfoVisible.value = true
}
const testConnectionInfo = () => {
if (connectionTestStatus.value === 'testing') return
connectionTestStatus.value = 'testing'
let settled = false
let socket
const finish = (status) => {
if (settled) return
settled = true
window.clearTimeout(timeout)
connectionTestStatus.value = status
if (status === 'success') {
ElMessage.success('WebSocket 握手成功')
socket?.close(1000, 'connection test complete')
} else {
ElMessage.error('WebSocket 握手失败,请查看独立日志')
}
}
const timeout = window.setTimeout(() => finish('error'), 8000)
try {
socket = new WebSocket(connectionInfo.value.url)
socket.onopen = () => finish('success')
socket.onerror = () => finish('error')
socket.onclose = () => {
if (!settled) finish('error')
}
} catch {
finish('error')
}
}
const copyText = async (value) => {
try {
await navigator.clipboard.writeText(value)
ElMessage.success('已复制到剪贴板')
} catch {
ElMessage.warning('复制失败,请手动复制')
}
}
const toggleIntegration = async (item, enabled) => {
if (isBusy(item)) return
const previous = item.enabled
@@ -754,6 +882,10 @@ onMounted(load)
line-height: 1.5;
}
.copy-example {
margin-top: 8px;
}
.editor-actions {
display: flex;
align-items: center;