Files
JoyD/Web/Vue/CubeLib/examples/CubeWebSocket/AutoReconnectExample.vue
2026-01-30 15:38:48 +08:00

387 lines
8.2 KiB
Vue

<template>
<div class="auto-reconnect-example">
<div class="config-panel">
<h3>重连配置</h3>
<div class="config-item">
<label class="config-label">自动重连:</label>
<input type="checkbox" v-model="reconnectEnabled" />
</div>
<div class="config-item">
<label class="config-label">最大重连延迟:</label>
<input
type="number"
v-model.number="maxReconnectDelay"
:disabled="!reconnectEnabled"
min="1000"
step="1000"
/>
<span class="unit">毫秒</span>
</div>
<div class="config-item">
<label class="config-label">调试模式:</label>
<input type="checkbox" v-model="debugMode" />
</div>
</div>
<div class="status-panel">
<h3>连接状态</h3>
<div class="status-display">
<div :class="['status-indicator', status]"></div>
<div class="status-info">
<div class="status-text">{{ statusText }}</div>
<div v-if="reconnectAttempts > 0" class="reconnect-info">
重连次数: {{ reconnectAttempts }}
</div>
<div v-if="nextReconnectDelay > 0" class="reconnect-info">
下次重连: {{ nextReconnectDelay }}秒后
</div>
</div>
</div>
</div>
<div class="timeline-panel">
<h3>重连时间线</h3>
<div class="timeline">
<div
v-for="(event, index) in timeline"
:key="index"
:class="['timeline-item', event.type]"
>
<div class="timeline-time">{{ event.time }}</div>
<div class="timeline-content">{{ event.message }}</div>
</div>
</div>
</div>
<div class="control-panel">
<h3>控制</h3>
<div class="control-buttons">
<button @click="handleConnect" :disabled="status === 'connecting'">
连接
</button>
<button @click="handleDisconnect" :disabled="status === 'disconnected'">
断开
</button>
<button @click="handleSendTest" :disabled="status !== 'connected'">
发送测试消息
</button>
<button @click="handleSimulateDisconnect" :disabled="status !== 'connected'">
模拟断开
</button>
<button @click="handleClearTimeline">清空时间线</button>
</div>
</div>
<CubeWebSocket
ref="wsRef"
ws-url="ws://localhost:8086/ws"
:auto-connect="false"
:reconnect="reconnectEnabled"
:max-reconnect-delay="maxReconnectDelay"
:debug-mode="debugMode"
@connected="handleConnected"
@disconnected="handleDisconnected"
@error="handleError"
@status-changed="handleStatusChanged"
@connecting="handleConnecting"
@reconnecting="handleReconnecting"
@message-sent="handleMessageSent"
/>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import CubeWebSocket from 'joyd.web.vue.cubelib'
const wsRef = ref(null)
const status = ref('disconnected')
const reconnectEnabled = ref(true)
const maxReconnectDelay = ref(30000)
const debugMode = ref(false)
const reconnectAttempts = ref(0)
const nextReconnectDelay = ref(0)
const timeline = ref([])
const statusText = computed(() => {
const statusMap = {
disconnected: '未连接',
connecting: '连接中',
connected: '已连接',
reconnecting: '重连中',
error: '错误'
}
return statusMap[status.value] || status.value
})
const addTimelineEvent = (type, message) => {
timeline.value.unshift({
type,
time: new Date().toLocaleTimeString(),
message
})
if (timeline.value.length > 20) {
timeline.value = timeline.value.slice(0, 20)
}
}
const handleConnected = () => {
console.log('[自动重连示例] WebSocket 已连接')
reconnectAttempts.value = 0
nextReconnectDelay.value = 0
addTimelineEvent('success', '连接成功')
}
const handleDisconnected = (code, reason) => {
console.log('[自动重连示例] WebSocket 已断开:', code, reason)
addTimelineEvent('warning', `连接断开 (${code}): ${reason}`)
}
const handleError = (error) => {
console.error('[自动重连示例] WebSocket 错误:', error)
addTimelineEvent('error', `连接错误: ${error.message}`)
}
const handleStatusChanged = (newStatus) => {
console.log('[自动重连示例] 状态变化:', newStatus)
status.value = newStatus
}
const handleConnecting = () => {
console.log('[自动重连示例] 开始连接')
addTimelineEvent('info', '开始连接')
}
const handleReconnecting = () => {
console.log('[自动重连示例] 开始重连')
reconnectAttempts.value++
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.value - 1), maxReconnectDelay.value)
nextReconnectDelay.value = Math.round(delay / 1000)
addTimelineEvent('info', `开始重连 (第${reconnectAttempts.value}次,${nextReconnectDelay.value}秒后)`)
}
const handleMessageSent = (message) => {
console.log('[自动重连示例] 消息已发送:', message)
addTimelineEvent('success', `发送消息: ${message.type}`)
}
const handleConnect = () => {
wsRef.value?.connect()
}
const handleDisconnect = () => {
wsRef.value?.disconnect()
}
const handleSendTest = () => {
wsRef.value?.send('test', {
text: '测试消息',
timestamp: Date.now()
})
}
const handleSimulateDisconnect = () => {
if (wsRef.value) {
wsRef.value.disconnect()
addTimelineEvent('warning', '手动断开连接')
}
}
const handleClearTimeline = () => {
timeline.value = []
}
</script>
<style scoped>
.auto-reconnect-example {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.config-panel,
.status-panel,
.timeline-panel,
.control-panel {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.config-panel h3,
.status-panel h3,
.timeline-panel h3,
.control-panel h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
}
.config-item {
display: flex;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
}
.config-label {
width: 120px;
color: #666;
}
.config-item input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.config-item input[type="number"] {
width: 100px;
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.config-item input:disabled {
background: #f0f0f0;
cursor: not-allowed;
}
.unit {
margin-left: 8px;
color: #999;
}
.status-display {
display: flex;
align-items: center;
gap: 20px;
}
.status-indicator {
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
color: #fff;
}
.status-indicator.disconnected {
background: #999;
}
.status-indicator.connecting {
background: #faad14;
}
.status-indicator.connected {
background: #52c41a;
}
.status-indicator.reconnecting {
background: #1890ff;
}
.status-indicator.error {
background: #ff4d4f;
}
.status-info {
flex: 1;
}
.status-text {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.reconnect-info {
font-size: 14px;
color: #666;
margin-top: 4px;
}
.timeline {
max-height: 300px;
overflow-y: auto;
background: #fff;
border-radius: 4px;
padding: 10px;
}
.timeline-item {
display: flex;
gap: 12px;
padding: 8px;
border-bottom: 1px solid #f0f0f0;
}
.timeline-item:last-child {
border-bottom: none;
}
.timeline-item.success {
border-left: 3px solid #52c41a;
}
.timeline-item.warning {
border-left: 3px solid #faad14;
}
.timeline-item.error {
border-left: 3px solid #ff4d4f;
}
.timeline-item.info {
border-left: 3px solid #1890ff;
}
.timeline-time {
min-width: 80px;
color: #999;
font-size: 12px;
}
.timeline-content {
flex: 1;
font-size: 14px;
color: #333;
}
.control-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.control-buttons button {
padding: 10px 20px;
border: none;
border-radius: 4px;
background: #667eea;
color: #fff;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.control-buttons button:hover:not(:disabled) {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
.control-buttons button:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
</style>