252 lines
6.1 KiB
Vue
252 lines
6.1 KiB
Vue
<script setup>import { ref, onUnmounted, watch } from 'vue';
|
||
|
||
// 定义组件的props
|
||
const props = defineProps({
|
||
url: {
|
||
type: String,
|
||
default: 'ws://localhost:8805'
|
||
},
|
||
protocols: {
|
||
type: Array,
|
||
default: () => ['device-web']
|
||
},
|
||
autoConnect: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
maxReconnectAttempts: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
initialReconnectDelay: {
|
||
type: Number,
|
||
default: 1000
|
||
},
|
||
maxReconnectDelay: {
|
||
type: Number,
|
||
default: 30000
|
||
}
|
||
});
|
||
|
||
// 定义组件的emits
|
||
const emit = defineEmits([
|
||
'connect', // 连接成功时触发
|
||
'disconnect', // 断开连接时触发
|
||
'message', // 收到消息时触发
|
||
'error', // 发生错误时触发
|
||
'status-change' // 连接状态改变时触发
|
||
]);
|
||
|
||
// 内部状态管理
|
||
const ws = ref(null);
|
||
const isConnected = ref(false);
|
||
const reconnectAttempts = ref(0);
|
||
const reconnectDelay = ref(props.initialReconnectDelay);
|
||
const reconnectTimer = ref(null);
|
||
const manualDisconnect = ref(false);
|
||
const currentUrl = ref(props.url);
|
||
|
||
// 调试日志函数
|
||
const debugLog = (...args) => {
|
||
if (window.DEBUG) {
|
||
console.log('[WebSocketClient]', ...args);
|
||
}
|
||
};
|
||
|
||
// 更新连接状态并发出状态改变事件
|
||
const updateConnectionStatus = (connected) => {
|
||
if (isConnected.value !== connected) {
|
||
isConnected.value = connected;
|
||
emit('status-change', connected);
|
||
if (connected) {
|
||
// 连接成功时发出连接事件
|
||
emit('connect', currentUrl.value);
|
||
// 重置重连状态
|
||
reconnectAttempts.value = 0;
|
||
reconnectDelay.value = props.initialReconnectDelay;
|
||
} else {
|
||
ws.value = null;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 取消当前的重连尝试
|
||
const cancelReconnect = () => {
|
||
if (reconnectTimer.value) {
|
||
clearTimeout(reconnectTimer.value);
|
||
reconnectTimer.value = null;
|
||
}
|
||
};
|
||
|
||
// 智能重连策略
|
||
const reconnect = () => {
|
||
// 如果是手动断开,不进行重连
|
||
if (manualDisconnect.value) {
|
||
return;
|
||
}
|
||
|
||
// 达到最大重连次数且最大重连次数不为0时,停止重连
|
||
// 当maxReconnectAttempts为0时表示无限重试
|
||
if (props.maxReconnectAttempts > 0 && reconnectAttempts.value >= props.maxReconnectAttempts) {
|
||
debugLog('已达到最大重连次数');
|
||
emit('error', '已达到最大重连次数,请手动尝试重新连接');
|
||
return;
|
||
}
|
||
|
||
// 指数退避重连延迟
|
||
const delay = Math.min(reconnectDelay.value * Math.pow(2, reconnectAttempts.value), props.maxReconnectDelay);
|
||
|
||
// 调整日志显示:当maxReconnectAttempts为0时显示"无限重试"模式
|
||
if (props.maxReconnectAttempts === 0) {
|
||
debugLog(`无限重试模式:将在${Math.round(delay/1000)}秒后尝试第${reconnectAttempts.value + 1}次重连`);
|
||
} else {
|
||
debugLog(`将在${Math.round(delay/1000)}秒后尝试第${reconnectAttempts.value + 1}/${props.maxReconnectAttempts}次重连`);
|
||
}
|
||
|
||
reconnectTimer.value = setTimeout(() => {
|
||
reconnectAttempts.value++;
|
||
connect();
|
||
}, delay);
|
||
};
|
||
|
||
// 连接WebSocket服务器
|
||
const connect = (url = null) => {
|
||
// 如果提供了新的URL,则更新当前URL
|
||
if (url) {
|
||
currentUrl.value = url;
|
||
}
|
||
|
||
manualDisconnect.value = false;
|
||
cancelReconnect();
|
||
|
||
try {
|
||
debugLog(`尝试连接到: ${currentUrl.value}`);
|
||
|
||
ws.value = new WebSocket(currentUrl.value, props.protocols);
|
||
|
||
// 连接成功
|
||
ws.value.onopen = () => {
|
||
debugLog('连接成功');
|
||
updateConnectionStatus(true);
|
||
};
|
||
|
||
// 接收消息
|
||
ws.value.onmessage = (event) => {
|
||
debugLog('收到消息:', event.data);
|
||
emit('message', event.data);
|
||
};
|
||
|
||
// 连接关闭
|
||
ws.value.onclose = (event) => {
|
||
debugLog(`连接关闭: 代码=${event.code}, 原因=${event.reason}`);
|
||
updateConnectionStatus(false);
|
||
emit('disconnect', event.code, event.reason);
|
||
|
||
// 异常断开时自动重连
|
||
if (event.code === 1006) {
|
||
reconnect();
|
||
} else if (!manualDisconnect.value) {
|
||
// 非手动断开且非1006错误时也尝试重连
|
||
reconnect();
|
||
}
|
||
};
|
||
|
||
// 连接错误
|
||
ws.value.onerror = (error) => {
|
||
debugLog('WebSocket错误:', error);
|
||
emit('error', '连接错误,请检查服务器是否运行');
|
||
updateConnectionStatus(false);
|
||
// 错误时也尝试重连
|
||
if (!manualDisconnect.value) {
|
||
reconnect();
|
||
}
|
||
};
|
||
} catch (error) {
|
||
debugLog('连接失败:', error);
|
||
emit('error', '连接失败,请重试');
|
||
// 连接失败也尝试重连
|
||
if (!manualDisconnect.value) {
|
||
reconnect();
|
||
}
|
||
}
|
||
};
|
||
|
||
// 断开WebSocket连接
|
||
const disconnect = () => {
|
||
if (ws.value) {
|
||
manualDisconnect.value = true;
|
||
cancelReconnect();
|
||
ws.value.close();
|
||
updateConnectionStatus(false);
|
||
debugLog('已断开连接');
|
||
}
|
||
};
|
||
|
||
// 发送消息
|
||
const send = (message) => {
|
||
if (!message || !ws.value || !isConnected.value) {
|
||
debugLog('发送消息失败: 连接未建立或消息为空');
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
ws.value.send(message);
|
||
debugLog('消息发送成功:', message);
|
||
return true;
|
||
} catch (error) {
|
||
debugLog('消息发送失败:', error);
|
||
emit('error', '消息发送失败');
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// 切换连接状态
|
||
const toggleConnection = () => {
|
||
if (isConnected.value) {
|
||
disconnect();
|
||
} else {
|
||
connect();
|
||
}
|
||
};
|
||
|
||
// 监听props.url的变化
|
||
watch(() => props.url, (newUrl) => {
|
||
if (newUrl && newUrl !== currentUrl.value) {
|
||
// 如果当前已连接,则先断开连接
|
||
if (isConnected.value) {
|
||
disconnect();
|
||
// 延迟重新连接,确保旧连接完全关闭
|
||
setTimeout(() => {
|
||
connect(newUrl);
|
||
}, 500);
|
||
} else {
|
||
// 如果当前未连接,直接更新URL
|
||
currentUrl.value = newUrl;
|
||
}
|
||
}
|
||
});
|
||
|
||
// 组件挂载时,如果设置了autoConnect为true,则自动连接
|
||
if (props.autoConnect) {
|
||
connect();
|
||
}
|
||
|
||
// 组件卸载时清理资源
|
||
onUnmounted(() => {
|
||
disconnect();
|
||
});
|
||
|
||
// 暴露方法给父组件使用
|
||
defineExpose({
|
||
connect,
|
||
disconnect,
|
||
send,
|
||
toggleConnection,
|
||
isConnected,
|
||
currentUrl
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 这个组件没有UI界面元素 -->
|
||
</template> |