优化结构
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user