Compare commits

...

4 Commits

Author SHA1 Message Date
qinjian 37066e2184 feat: 优化高级设置 Hook,调整配置获取和保存逻辑 3 weeks ago
qinjian 505aa3f199 feat:增强 WebSocket 处理机制,新增高级设置管理功能 3 weeks ago
qinjian f542142645 feat: 添加高级设置组件并在主组件中集成 1 month ago
qinjian b7182c7066 feat: 添加高级配置权限验证功能及相关组件 1 month ago
  1. 113
      client/src/sections/wuyuanbiaoba/actions/websocket.jsx
  2. 114
      client/src/sections/wuyuanbiaoba/components/AdvancedConfigAuth.jsx
  3. 630
      client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx
  4. 1
      client/src/sections/wuyuanbiaoba/components/CameraView.jsx
  5. 2
      client/src/sections/wuyuanbiaoba/components/index.js
  6. 72
      client/src/sections/wuyuanbiaoba/container/index.jsx
  7. 475
      client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js
  8. 1
      client/src/sections/wuyuanbiaoba/hooks/useAuth.js
  9. 246
      client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md
  10. 4
      config.cjs
  11. 2
      package.json
  12. 20
      server/tcpProxy/index.js

113
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.5TCP
// 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,
//

114
client/src/sections/wuyuanbiaoba/components/AdvancedConfigAuth.jsx

@ -0,0 +1,114 @@
import React, { useState } from 'react';
import { Card, Input, Button, message } from 'antd';
import { LockOutlined } from '@ant-design/icons';
/**
* 高级配置密码验证组件
*/
const AdvancedConfigAuth = ({ verifyPassword, onUnlock }) => {
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleVerify = async () => {
if (!password) {
message.warning('请输入密码');
return;
}
setLoading(true);
try {
const isValid = await verifyPassword(password);
if (isValid) {
message.success('密码正确,已解锁高级配置');
onUnlock && onUnlock();
} else {
message.error('密码错误,请重试');
setPassword('');
}
} catch (error) {
console.error('密码验证失败:', error);
message.error('验证过程出错,请重试');
} finally {
setLoading(false);
}
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleVerify();
}
};
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: 'calc(100vh - 92px)',
padding: '24px',
}}
>
<Card
style={{
width: 400,
textAlign: 'center',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
}}
>
<div
style={{
width: 64,
height: 64,
borderRadius: '50%',
backgroundColor: '#e6f4ff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 24px',
}}
>
<LockOutlined style={{ fontSize: 32, color: '#1890ff' }} />
</div>
<h2 style={{ marginBottom: 8 }}>内部高级配置</h2>
<p style={{ color: '#999', marginBottom: 24 }}>
请输入管理员访问密码以继续
</p>
<Input.Password
size="large"
placeholder="请输入密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyPress={handleKeyPress}
prefix={<LockOutlined style={{ color: '#999' }} />}
style={{ marginBottom: 16 }}
/>
<Button
type="primary"
size="large"
block
loading={loading}
onClick={handleVerify}
>
解锁配置
</Button>
<div
style={{
marginTop: 24,
fontSize: 12,
color: '#999',
}}
>
视觉位移计配置工具 v{window.env?.FS_VERSION || ''} Build {new Date().getFullYear()}
</div>
</Card>
</div>
);
};
export default AdvancedConfigAuth;

630
client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx

@ -0,0 +1,630 @@
import React, { useEffect } from "react";
import {
Card,
Input,
Slider,
InputNumber,
Switch,
Select,
Row,
Col,
Button,
message,
Space,
Typography,
Spin,
Alert,
} from "antd";
import {
SettingOutlined,
SaveOutlined,
DatabaseOutlined,
ClockCircleOutlined,
BellOutlined,
FilterOutlined,
CloudUploadOutlined,
SyncOutlined,
} from "@ant-design/icons";
import useAdvancedSettings from "../hooks/useAdvancedSettings";
const { Option } = Select;
const { Title, Text } = Typography;
const AdvancedSettings = ({ onLogout }) => {
// 使 Hook
const {
settings,
loading,
isConnected,
isReady,
fetchAllSettings,
saveAllSettings,
resetSettings,
updateLocalSettings,
} = useAdvancedSettings();
// isReady true
const hasFetchedRef = React.useRef(false);
useEffect(() => {
if (isReady && !hasFetchedRef.current) {
fetchAllSettings();
hasFetchedRef.current = true;
}
}, [isReady, fetchAllSettings]);
// 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 = 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 handleReset = () => {
fetchAllSettings();
};
return (
<Spin spinning={loading} tip="正在加载配置...">
<div
style={{
padding: "12px",
backgroundColor: "#f5f7fa",
minHeight: "calc(100vh - 92px)",
}}
>
{/* WebSocket 连接状态提示 */}
{!isConnected && (
<Alert
message="WebSocket 未连接"
description="当前未连接到设备,无法读取或保存配置。请检查网络连接。"
type="warning"
showIcon
style={{ marginBottom: 12, borderRadius: 8 }}
/>
)}
<Card
style={{
marginBottom: 12,
background:
"linear-gradient(135deg, #01152cff 0%, #063b77ff 100%)",
borderRadius: 12,
}}
styles={{ padding: 12 }}
>
<Row justify="space-between" align="middle">
<Col>
<Space direction="vertical" size={4}>
<Title
level={2}
style={{
margin: 0,
color: "white",
display: "flex",
alignItems: "center",
gap: 12,
}}
>
<SettingOutlined />
高级参数配置
</Title>
<Text
style={{
color: "rgba(255,255,255,0.85)",
fontSize: 14,
}}
>
针对下位机运行的参数配置修改后请及时保存(慎用)
</Text>
</Space>
</Col>
<Col>
<Space size="middle">
<Button
icon={<SyncOutlined spin={loading} />}
onClick={handleReset}
size="large"
disabled={!isConnected || loading}
style={{ height: 42, borderRadius: 8 }}
>
刷新配置
</Button>
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSave}
size="large"
disabled={!isConnected || loading}
style={{
height: 42,
borderRadius: 8,
background: "white",
color: "#667eea",
borderColor: "white",
}}
>
保存更改
</Button>
</Space>
</Col>
</Row>
</Card>
<Row gutter={12}>
<Col xs={12} lg={12}>
<Card
title={
<Space>
<DatabaseOutlined style={{ color: "#667eea" }} />
<span>基础设备信息</span>
</Space>
}
style={{
marginBottom: 12,
borderRadius: 12,
paddingLeft: 24,
paddingRight: 24,
}}
>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
设备编码
</div>
<Input
value={deviceId}
onChange={(e) => setDeviceId(e.target.value)}
placeholder="请输入设备编码"
size="large"
style={{ borderRadius: 8 }}
/>
</Card>
<Card
title={
<Space>
<ClockCircleOutlined style={{ color: "#667eea" }} />
<span>数据帧率 (FPS)</span>
</Space>
}
style={{
marginBottom: 12,
borderRadius: 12,
paddingLeft: 24,
paddingRight: 24,
}}
>
<div style={{ marginBottom: 12, fontWeight: 500 }}>
数据帧率
</div>
<Row gutter={12} align="middle">
<Col span={18}>
<Slider
min={1}
max={30}
value={fps}
onChange={setFps}
marks={{ 1: "1 Hz", 30: "30 Hz" }}
/>
</Col>
<Col span={6}>
<InputNumber
min={1}
max={30}
value={fps}
onChange={setFps}
style={{ width: "100%", borderRadius: 8 }}
size="large"
/>
</Col>
</Row>
<Text type="secondary" style={{ fontSize: 12 }}>
有效范围 1~30 Hz
</Text>
</Card>
<Card
title={
<Space>
<BellOutlined style={{ color: "#667eea" }} />
<span>异常监控与报警</span>
</Space>
}
style={{
marginBottom: 12,
borderRadius: 12,
paddingLeft: 24,
paddingRight: 24,
}}
>
<div
style={{
background:
"linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%)",
padding: 20,
borderRadius: 12,
marginBottom: 12,
}}
>
<Row justify="space-between" align="middle">
<Col>
<Text strong>启用离线超时告警</Text>
<br />
<Text type="secondary" style={{ fontSize: 12 }}>
超过此时长未接收到新数据则触发报警
</Text>
</Col>
<Col>
<Switch
checked={enableOfflineAlert}
onChange={setEnableOfflineAlert}
/>
</Col>
</Row>
</div>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
超时阈值
</div>
<InputNumber
min={10}
max={300}
value={offlineThreshold}
onChange={setOfflineThreshold}
size="large"
addonAfter="秒"
style={{ width: "100%", borderRadius: 8 }}
/>
</Card>
</Col>
<Col xs={12} lg={12}>
<Card
title={
<Space>
<FilterOutlined style={{ color: "#667eea" }} />
<span>数据滤波与异常记录</span>
</Space>
}
style={{
marginBottom: 12,
borderRadius: 12,
paddingLeft: 24,
paddingRight: 24,
}}
>
<div
style={{
background:
"linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%)",
padding: 20,
borderRadius: 12,
marginBottom: 20,
}}
>
<Row justify="space-between" align="middle">
<Col>
<Text strong>滤波配置</Text>
<br />
<Text type="secondary" style={{ fontSize: 12 }}>
启用数据滤波算法
</Text>
</Col>
<Col>
<Switch
checked={enableFiltering}
onChange={setEnableFiltering}
checkedChildren="已启用"
unCheckedChildren="已停用"
/>
</Col>
</Row>
</div>
<Row gutter={[12, 12]}>
<Col span={12}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
滤波方法
</div>
<Select
value={filterMethod}
onChange={setFilterMethod}
size="large"
disabled={!enableFiltering}
style={{ width: "100%", borderRadius: 8 }}
>
<Option value="median">中值滤波</Option>
</Select>
</Col>
<Col span={12}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
窗口大小
</div>
<InputNumber
min={3}
max={21}
step={2}
value={windowSize}
onChange={setWindowSize}
size="large"
disabled={!enableFiltering}
style={{ width: "100%", borderRadius: 8 }}
/>
</Col>
<Col span={12}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
滤波阈值
</div>
<InputNumber
value={filterThreshold}
onChange={setFilterThreshold}
addonAfter="mm"
size="large"
disabled={!enableFiltering}
style={{ width: "100%", borderRadius: 8 }}
/>
</Col>
<Col span={12}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
波动阈值
</div>
<InputNumber
min={0}
max={100}
step={0.1}
value={flowThreshold}
onChange={setFlowThreshold}
addonAfter="mm"
size="large"
disabled={!enableFiltering}
style={{ width: "100%", borderRadius: 8 }}
/>
</Col>
</Row>
</Card>
<Card
title={
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Space>
<CloudUploadOutlined style={{ color: "#667eea" }} />
<span>数据上报 (MQTT)</span>
</Space>
<Switch
checked={enableMqtt}
onChange={setEnableMqtt}
checkedChildren="启用"
unCheckedChildren="禁用"
/>
</div>
}
style={{
borderRadius: 12,
paddingLeft: 24,
paddingRight: 24,
}}
>
<Row gutter={[12, 12]}>
<Col span={12}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
Broker Address
</div>
<Input
value={brokerAddress}
onChange={(e) => setBrokerAddress(e.target.value)}
placeholder="例如: 218.3.126.49"
size="large"
disabled={!enableMqtt}
style={{ borderRadius: 8 }}
/>
</Col>
<Col span={8}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
Port
</div>
<Input
value={mqttPort}
onChange={(e) => setMqttPort(e.target.value)}
placeholder="1883"
size="large"
disabled={!enableMqtt}
style={{ borderRadius: 8 }}
/>
</Col>
<Col span={12}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
Topic
</div>
<Input
value={mqttTopic}
onChange={(e) => setMqttTopic(e.target.value)}
placeholder="例如: wybb/z/mqtt179"
size="large"
disabled={!enableMqtt}
style={{ borderRadius: 8 }}
/>
</Col>
<Col span={8}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
Client ID
</div>
<Input
value={mqttClientId}
onChange={(e) => setMqttClientId(e.target.value)}
placeholder="wybb_z1_123"
size="large"
disabled={!enableMqtt}
style={{ borderRadius: 8 }}
/>
</Col>
<Col span={8}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
Username
</div>
<Input
value={mqttUsername}
onChange={(e) => setMqttUsername(e.target.value)}
placeholder="用户名"
size="large"
disabled={!enableMqtt}
style={{ borderRadius: 8 }}
/>
</Col>
<Col span={8}>
<div style={{ marginBottom: 4, fontWeight: 500 }}>
Password
</div>
<Input.Password
value={mqttPassword}
onChange={(e) => setMqttPassword(e.target.value)}
placeholder="密码"
size="large"
disabled={!enableMqtt}
style={{ borderRadius: 8 }}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
</div>
</Spin>
);
};
export default AdvancedSettings;

1
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 = () => {

2
client/src/sections/wuyuanbiaoba/components/index.js

@ -5,3 +5,5 @@ export { default as RealtimeCharts } from './RealtimeCharts';
export { default as RealtimeDataTable } from './RealtimeDataTable';
export { default as TemplateModal } from './TemplateModal';
export { default as TargetDetailModal } from './TargetDetailModal';
export { default as AdvancedConfigAuth } from './AdvancedConfigAuth';
export { default as AdvancedSettings } from './AdvancedSettings';

72
client/src/sections/wuyuanbiaoba/container/index.jsx

@ -1,5 +1,6 @@
import React, { useState, useEffect } from "react";
import { Tabs, Typography } from "antd";
import { Tabs, Typography, Menu } from "antd";
import { MonitorOutlined, LockOutlined } from "@ant-design/icons";
import ExcelJS from "exceljs";
import {
@ -10,6 +11,8 @@ import {
RealtimeDataTable,
TemplateModal,
TargetDetailModal,
AdvancedConfigAuth,
AdvancedSettings,
} from "../components";
import {
WebSocketProvider,
@ -18,12 +21,13 @@ import {
} from "../actions/websocket.jsx";
import { useTemplateStorage } from "../hooks/useTemplateStorage.js";
import { useTargetStorage } from "../hooks/useTargetStorage.js";
import { useAuth } from "../hooks/useAuth.js";
import { useRef } from "react";
const { Title } = Typography;
// 使WebSocket hook
const WuyuanbiaobaContent = () => {
const { isConnected, sendMessage } = useWebSocket();
const { isConnected, isReady, sendMessage } = useWebSocket();
//
const realtimeDataSubscription = useWebSocketSubscription("dev", "data");
@ -93,6 +97,12 @@ const WuyuanbiaobaContent = () => {
//
const [selectedTargetId, setSelectedTargetId] = useState(null);
//
const [currentMenu, setCurrentMenu] = useState("monitor");
// Hook
const { isUnlocked, verifyPassword, logout } = useAuth();
//
const processRealtimeData = (data) => {
if (!data || !data.data || !Array.isArray(data.data)) {
@ -483,24 +493,57 @@ const WuyuanbiaobaContent = () => {
backgroundColor: "#f0f2f5",
}}
>
{/* Header 区域 */}
{/* Header 区域 - 固定在顶部 */}
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
height: "60px",
backgroundColor: "white",
color: "#333",
display: "flex",
alignItems: "center",
// justifyContent: "space-between",
padding: "0 24px",
flexShrink: 0,
borderBottom: "1px solid #d9d9d9",
zIndex: 1000,
}}
>
<Title level={3} style={{ color: "#333", margin: 0 }}>
视觉位移计配置工具
</Title>
<Menu
mode="horizontal"
selectedKeys={[currentMenu]}
onClick={({ key }) => setCurrentMenu(key)}
style={{
border: "none",
fontSize: "18px",
justifyContent: "flex-end",
}}
items={[
{
key: "monitor",
icon: <MonitorOutlined />,
label: "实时监控",
},
{
key: "advanced",
icon: <LockOutlined />,
label: "高级配置",
},
]}
/>
</div>
{/* 内容区域 - 添加顶部padding以避免被固定header遮挡 */}
<div style={{ paddingTop: "60px" }}>
{/* 实时监控页面 */}
{currentMenu === "monitor" && (
<>
{/* 中间区域 - 固定视口剩余高度 */}
<div
style={{
@ -515,7 +558,9 @@ const WuyuanbiaobaContent = () => {
onClearSelection={handleClearSelection}
onRectangleClick={handleRectangleClick}
selectedTemplate={
selectedTemplate ? getTemplateByKey(selectedTemplate) : null
selectedTemplate
? getTemplateByKey(selectedTemplate)
: null
}
// Hook
targets={targetListData}
@ -569,6 +614,23 @@ const WuyuanbiaobaContent = () => {
{/* Table 区域 - 使用采样数据显示 */}
<RealtimeDataTable realtimeData={realtimeData} />
</div>
</>
)}
{/* 高级配置页面 */}
{currentMenu === "advanced" && (
<div style={{ margin: "16px" }}>
{isUnlocked ? (
<AdvancedSettings onLogout={logout} />
) : (
<AdvancedConfigAuth
verifyPassword={verifyPassword}
onUnlock={() => {}}
/>
)}
</div>
)}
</div>
{/* 模板编辑模态框 */}
<TemplateModal

475
client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js

@ -0,0 +1,475 @@
/**
* useAdvancedSettings Hook
*
* 高级配置管理 Hook用于管理设备的高级参数配置
*
* 功能包括
* - 设备编码读取与设置 (getId/setId)
* - 数据帧率读取与设置 (getDataFps/setDataFps)
* - 异常超时监控读取与设置 (getAlert/setAlert)
* - 滤波配置读取与设置 (getWin/setWin)
* - MQTT 上报配置读取与设置 (getMqtt/setMqtt)
*
* 使用示例
* ```jsx
* const {
* settings,
* loading,
* fetchAllSettings,
* updateDeviceId,
* updateDataFps,
* updateAlertConfig,
* updateFilterConfig,
* updateMqttConfig,
* } = useAdvancedSettings();
* ```
*/
import { useState, useEffect, useCallback } from 'react';
import { message } from 'antd';
import { useWebSocket, useWebSocketSubscription } from '../actions/websocket.jsx';
/**
* 高级配置默认值
*/
const DEFAULT_SETTINGS = {
// 设备编码
deviceId: '',
// 数据帧率
dataFps: 10,
// 异常监控配置
alertConfig: {
enable: false,
intervalSec: 60
},
// 滤波配置
filterConfig: {
enable: true,
method: 'median',
size: 5,
threshold: -0.1,
imgThreshold: 10.0
},
// MQTT 配置
mqttConfig: {
enable: true,
mqtt: {
broker: '',
port: 1883,
topic: '',
username: '',
password: '',
client_id: ''
}
}
};
/**
* 高级配置管理 Hook
*/
const useAdvancedSettings = () => {
const { isConnected, isReady, sendMessage } = useWebSocket();
// 配置状态
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
const [loading, setLoading] = useState(false);
const [fetchStatus, setFetchStatus] = useState({});
// WebSocket 订阅 - 监听所有来自设备的响应
const { latest: getIdResponse } = useWebSocketSubscription('dev', 'getId');
const { latest: setIdResponse } = useWebSocketSubscription('dev', 'setId');
const { latest: getDataFpsResponse } = useWebSocketSubscription('dev', 'getDataFps');
const { latest: setDataFpsResponse } = useWebSocketSubscription('dev', 'setDataFps');
const { latest: getAlertResponse } = useWebSocketSubscription('dev', 'getAlert');
const { latest: setAlertResponse } = useWebSocketSubscription('dev', 'setAlert');
const { latest: getWinResponse } = useWebSocketSubscription('dev', 'getWin');
const { latest: setWinResponse } = useWebSocketSubscription('dev', 'setWin');
const { latest: getMqttResponse } = useWebSocketSubscription('dev', 'getMqtt');
const { latest: setMqttResponse } = useWebSocketSubscription('dev', 'setMqtt');
/**
* 处理设备编码读取响应
*/
useEffect(() => {
if (getIdResponse?.values?.id !== undefined) {
console.log('📥 收到设备编码:', getIdResponse.values.id);
setSettings(prev => ({
...prev,
deviceId: getIdResponse.values.id
}));
setFetchStatus(prev => ({ ...prev, deviceId: 'success' }));
}
}, [getIdResponse]);
/**
* 处理设备编码设置响应
*/
useEffect(() => {
if (setIdResponse?.values?.operate !== undefined) {
if (setIdResponse.values.operate) {
} else {
message.error('设备编码设置失败');
}
}
}, [setIdResponse]);
/**
* 处理数据帧率读取响应
*/
useEffect(() => {
if (getDataFpsResponse?.values?.dataFps !== undefined) {
console.log('📥 收到数据帧率:', getDataFpsResponse.values.dataFps);
setSettings(prev => ({
...prev,
dataFps: getDataFpsResponse.values.dataFps
}));
setFetchStatus(prev => ({ ...prev, dataFps: 'success' }));
}
}, [getDataFpsResponse]);
/**
* 处理数据帧率设置响应
*/
useEffect(() => {
if (setDataFpsResponse?.values?.operate !== undefined) {
if (setDataFpsResponse.values.operate) {
} else {
message.error('数据帧率设置失败');
}
}
}, [setDataFpsResponse]);
/**
* 处理异常监控配置读取响应
*/
useEffect(() => {
if (getAlertResponse?.values) {
console.log('📥 收到异常监控配置:', getAlertResponse.values);
setSettings(prev => ({
...prev,
alertConfig: getAlertResponse.values
}));
setFetchStatus(prev => ({ ...prev, alert: 'success' }));
}
}, [getAlertResponse]);
/**
* 处理异常监控设置响应
*/
useEffect(() => {
if (setAlertResponse?.values?.operate !== undefined) {
if (setAlertResponse.values.operate) {
} else {
message.error('异常监控配置设置失败');
}
}
}, [setAlertResponse]);
/**
* 处理滤波配置读取响应
*/
useEffect(() => {
if (getWinResponse?.values) {
console.log('📥 收到滤波配置:', getWinResponse.values);
setSettings(prev => ({
...prev,
filterConfig: getWinResponse.values
}));
setFetchStatus(prev => ({ ...prev, filter: 'success' }));
}
}, [getWinResponse]);
/**
* 处理滤波配置设置响应
*/
useEffect(() => {
if (setWinResponse?.values?.operate !== undefined) {
if (setWinResponse.values.operate) {
} else {
message.error('滤波配置设置失败');
}
}
}, [setWinResponse]);
/**
* 处理 MQTT 配置读取响应
*/
useEffect(() => {
if (getMqttResponse?.values) {
console.log('📥 收到 MQTT 配置:', getMqttResponse.values);
setSettings(prev => ({
...prev,
mqttConfig: getMqttResponse.values
}));
setFetchStatus(prev => ({ ...prev, mqtt: 'success' }));
}
}, [getMqttResponse]);
/**
* 处理 MQTT 配置设置响应
*/
useEffect(() => {
if (setMqttResponse?.values?.operate !== undefined) {
if (setMqttResponse.values.operate) {
} else {
message.error('MQTT 配置设置失败');
}
}
}, [setMqttResponse]);
/**
* 发送 WebSocket 命令
*/
const sendCommand = useCallback((cmd, values = {}) => {
if (!isConnected) {
message.warning('WebSocket 未连接,请稍后重试');
return false;
}
const command = {
_from: 'setup',
cmd,
values
};
console.log('📤 发送命令:', command);
return sendMessage(JSON.stringify(command));
}, [isConnected, sendMessage]);
/**
* 获取设备编码
*/
const fetchDeviceId = useCallback(() => {
setFetchStatus(prev => ({ ...prev, deviceId: 'loading' }));
return sendCommand('getId', {});
}, [sendCommand]);
/**
* 更新设备编码
*/
const updateDeviceId = useCallback((id) => {
if (!id || !id.trim()) {
message.warning('请输入设备编码');
return false;
}
return sendCommand('setId', { id });
}, [sendCommand]);
/**
* 获取数据帧率
*/
const fetchDataFps = useCallback(() => {
setFetchStatus(prev => ({ ...prev, dataFps: 'loading' }));
return sendCommand('getDataFps', {});
}, [sendCommand]);
/**
* 更新数据帧率
*/
const updateDataFps = useCallback((dataFps) => {
if (dataFps < 1 || dataFps > 30) {
message.warning('数据帧率范围为 1-30 Hz');
return false;
}
return sendCommand('setDataFps', { dataFps });
}, [sendCommand]);
/**
* 获取异常监控配置
*/
const fetchAlertConfig = useCallback(() => {
setFetchStatus(prev => ({ ...prev, alert: 'loading' }));
return sendCommand('getAlert', {});
}, [sendCommand]);
/**
* 更新异常监控配置
*/
const updateAlertConfig = useCallback((alertConfig) => {
const { enable, intervalSec } = alertConfig;
return sendCommand('setAlert', { enable, intervalSec });
}, [sendCommand]);
/**
* 获取滤波配置
*/
const fetchFilterConfig = useCallback(() => {
setFetchStatus(prev => ({ ...prev, filter: 'loading' }));
return sendCommand('getWin', {});
}, [sendCommand]);
/**
* 更新滤波配置
*/
const updateFilterConfig = useCallback((filterConfig) => {
const { enable, method, size, threshold, imgThreshold } = filterConfig;
return sendCommand('setWin', {
enable,
method,
size,
threshold,
imgThreshold
});
}, [sendCommand]);
/**
* 获取 MQTT 配置
*/
const fetchMqttConfig = useCallback(() => {
setFetchStatus(prev => ({ ...prev, mqtt: 'loading' }));
return sendCommand('getMqtt', {});
}, [sendCommand]);
/**
* 更新 MQTT 配置
*/
const updateMqttConfig = useCallback((mqttConfig) => {
const { enable, mqtt } = mqttConfig;
// 验证必填字段
if (enable) {
if (!mqtt.broker || !mqtt.broker.trim()) {
message.warning('请输入 Broker 地址');
return false;
}
if (!mqtt.port) {
message.warning('请输入端口号');
return false;
}
if (!mqtt.topic || !mqtt.topic.trim()) {
message.warning('请输入 Topic');
return false;
}
if (!mqtt.client_id || !mqtt.client_id.trim()) {
message.warning('请输入 Client ID');
return false;
}
}
return sendCommand('setMqtt', { enable, mqtt });
}, [sendCommand]);
/**
* 获取所有配置
*/
const fetchAllSettings = useCallback(async () => {
if (!isConnected) {
message.warning('WebSocket 未连接,无法获取配置');
return;
}
if (!isReady) {
message.info('连接正在建立中,指令已加入队列...');
}
setLoading(true);
setFetchStatus({});
console.log('🔄 开始获取所有高级配置...');
// 所有指令都用 setTimeout 包裹,首个指令也延迟,避免第一个指令丢包
fetchDeviceId()
setTimeout(() => fetchDeviceId(), 50);
setTimeout(() => fetchDataFps(), 150);
setTimeout(() => fetchAlertConfig(), 250);
setTimeout(() => fetchFilterConfig(), 350);
setTimeout(() => fetchMqttConfig(), 450);
// 等待所有响应
setTimeout(() => {
setLoading(false);
console.log('配置获取完成');
}, 2000);
}, [isConnected, isReady, fetchDeviceId, fetchDataFps, fetchAlertConfig, fetchFilterConfig, fetchMqttConfig]);
/**
* 保存所有配置
*/
const saveAllSettings = useCallback(async () => {
if (!isConnected) {
message.warning('WebSocket 未连接,无法保存配置');
return false;
}
if (!isReady) {
message.info('连接正在建立中,指令已加入队列...');
}
// 依次发送所有设置命令,并收集每项的结果
const results = [];
results.push(await updateDeviceId(settings.deviceId));
results.push(await new Promise(resolve => setTimeout(() => resolve(updateDataFps(settings.dataFps)), 100)));
results.push(await new Promise(resolve => setTimeout(() => resolve(updateAlertConfig(settings.alertConfig)), 200)));
results.push(await new Promise(resolve => setTimeout(() => resolve(updateFilterConfig(settings.filterConfig)), 300)));
results.push(await new Promise(resolve => setTimeout(() => resolve(updateMqttConfig(settings.mqttConfig)), 400)));
// 判断是否全部成功
const allSuccess = results.every(r => r !== false);
if (allSuccess) {
message.success('保存成功');
return true;
} else {
message.error('保存失败');
return false;
}
}, [isConnected, isReady, settings, updateDeviceId, updateDataFps, updateAlertConfig, updateFilterConfig, updateMqttConfig]);
/**
* 重置配置到默认值
*/
const resetSettings = useCallback(() => {
setSettings(DEFAULT_SETTINGS);
message.info('配置已重置为默认值');
}, []);
/**
* 更新本地设置不发送到设备
*/
const updateLocalSettings = useCallback((updates) => {
setSettings(prev => ({
...prev,
...updates
}));
}, []);
// 组件应在需要时自行调用 fetchAllSettings,不再在 hook 内自动调用
return {
// 状态
settings,
loading,
fetchStatus,
isConnected,
isReady,
// 操作方法
fetchAllSettings,
saveAllSettings,
resetSettings,
updateLocalSettings,
// 单项操作
fetchDeviceId,
updateDeviceId,
fetchDataFps,
updateDataFps,
fetchAlertConfig,
updateAlertConfig,
fetchFilterConfig,
updateFilterConfig,
fetchMqttConfig,
updateMqttConfig,
};
};
export default useAdvancedSettings;

1
client/src/sections/wuyuanbiaoba/hooks/useAuth.js

@ -0,0 +1 @@
import { useState, useEffect, useCallback } from "react"; const CORRECT_PASSWORD_HASH = "77796ac7e66ecc44954287ed7de7096c4016dd6ffb2763091c4eb3bc4d28b6dc", AUTH_STATUS_KEY = "advanced_config_unlocked"; async function generatePasswordHash(e) { try { var t = (new TextEncoder).encode(e), c = await crypto.subtle.digest("SHA-256", t); return Array.from(new Uint8Array(c)).map(e => e.toString(16).padStart(2, "0")).join("") } catch (e) { throw console.error("密码哈希生成失败:", e), e } } function checkUnlockStatus() { return "true" === sessionStorage.getItem(AUTH_STATUS_KEY) } function setUnlockStatus(e) { e ? sessionStorage.setItem(AUTH_STATUS_KEY, "true") : sessionStorage.removeItem(AUTH_STATUS_KEY) } function useAuth() { let [e, t] = useState(checkUnlockStatus); return useEffect(() => { t(checkUnlockStatus()) }, []), { isUnlocked: e, verifyPassword: useCallback(async e => { try { return e ? await generatePasswordHash(e) === CORRECT_PASSWORD_HASH && (setUnlockStatus(!0), t(!0), !0) : !1 } catch (e) { return console.error("密码验证失败:", e), !1 } }, []), logout: useCallback(() => { setUnlockStatus(!1), t(!1) }, []) } } export { useAuth, generatePasswordHash, checkUnlockStatus };

246
client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md

@ -0,0 +1,246 @@
# 高级配置 WebSocket 协议对接文档
1. 主机编码读取指令
```json
{
"_from": "setup",
"cmd": "getId",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getId",
"values": {
"id": "uu1234"
}
}
```
2. 主机编码设置指令
```json
{
"_from": "setup",
"cmd": "setId",
"values": { "id": "uu1234" }
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setId",
"values": { "operate": true }
}
```
3. 帧率读取指令
```json
{
"_from": "setup",
"cmd": "getDataFps",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getDataFps",
"values": { "dataFps": 10 }
}
```
4. 帧率设置指令
```json
{
"_from": "setup",
"cmd": "setDataFps",
"values": { "dataFps": 10 }
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setDataFps",
"values": { "operate": true }
}
```
5. 异常超时监控-读取指令
```json
{
"_from": "setup",
"cmd": "getAlert",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getAlert",
"values": {
"enable": false,
"intervalSec": 6
}
}
```
6. 异常超时监控-设置指令
```json
{
"_from": "setup",
"cmd": "setAlert",
"values": {
"enable": false,
"intervalSec": 6
}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setAlert",
"values": {
"operate": true
}
}
```
7. 滤波-读取
```json
{
"_from": "setup",
"cmd":"getWin",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getWin",
"values": {
"enable": true,
"method": "median",
"size": 5,
"threshold": -0.1,
"imgThreshold": 10.0
}
}
```
8. 滤波-设置
```json
{
"_from": "setup",
"cmd":"setWin",
"values": {
"enable": true,
"method": "median",
"size": 5,
"threshold": -0.1,
"imgThreshold": 10.0
}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setWin",
"values": {
"operate": true
}
}
```
9. mqtt 上报读取
```json
{
"_from": "setup",
"cmd":"getMqtt",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getMqtt",
"values": {
"mqtt": {
"broker": "218.3.126.49",
"port": 1883,
"topic": "wybb/zj/mqtt110_debug",
"username": "",
"password": "",
"client_id": "wybb_debug"
},
"enable": true
}
}
```
10. mqtt上报设置
```json
{
"_from": "setup",
"cmd": "setMqtt",
"values": {
"mqtt": {
"broker": "218.3.126.49",
"port": 1883,
"topic": "wybb/zj/mqtt110_debug",
"username": "",
"password": "",
"client_id": "wybb_debug"
},
"enable": true
}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setMqtt",
"values": {
"operate": true
}
}
```
10. 异常超时监控-读取
```json
{
"_from": "setup",
"cmd":"getAlert",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getAlert",
"values": {
"enable": false,
"intervalSec": 6
}
}
```

4
config.cjs

@ -1,8 +1,11 @@
const path = require('path')
const packageJson = require('./package.json')
const flag = process.env.npm_lifecycle_script.includes('--mode localdev') ? 'localdev' : null;
if (flag) {
process.env.FS_FLAG = flag;
}
process.env.FS_VERSION = packageJson.version;
module.exports = {
env: process.env.NODE_ENV || 'development', // 运行环境 development | production
port: process.env.PORT || 5000, // 服务端口
@ -12,6 +15,7 @@ module.exports = {
'/client/assets/script/peace.js'
],
flag: flag, // 自定义环境标识
version: packageJson.version, // 版本号
proxy: [{ // 代理配置
path: '/_api',
target: process.env.API,

2
package.json

@ -1,6 +1,6 @@
{
"name": "wuyuanbiaoba-web",
"version": "1.0.4",
"version": "1.1.0",
"main": "index.html",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",

20
server/tcpProxy/index.js

@ -27,11 +27,26 @@ function setupTcpProxy(conf) {
const tcpClient = new net.Socket();
// TCP数据缓冲区
let tcpDataBuffer = '';
let tcpConnected = false; // 新增:TCP连接状态标志
// TCP连接成功
tcpClient.connect(process.env.TCP_PORT || TCP_PORT, process.env.TCP_HOST || TCP_HOST, () => {
// console.log(process.env);
// console.log(`TCP连接已建立到 ${TCP_HOST}:${TCP_PORT}`);
tcpConnected = true;
// 向WebSocket客户端发送就绪信号
if (ws.readyState === WebSocket.OPEN) {
const readySignal = JSON.stringify({
_from: 'proxy',
cmd: 'ready',
values: {
message: 'TCP connection established',
timestamp: Date.now()
}
});
ws.send(readySignal);
}
});
// TCP接收数据,转发到WebSocket
@ -40,9 +55,8 @@ function setupTcpProxy(conf) {
// 尝试解析为文本
try {
textData = data.toString('utf8');
// console.log('收到TCP数据片段:', textData.length, '字节');
} catch (e) {
// console.log('TCP数据无法解析为文本');
console.log('TCP数据无法解析为文本');
return;
}
@ -94,7 +108,7 @@ function setupTcpProxy(conf) {
// 转发字符串数据到TCP服务器
if (tcpClient.writable) {
console.log('准备发送数据到TCP服务器:', messageStr);
console.log('发送数据到TCP服务器:', messageStr);
// 检查数据大小
const dataSize = Buffer.byteLength(messageStr, 'utf8');

Loading…
Cancel
Save