From a6c1bc983449e2d96235aba129bcf176ea3b1cf6 Mon Sep 17 00:00:00 2001 From: liujiangyong Date: Fri, 30 Oct 2026 14:11:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E4=B8=8A=E6=8A=A5=E5=92=8C=E8=A1=A5=E5=85=A8=E7=AE=97=E6=B3=95?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=EF=BC=9B=E9=80=82=E9=85=8D=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E4=B8=8A=E6=8A=A5=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdvancedSettings.jsx | 71 +++++++++++++++- .../sections/wuyuanbiaoba/container/index.jsx | 85 ++++++++++++------- .../wuyuanbiaoba/hooks/useAdvancedSettings.js | 23 +++-- .../wuyuanbiaoba/hooks/高级配置说明.md | 43 +++++++++- 4 files changed, 176 insertions(+), 46 deletions(-) diff --git a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx index 5c9534a..e3d0654 100644 --- a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx +++ b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx @@ -94,6 +94,8 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { const mqttClientId = settings.mqttConfig?.mqtt?.client_id ?? ''; const mqttUsername = settings.mqttConfig?.mqtt?.username ?? ''; const mqttPassword = settings.mqttConfig?.mqtt?.password ?? ''; + const enableBatch = settings.mqttConfig?.batch ?? false; + const batchInterpolation = settings.mqttConfig?.interpolation ?? 'copy'; // 更新本地状态的辅助函数 const setDeviceId = (value) => updateLocalSettings({ deviceId: value }); @@ -207,6 +209,20 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { }, }, }); + const setEnableBatch = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + batch: value + }, + }); + const setBatchInterpolation = (value) => + updateLocalSettings({ + mqttConfig: { + ...settings.mqttConfig, + interpolation: value + }, + }); const [saving, setSaving] = React.useState(false); const [captureEditable, setCaptureEditable] = React.useState(false); @@ -497,16 +513,16 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { { - 有效范围 1~30 Hz + 有效范围 1~50 Hz @@ -807,6 +823,53 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { /> + + {/* 每秒批量上报 */} +
+ + + 每秒批量上报 +
+ + 开启后,每秒将多条数据合并为一个数组发送。 + + + + + +
+
+ + {/* 批量补全算法 */} +
+
+ 批量补全算法 +
+ +
diff --git a/client/src/sections/wuyuanbiaoba/container/index.jsx b/client/src/sections/wuyuanbiaoba/container/index.jsx index 13bff85..375a9d0 100644 --- a/client/src/sections/wuyuanbiaoba/container/index.jsx +++ b/client/src/sections/wuyuanbiaoba/container/index.jsx @@ -122,6 +122,17 @@ const WuyuanbiaobaContent = () => { })); }; + // 归一化设备上报数据:兼容批量格式(values 为数组)与单条格式(values 为对象) + const normalizeDataGroups = (values) => { + if (Array.isArray(values)) { + return values.filter((g) => g && Array.isArray(g.data)); + } + if (values && Array.isArray(values.data)) { + return [values]; + } + return []; + }; + // 初始化数据 useEffect(() => { // 初始化空的实时数据表格 @@ -172,46 +183,54 @@ const WuyuanbiaobaContent = () => { realtimeDataSubscription.latest && realtimeDataSubscription.latest.values ) { - const newRealtimeData = processRealtimeData( + // 兼容批量上报(values 为数组)与单条上报(values 为对象) + const dataGroups = normalizeDataGroups( realtimeDataSubscription.latest.values ); - if (newRealtimeData.length > 0) { - const currentTime = Date.now(); - const currentSecond = Math.floor(currentTime / 1000); - - // 每秒采样一次数据用于图表和表格显示 - if (currentSecond > lastSampleTime) { - setLastSampleTime(currentSecond); + if (dataGroups.length > 0) { + // 维护两分钟的缓冲区(批量模式下保留批量内全部数据点,保证导出全分辨率) + dataGroups.forEach((group) => pushToRealtimeBuffer(group)); + setLastUpdateTime(new Date()); - // 更新采样后的历史数据(用于图表显示) - setTableData((prevData) => { - const updatedData = [ - ...prevData, - ...newRealtimeData.map((point) => ({ + // 展示采样:取最新一条数据(批量模式下取批量内最后一条) + const latestGroup = dataGroups[dataGroups.length - 1]; + const newRealtimeData = processRealtimeData(latestGroup); + + if (newRealtimeData.length > 0) { + const currentTime = Date.now(); + const currentSecond = Math.floor(currentTime / 1000); + + // 每秒采样一次数据用于图表和表格显示 + if (currentSecond > lastSampleTime) { + setLastSampleTime(currentSecond); + + // 更新采样后的历史数据(用于图表显示) + setTableData((prevData) => { + const updatedData = [ + ...prevData, + ...newRealtimeData.map((point) => ({ + ...point, + key: `${currentTime}_${point.key}`, + timestamp: currentTime, + updateTime: new Date(currentTime).toLocaleString(), + })), + ]; + + // 只保留最近25个数据点 + return updatedData.slice(-75); // 3个设备 * 25个时间点 + }); + + // 更新实时数据表格(使用最新的采样数据) + setRealtimeData( + newRealtimeData.map((point) => ({ ...point, - key: `${currentTime}_${point.key}`, - timestamp: currentTime, + key: `realtime_${point.key}`, updateTime: new Date(currentTime).toLocaleString(), - })), - ]; - - // 只保留最近25个数据点 - return updatedData.slice(-75); // 3个设备 * 25个时间点 - }); - - // 更新实时数据表格(使用最新的采样数据) - setRealtimeData( - newRealtimeData.map((point) => ({ - ...point, - key: `realtime_${point.key}`, - updateTime: new Date(currentTime).toLocaleString(), - })) - ); + })) + ); + } } - //维护两分钟的缓冲区 - pushToRealtimeBuffer(realtimeDataSubscription.latest.values); - setLastUpdateTime(new Date()); } } }, [realtimeDataSubscription.latest]); diff --git a/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js b/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js index 4a20a65..c542731 100644 --- a/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js +++ b/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js @@ -61,6 +61,8 @@ const DEFAULT_SETTINGS = { // MQTT 配置 mqttConfig: { enable: true, + batch: false, + interpolation: 'copy', mqtt: { broker: '', port: 1883, @@ -343,8 +345,8 @@ const useAdvancedSettings = () => { * 更新数据帧率 */ const updateDataFps = useCallback((dataFps) => { - if (dataFps < 1 || dataFps > 30) { - message.warning('数据帧率范围为 1-30 Hz'); + if (dataFps < 1 || dataFps > 50) { + message.warning('数据帧率范围为 1-50 Hz'); return false; } return sendCommand('setDataFps', { dataFps }); @@ -400,7 +402,7 @@ const useAdvancedSettings = () => { * 更新 MQTT 配置 */ const updateMqttConfig = useCallback((mqttConfig) => { - const { enable, mqtt } = mqttConfig; + const { enable, mqtt, batch, interpolation } = mqttConfig; // 验证必填字段 if (enable) { @@ -422,7 +424,12 @@ const useAdvancedSettings = () => { } } - return sendCommand('setMqtt', { enable, mqtt }); + return sendCommand('setMqtt', { + enable, + mqtt, + batch: batch ?? false, + interpolation: interpolation ?? 'copy' + }); }, [sendCommand]); const sendAndWaitOperate = useCallback((cmd, values = {}, timeout = SAVE_RESPONSE_TIMEOUT) => { @@ -484,8 +491,8 @@ const useAdvancedSettings = () => { return false; } - if (settings.dataFps < 1 || settings.dataFps > 30) { - message.warning('数据帧率范围为 1-30 Hz'); + if (settings.dataFps < 1 || settings.dataFps > 50) { + message.warning('数据帧率范围为 1-50 Hz'); return false; } @@ -561,7 +568,9 @@ const useAdvancedSettings = () => { cmd: 'setMqtt', values: { enable: settings.mqttConfig.enable, - mqtt: settings.mqttConfig.mqtt + mqtt: settings.mqttConfig.mqtt, + batch: settings.mqttConfig.batch ?? false, + interpolation: settings.mqttConfig.interpolation ?? 'copy' } } ]; diff --git a/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md b/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md index e83a75f..3a75ce4 100644 --- a/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md +++ b/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md @@ -230,11 +230,18 @@ "password": "", "client_id": "wybb_debug" }, - "enable": true + "enable": true, + "batch": true, + "interpolation": "lowpass" } } ``` +字段说明: +- `enable`:MQTT 上报总开关 +- `batch`:每秒批量上报开关。开启后,每秒将多条数据合并为一个数组发送;关闭时数据格式不变(一秒内上报多次) +- `interpolation`:批量补全算法,`copy`=分段复制(现有算法,数据缺失时仍复制该条),`lowpass`=低通滤波插值(3Hz FIR 滤波器插值补全缺失数据) + 12. mqtt上报设置 ```json { @@ -249,7 +256,9 @@ "password": "", "client_id": "wybb_debug" }, - "enable": true + "enable": true, + "batch": true, + "interpolation": "lowpass" } } ``` @@ -263,3 +272,33 @@ } } ``` + +13. 批量上报数据格式(batch 开启时) +`batch` 开启后,设备每秒上报一次,`values` 为数组,每项为该秒内的一条数据(含时间戳): +```json +{ + "_from": "dev", + "cmd": "data", + "values": [ + { + "data": [{"pos": "0", "desc": "bb_0", "x": 0.12, "y": 0.34}], + "time": "2026-10-27 10:00:00.000" + }, + { + "data": [{"pos": "0", "desc": "bb_0", "x": 0.13, "y": 0.35}], + "time": "2026-10-27 10:00:00.020" + } + ] +} +``` +`batch` 关闭时,`values` 仍为单条对象格式: +```json +{ + "_from": "dev", + "cmd": "data", + "values": { + "data": [{"pos": "0", "desc": "bb_0", "x": 0.12, "y": 0.34}], + "time": "2026-10-27 10:00:00.000" + } +} +```