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"
+ }
+}
+```