diff --git a/client/src/sections/wuyuanbiaoba/actions/websocket.jsx b/client/src/sections/wuyuanbiaoba/actions/websocket.jsx
index f2d379d..f76b82c 100644
--- a/client/src/sections/wuyuanbiaoba/actions/websocket.jsx
+++ b/client/src/sections/wuyuanbiaoba/actions/websocket.jsx
@@ -125,6 +125,7 @@ const WebSocketContext = createContext();
// WebSocket Provider组件
export const WebSocketProvider = ({ children }) => {
const [isConnected, setIsConnected] = useState(false);
+ const [isReady, setIsReady] = useState(false); // 新增:连接真正就绪状态
const [connectionStatus, setConnectionStatus] = useState("disconnected");
const [lastMessage, setLastMessage] = useState(null); // 最后接收的消息
const [messageHistory, setMessageHistory] = useState([]); // 消息历史
@@ -132,9 +133,12 @@ export const WebSocketProvider = ({ children }) => {
const reconnectTimeoutRef = useRef(null);
const reconnectAttemptsRef = useRef(0);
const subscriptionsRef = useRef(new Map()); // 订阅器存储
+ const messageQueueRef = useRef([]); // 新增:消息队列
+ const readyCheckTimeoutRef = useRef(null); // 新增:就绪检查定时器
const maxReconnectAttempts = 5;
const reconnectInterval = 3000; // 3秒
const maxHistoryLength = 100; // 最大历史消息数量
+ const READY_TIMEOUT = 1500; // 连接建立后等待1.5秒确保TCP链路就绪
// 动态获取WebSocket连接地址,支持局域网访问
const getWebSocketUrl = () => {
@@ -217,6 +221,24 @@ export const WebSocketProvider = ({ children }) => {
return;
}
+ // 处理代理服务器的就绪信号
+ if (parsedData._from === 'proxy' && parsedData.cmd === 'ready') {
+ console.log('🟢 收到TCP连接就绪信号');
+
+ // 清除原有的定时器
+ if (readyCheckTimeoutRef.current) {
+ clearTimeout(readyCheckTimeoutRef.current);
+ readyCheckTimeoutRef.current = null;
+ }
+
+ // 立即设置为就绪状态
+ setIsReady(true);
+
+ // 发送队列中的消息
+ flushMessageQueue();
+ return;
+ }
+
// console.log(`收到消息 [${parsedData._from}:${parsedData.cmd}]:`, parsedData);
} catch (error) {
console.error("解析WebSocket消息失败:", error, data);
@@ -243,22 +265,60 @@ export const WebSocketProvider = ({ children }) => {
[notifySubscribers]
);
+ // 处理队列中的消息
+ const flushMessageQueue = useCallback(() => {
+ if (messageQueueRef.current.length === 0) return;
+
+ console.log(`📤 开始发送队列中的 ${messageQueueRef.current.length} 条消息`);
+
+ const queue = [...messageQueueRef.current];
+ messageQueueRef.current = [];
+
+ queue.forEach((message, index) => {
+ setTimeout(() => {
+ if (socketRef.current?.readyState === WebSocket.OPEN) {
+ try {
+ socketRef.current.send(message);
+ console.log(`✅ 队列消息 ${index + 1}/${queue.length} 已发送`);
+ } catch (error) {
+ console.error(`❌ 发送队列消息 ${index + 1} 失败:`, error);
+ // 发送失败的消息重新加入队列
+ messageQueueRef.current.push(message);
+ }
+ }
+ }, index * 100); // 每条消息间隔100ms发送,避免拥塞
+ });
+ }, []);
+
// 发送消息
const sendMessage = useCallback((message) => {
- if (socketRef.current?.readyState === WebSocket.OPEN) {
+ // 如果连接就绪,直接发送
+ if (socketRef.current?.readyState === WebSocket.OPEN && isReady) {
try {
socketRef.current.send(message);
- // console.log('发送WebSocket消息:', message);
return true;
} catch (error) {
console.error("发送WebSocket消息失败:", error);
return false;
}
- } else {
- console.warn("WebSocket未连接,无法发送消息");
- return false;
}
- }, []);
+ // 如果已连接但未就绪,加入队列
+ else if (socketRef.current?.readyState === WebSocket.OPEN && !isReady) {
+ const cmdMatch = message.match(/"cmd"\s*:\s*"([^"]+)"/);
+ const cmd = cmdMatch ? cmdMatch[1] : 'unknown';
+ console.log(`⏳ 连接尚未完全就绪,消息 [${cmd}] 已加入队列`);
+ messageQueueRef.current.push(message);
+ return true;
+ }
+ // 如果未连接,加入队列
+ else {
+ const cmdMatch = message.match(/"cmd"\s*:\s*"([^"]+)"/);
+ const cmd = cmdMatch ? cmdMatch[1] : 'unknown';
+ console.warn(`⚠️ WebSocket未连接,消息 [${cmd}] 已加入队列,等待连接建立`);
+ messageQueueRef.current.push(message);
+ return true;
+ }
+ }, [isReady]);
// 连接WebSocket
const connect = useCallback(() => {
@@ -267,23 +327,24 @@ export const WebSocketProvider = ({ children }) => {
}
setConnectionStatus("connecting");
+ setIsReady(false); // 重置就绪状态
console.log("尝试连接WebSocket:", websocketUrl);
try {
socketRef.current = new WebSocket(websocketUrl);
socketRef.current.onopen = () => {
- console.log("WebSocket连接已建立");
+ console.log("✅ WebSocket连接已建立");
setIsConnected(true);
setConnectionStatus("connected");
reconnectAttemptsRef.current = 0;
- // 发送初始消息或心跳
- // sendMessage(JSON.stringify({
- // _from: 'setup',
- // cmd: 'init',
- // values: { timestamp: Date.now() }
- // }));
+ // 等待一段时间确保底层TCP连接完全建立
+ // 如果在此期间收到代理的 ready 信号,则会提前取消这个定时器
+ readyCheckTimeoutRef.current = setTimeout(() => {
+ setIsReady(true);
+ flushMessageQueue();
+ }, READY_TIMEOUT);
};
socketRef.current.onmessage = (event) => {
@@ -300,21 +361,28 @@ export const WebSocketProvider = ({ children }) => {
};
socketRef.current.onclose = (event) => {
- console.log("WebSocket连接已关闭:", event.code, event.reason);
+ console.log("❌ WebSocket连接已关闭:", event.code, event.reason);
setIsConnected(false);
+ setIsReady(false); // 重置就绪状态
setConnectionStatus("disconnected");
+ // 清除就绪检查定时器
+ if (readyCheckTimeoutRef.current) {
+ clearTimeout(readyCheckTimeoutRef.current);
+ readyCheckTimeoutRef.current = null;
+ }
+
// 自动重连逻辑
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
reconnectAttemptsRef.current++;
console.log(
- `尝试重连 ${reconnectAttemptsRef.current}/${maxReconnectAttempts}`
+ `🔄 尝试重连 ${reconnectAttemptsRef.current}/${maxReconnectAttempts}`
);
reconnectTimeoutRef.current = setTimeout(() => {
connect();
}, reconnectInterval);
} else {
- console.log("达到最大重连次数,停止重连");
+ console.log("🛑 达到最大重连次数,停止重连");
setConnectionStatus("error");
}
};
@@ -326,8 +394,9 @@ export const WebSocketProvider = ({ children }) => {
} catch (error) {
console.error("创建WebSocket连接失败:", error);
setConnectionStatus("error");
+ setIsReady(false);
}
- }, [handleMessage, sendMessage]);
+ }, [handleMessage, flushMessageQueue]);
// 断开连接
const disconnect = useCallback(() => {
@@ -335,12 +404,18 @@ export const WebSocketProvider = ({ children }) => {
clearTimeout(reconnectTimeoutRef.current);
}
+ if (readyCheckTimeoutRef.current) {
+ clearTimeout(readyCheckTimeoutRef.current);
+ readyCheckTimeoutRef.current = null;
+ }
+
if (socketRef.current) {
socketRef.current.close(1000, "用户主动断开");
socketRef.current = null;
}
setIsConnected(false);
+ setIsReady(false);
setConnectionStatus("disconnected");
reconnectAttemptsRef.current = 0;
}, []);
@@ -373,6 +448,9 @@ export const WebSocketProvider = ({ children }) => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
+ if (readyCheckTimeoutRef.current) {
+ clearTimeout(readyCheckTimeoutRef.current);
+ }
if (socketRef.current) {
socketRef.current.close();
}
@@ -383,6 +461,7 @@ export const WebSocketProvider = ({ children }) => {
const value = {
// 连接状态
isConnected,
+ isReady, // 新增:暴露就绪状态
connectionStatus,
// 连接控制
diff --git a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx
index 4f4eabe..5cd71ca 100644
--- a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx
+++ b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect } from "react";
import {
Card,
Input,
@@ -12,6 +12,8 @@ import {
message,
Space,
Typography,
+ Spin,
+ Alert,
} from "antd";
import {
SettingOutlined,
@@ -22,122 +24,251 @@ import {
BellOutlined,
FilterOutlined,
CloudUploadOutlined,
+ SyncOutlined,
} from "@ant-design/icons";
+import useAdvancedSettings from "../hooks/useAdvancedSettings";
const { Option } = Select;
const { Title, Text } = Typography;
const AdvancedSettings = ({ onLogout }) => {
- const [deviceId, setDeviceId] = useState("");
- const [fps, setFps] = useState(5);
- const [enableOfflineAlert, setEnableOfflineAlert] = useState(false);
- const [offlineThreshold, setOfflineThreshold] = useState(60);
- const [enableFiltering, setEnableFiltering] = useState(true);
- const [filterMethod, setFilterMethod] = useState("median");
- const [windowSize, setWindowSize] = useState(5);
- const [filterThreshold, setFilterThreshold] = useState(0.1);
- const [flowThreshold, setFlowThreshold] = useState(10.0);
- const [enableMqtt, setEnableMqtt] = useState(true);
- const [brokerAddress, setBrokerAddress] = useState("");
- const [mqttPort, setMqttPort] = useState("");
- const [mqttTopic, setMqttTopic] = useState("");
- const [mqttClientId, setMqttClientId] = useState("");
- const [mqttUsername, setMqttUsername] = useState("");
- const [mqttPassword, setMqttPassword] = useState("");
+ // 使用高级配置 Hook
+ const {
+ settings,
+ loading,
+ isConnected,
+ isReady,
+ fetchAllSettings,
+ saveAllSettings,
+ resetSettings,
+ updateLocalSettings,
+ } = useAdvancedSettings();
+
+ // 从 settings 中提取配置
+ const deviceId = settings.deviceId;
+ const fps = settings.dataFps;
+ const enableOfflineAlert = settings.alertConfig?.enable ?? false;
+ const offlineThreshold = settings.alertConfig?.intervalSec ?? 60;
+ const enableFiltering = settings.filterConfig?.enable ?? true;
+ const filterMethod = settings.filterConfig?.method ?? 'median';
+ const windowSize = settings.filterConfig?.size ?? 5;
+ const filterThreshold = Math.abs(settings.filterConfig?.threshold ?? 0.1);
+ const flowThreshold = settings.filterConfig?.imgThreshold ?? 10.0;
+ const enableMqtt = settings.mqttConfig?.enable ?? true;
+ const brokerAddress = settings.mqttConfig?.mqtt?.broker ?? '';
+ const mqttPort = settings.mqttConfig?.mqtt?.port ?? 1883;
+ const mqttTopic = settings.mqttConfig?.mqtt?.topic ?? '';
+ const mqttClientId = settings.mqttConfig?.mqtt?.client_id ?? '';
+ const mqttUsername = settings.mqttConfig?.mqtt?.username ?? '';
+ const mqttPassword = settings.mqttConfig?.mqtt?.password ?? '';
+
+ // 更新本地状态的辅助函数
+ const setDeviceId = (value) => updateLocalSettings({ deviceId: value });
+ const setFps = (value) => updateLocalSettings({ dataFps: value });
+ const setEnableOfflineAlert = (value) =>
+ updateLocalSettings({
+ alertConfig: {
+ enable: value,
+ intervalSec: settings.alertConfig?.intervalSec ?? 60
+ },
+ });
+ const setOfflineThreshold = (value) =>
+ updateLocalSettings({
+ alertConfig: {
+ enable: settings.alertConfig?.enable ?? false,
+ intervalSec: value
+ },
+ });
+ const setEnableFiltering = (value) =>
+ updateLocalSettings({
+ filterConfig: { ...settings.filterConfig, enable: value },
+ });
+ const setFilterMethod = (value) =>
+ updateLocalSettings({
+ filterConfig: { ...settings.filterConfig, method: value },
+ });
+ const setWindowSize = (value) =>
+ updateLocalSettings({
+ filterConfig: { ...settings.filterConfig, size: value },
+ });
+ const setFilterThreshold = (value) =>
+ updateLocalSettings({
+ filterConfig: { ...settings.filterConfig, threshold: -Math.abs(value) },
+ });
+ const setFlowThreshold = (value) =>
+ updateLocalSettings({
+ filterConfig: { ...settings.filterConfig, imgThreshold: value },
+ });
+ const setEnableMqtt = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ enable: value,
+ mqtt: settings.mqttConfig?.mqtt ?? {
+ broker: '',
+ port: 1883,
+ topic: '',
+ username: '',
+ password: '',
+ client_id: ''
+ }
+ },
+ });
+ const setBrokerAddress = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ ...settings.mqttConfig,
+ mqtt: {
+ ...(settings.mqttConfig?.mqtt ?? {}),
+ broker: value
+ },
+ },
+ });
+ const setMqttPort = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ ...settings.mqttConfig,
+ mqtt: {
+ ...(settings.mqttConfig?.mqtt ?? {}),
+ port: value
+ },
+ },
+ });
+ const setMqttTopic = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ ...settings.mqttConfig,
+ mqtt: {
+ ...(settings.mqttConfig?.mqtt ?? {}),
+ topic: value
+ },
+ },
+ });
+ const setMqttClientId = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ ...settings.mqttConfig,
+ mqtt: {
+ ...(settings.mqttConfig?.mqtt ?? {}),
+ client_id: value
+ },
+ },
+ });
+ const setMqttUsername = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ ...settings.mqttConfig,
+ mqtt: {
+ ...(settings.mqttConfig?.mqtt ?? {}),
+ username: value
+ },
+ },
+ });
+ const setMqttPassword = (value) =>
+ updateLocalSettings({
+ mqttConfig: {
+ ...settings.mqttConfig,
+ mqtt: {
+ ...(settings.mqttConfig?.mqtt ?? {}),
+ password: value
+ },
+ },
+ });
+
+ const handleSave = () => {
+ saveAllSettings();
+ };
- const handleSave = () => message.success("配置已保存");
const handleReset = () => {
- setDeviceId("");
- setFps(5);
- setEnableOfflineAlert(false);
- setOfflineThreshold(60);
- setEnableFiltering(true);
- setFilterMethod("median");
- setWindowSize(5);
- setFilterThreshold(0.1);
- setFlowThreshold(10.0);
- setEnableMqtt(true);
- setBrokerAddress("");
- setMqttPort("");
- setMqttTopic("");
- setMqttClientId("");
- setMqttUsername("");
- setMqttPassword("");
- message.info("配置已重置");
+ fetchAllSettings();
};
return (
-
-
+
-
-
-
-
-
- 高级参数配置
-
-
- 针对下位机运行的参数配置,修改后请及时保存(慎用)
-
-
-
-
-
- }
- onClick={handleReset}
- size="large"
- style={{ height: 42, borderRadius: 8 }}
- >
- 重置/刷新
-
- }
- onClick={handleSave}
- size="large"
- style={{
- height: 42,
- borderRadius: 8,
- background: "white",
- color: "#667eea",
- borderColor: "white",
- }}
- >
- 保存更改
-
-
-
-
-
+ {/* WebSocket 连接状态提示 */}
+ {!isConnected && (
+
+ )}
+
+
+
+
+
+
+
+ 高级参数配置
+
+
+ 针对下位机运行的参数配置,修改后请及时保存(慎用)
+
+
+
+
+
+
+ }
+ onClick={handleReset}
+ size="large"
+ disabled={!isConnected || loading}
+ style={{ height: 42, borderRadius: 8 }}
+ >
+ 刷新配置
+
+ }
+ onClick={handleSave}
+ size="large"
+ disabled={!isConnected || loading}
+ style={{
+ height: 42,
+ borderRadius: 8,
+ background: "white",
+ color: "#667eea",
+ borderColor: "white",
+ }}
+ >
+ 保存更改
+
+
+
+
+
@@ -487,6 +618,7 @@ const AdvancedSettings = ({ onLogout }) => {
+
);
};
diff --git a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx
index 7a0db36..2fb077d 100644
--- a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx
+++ b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx
@@ -59,7 +59,6 @@ const CameraView = ({
if (window.env && window.env.FS_FLAG === "localdev") {
streamUrl = `http://10.8.30.179:2240/video_flow`; //开发用
}
- console.log(streamUrl,'测试')
// 应用变换
const applyTransform = () => {
diff --git a/client/src/sections/wuyuanbiaoba/container/index.jsx b/client/src/sections/wuyuanbiaoba/container/index.jsx
index b21bb96..44e16dd 100644
--- a/client/src/sections/wuyuanbiaoba/container/index.jsx
+++ b/client/src/sections/wuyuanbiaoba/container/index.jsx
@@ -27,7 +27,7 @@ const { Title } = Typography;
// 内部组件,使用WebSocket hook
const WuyuanbiaobaContent = () => {
- const { isConnected, sendMessage } = useWebSocket();
+ const { isConnected, isReady, sendMessage } = useWebSocket();
// 订阅实时数据
const realtimeDataSubscription = useWebSocketSubscription("dev", "data");
@@ -513,6 +513,7 @@ const WuyuanbiaobaContent = () => {
>
视觉位移计配置工具
+