diff --git a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx index e3d0654..2de8956 100644 --- a/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx +++ b/client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx @@ -15,7 +15,8 @@ import { Spin, Alert, Tooltip, - Modal + Modal, + Tabs } from "antd"; import { SettingOutlined, @@ -87,15 +88,20 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { 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 enableBatch = settings.mqttConfig?.batch ?? false; const batchInterpolation = settings.mqttConfig?.interpolation ?? 'copy'; + const MQTT_TEMPLATE = { + enable: false, + broker: '', + port: 1883, + topic: '', + username: '', + password: '', + client_id: '' + }; + const mqtts = settings.mqttConfig?.mqtts ?? [MQTT_TEMPLATE, MQTT_TEMPLATE]; + const brokers = [0, 1].map((i) => ({ ...MQTT_TEMPLATE, ...(mqtts[i] ?? {}) })); + const anyMqttEnabled = brokers.some((b) => b.enable); // 更新本地状态的辅助函数 const setDeviceId = (value) => updateLocalSettings({ deviceId: value }); @@ -135,78 +141,22 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { 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) => + const setMqttEnable = (index, value) => updateLocalSettings({ mqttConfig: { ...settings.mqttConfig, - mqtt: { - ...(settings.mqttConfig?.mqtt ?? {}), - broker: value - }, + mqtts: brokers.map((b, i) => + i === index ? { ...b, enable: value } : b + ), }, }); - const setMqttPort = (value) => + const setMqttField = (index, field, 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 - }, + mqtts: brokers.map((b, i) => + i === index ? { ...b, [field]: value } : b + ), }, }); const setEnableBatch = (value) => @@ -303,21 +253,36 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { }; const handleSave = async () => { - if (enableMqtt) { - if (brokerAddress && !validateIP(brokerAddress)) { - message.error('Broker 地址格式不正确,请检查后重试'); + for (let index = 0; index < brokers.length; index++) { + const broker = brokers[index]; + if (!broker.enable) { + continue; + } + const brokerNo = index + 1; + if (broker.broker && !validateIP(broker.broker)) { + message.error(`Broker ${brokerNo} 地址格式不正确,请检查后重试`); return; } - if (mqttPort && !validatePort(mqttPort)) { - message.error('端口号格式不正确,请检查后重试'); + if (broker.port && !validatePort(broker.port)) { + message.error(`Broker ${brokerNo} 端口号格式不正确,请检查后重试`); return; } - if (!brokerAddress) { - message.warning('启用 MQTT 时请填写 Broker 地址'); + if (!broker.broker) { + message.warning(`启用了 Broker ${brokerNo},请填写 Broker 地址`); return; } } + if ( + brokers[0].enable && + brokers[1].enable && + brokers[0].broker.trim().toLowerCase() === + brokers[1].broker.trim().toLowerCase() + ) { + message.warning("Broker 1 与 Broker 2 的地址不能相同"); + return; + } + try { setSaving(true); const success = await saveAllSettings(); @@ -333,6 +298,145 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { } }; + // 渲染单个 Broker 配置面板(Tabs 内容) + const renderBrokerPane = (index) => { + const broker = brokers[index]; + const brokerNo = index + 1; + return ( +
+ {/* 启用开关 */} +
+ + + 启用 Broker {brokerNo} 上报 +
+ + 开启后,设备将按下方配置向该 Broker 上报数据。 + + + + setMqttEnable(index, value)} + checkedChildren="开启" + unCheckedChildren="关闭" + /> + +
+
+ + {/* Broker 配置字段 */} + + +
+ Broker Address + * +
+ setMqttField(index, 'broker', e.target.value)} + placeholder="例如: 218.3.126.49 或 mqtt.example.com" + size="large" + disabled={!broker.enable} + style={{ + borderRadius: 8, + borderColor: broker.broker && !validateIP(broker.broker) ? '#ff4d4f' : undefined + }} + status={broker.broker && !validateIP(broker.broker) ? 'error' : undefined} + /> + {broker.broker && !validateIP(broker.broker) && ( + + 请输入有效的 IPv4 地址 + + )} + + +
+ Port + * +
+ setMqttField(index, 'port', e.target.value)} + placeholder="1883" + size="large" + disabled={!broker.enable} + style={{ + borderRadius: 8, + borderColor: broker.port && !validatePort(broker.port) ? '#ff4d4f' : undefined + }} + status={broker.port && !validatePort(broker.port) ? 'error' : undefined} + /> + {broker.port && !validatePort(broker.port) && ( + + 端口范围: 1-65535 + + )} + + +
+ Topic +
+ setMqttField(index, 'topic', e.target.value)} + placeholder="例如: wybb/z/mqtt179" + size="large" + disabled={!broker.enable} + style={{ borderRadius: 8 }} + /> + + +
+ Client ID +
+ setMqttField(index, 'client_id', e.target.value)} + placeholder="wybb_z1_123" + size="large" + disabled={!broker.enable} + style={{ borderRadius: 8 }} + /> + + +
+ Username +
+ setMqttField(index, 'username', e.target.value)} + placeholder="用户名" + size="large" + disabled={!broker.enable} + style={{ borderRadius: 8 }} + /> + + +
+ Password +
+ setMqttField(index, 'password', e.target.value)} + placeholder="密码" + size="large" + disabled={!broker.enable} + style={{ borderRadius: 8 }} + /> + +
+
+ ); + }; + const handleReset = () => { fetchAllSettings(); }; @@ -654,7 +758,7 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { { - - - 数据上报 (MQTT) - - - + + + 数据上报 (MQTT) + } style={{ @@ -723,106 +813,14 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { paddingRight: 24, }} > - - -
- Broker Address - * -
- setBrokerAddress(e.target.value)} - placeholder="例如: 218.3.126.49 或 mqtt.example.com" - size="large" - disabled={!enableMqtt} - style={{ - borderRadius: 8, - borderColor: brokerAddress && !validateIP(brokerAddress) ? '#ff4d4f' : undefined - }} - status={brokerAddress && !validateIP(brokerAddress) ? 'error' : undefined} - /> - {brokerAddress && !validateIP(brokerAddress) && ( - - 请输入有效的 IPv4 地址 - - )} - - -
- Port - * -
- setMqttPort(e.target.value)} - placeholder="1883" - size="large" - disabled={!enableMqtt} - style={{ - borderRadius: 8, - borderColor: mqttPort && !validatePort(mqttPort) ? '#ff4d4f' : undefined - }} - status={mqttPort && !validatePort(mqttPort) ? 'error' : undefined} - /> - {mqttPort && !validatePort(mqttPort) && ( - - 端口范围: 1-65535 - - )} - - -
- Topic -
- setMqttTopic(e.target.value)} - placeholder="例如: wybb/z/mqtt179" - size="large" - disabled={!enableMqtt} - style={{ borderRadius: 8 }} - /> - - -
- Client ID -
- setMqttClientId(e.target.value)} - placeholder="wybb_z1_123" - size="large" - disabled={!enableMqtt} - style={{ borderRadius: 8 }} - /> - - -
- Username -
- setMqttUsername(e.target.value)} - placeholder="用户名" - size="large" - disabled={!enableMqtt} - style={{ borderRadius: 8 }} - /> - - -
- Password -
- setMqttPassword(e.target.value)} - placeholder="密码" - size="large" - disabled={!enableMqtt} - style={{ borderRadius: 8 }} - /> - -
+ ({ + key: String(index), + label: `Broker ${index + 1}`, + children: renderBrokerPane(index), + }))} + /> {/* 每秒批量上报 */}
{ onChange={setEnableBatch} checkedChildren="开启" unCheckedChildren="关闭" - disabled={!enableMqtt} + disabled={!anyMqttEnabled} /> @@ -863,7 +861,7 @@ const AdvancedSettings = ({ onLogout, locationConfig }) => { value={batchInterpolation} onChange={setBatchInterpolation} size="large" - disabled={!enableMqtt || !enableBatch} + disabled={!anyMqttEnabled || !enableBatch} style={{ width: "50%", borderRadius: 8 }} > diff --git a/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js b/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js index c542731..c5af305 100644 --- a/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js +++ b/client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js @@ -9,7 +9,7 @@ * - 数据帧率读取与设置 (getDataFps/setDataFps) * - 异常超时监控读取与设置 (getAlert/setAlert) * - 滤波配置读取与设置 (getWin/setWin) - * - MQTT 上报配置读取与设置 (getMqtt/setMqtt) + * - MQTT 上报配置读取与设置 (getMqtt2/setMqtt2,双 Broker) * * 使用示例: * ```jsx @@ -58,19 +58,30 @@ const DEFAULT_SETTINGS = { imgThreshold: 10.0 }, - // MQTT 配置 + // MQTT 配置(双 Broker) mqttConfig: { - enable: true, batch: false, interpolation: 'copy', - mqtt: { - broker: '', - port: 1883, - topic: '', - username: '', - password: '', - client_id: '' - } + mqtts: [ + { + enable: true, + broker: '', + port: 1883, + topic: '', + username: '', + password: '', + client_id: '' + }, + { + enable: false, + broker: '', + port: 1883, + topic: '', + username: '', + password: '', + client_id: '' + } + ] } }; @@ -82,7 +93,7 @@ const SAVE_ITEMS = { setDataFps: '数据帧率', setAlert: '异常监控配置', setWin: '滤波配置', - setMqtt: 'MQTT 配置' + setMqtt2: 'MQTT 配置' }; const MQTT_CONNECT_WARNING = '配置已保存,但 MQTT Broker 连接失败,请检查网络、Broker 地址和端口'; @@ -109,8 +120,8 @@ const useAdvancedSettings = () => { 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'); + const { latest: getMqtt2Response } = useWebSocketSubscription('dev', 'getMqtt2'); + const { latest: setMqtt2Response } = useWebSocketSubscription('dev', 'setMqtt2'); /** * 处理设备编码读取响应 @@ -248,31 +259,31 @@ const useAdvancedSettings = () => { }, [setWinResponse]); /** - * 处理 MQTT 配置读取响应 + * 处理 MQTT 配置读取响应(getMqtt2) */ useEffect(() => { - if (getMqttResponse?.values) { - console.log('📥 收到 MQTT 配置:', getMqttResponse.values); + if (getMqtt2Response?.values?.mqtts) { + console.log('📥 收到 MQTT 配置:', getMqtt2Response.values); setSettings(prev => ({ ...prev, - mqttConfig: getMqttResponse.values + mqttConfig: getMqtt2Response.values })); setFetchStatus(prev => ({ ...prev, mqtt: 'success' })); } - }, [getMqttResponse]); + }, [getMqtt2Response]); /** - * 处理 MQTT 配置设置响应 + * 处理 MQTT 配置设置响应(setMqtt2) */ useEffect(() => { - if (setMqttResponse?.values?.operate !== undefined) { - if (setMqttResponse.values.operate) { + if (setMqtt2Response?.values?.operate !== undefined) { + if (setMqtt2Response.values.operate) { } else { - console.warn('MQTT 配置已保存,但 Broker 连接失败:', setMqttResponse.values); + console.warn('MQTT 配置已保存,但 Broker 连接失败:', setMqtt2Response.values); } } - }, [setMqttResponse]); + }, [setMqtt2Response]); /** * 发送 WebSocket 命令 @@ -395,38 +406,52 @@ const useAdvancedSettings = () => { */ const fetchMqttConfig = useCallback(() => { setFetchStatus(prev => ({ ...prev, mqtt: 'loading' })); - return sendCommand('getMqtt', {}); + return sendCommand('getMqtt2', {}); }, [sendCommand]); /** - * 更新 MQTT 配置 + * 更新 MQTT 配置(双 Broker) */ const updateMqttConfig = useCallback((mqttConfig) => { - const { enable, mqtt, batch, interpolation } = mqttConfig; + const { mqtts, batch, interpolation } = mqttConfig; - // 验证必填字段 - if (enable) { - if (!mqtt.broker || !mqtt.broker.trim()) { - message.warning('请输入 Broker 地址'); + // 验证每个启用 Broker 的必填字段 + for (let index = 0; index < (mqtts || []).length; index++) { + const item = mqtts[index]; + if (!item?.enable) { + continue; + } + const brokerNo = index + 1; + if (!item.broker || !item.broker.trim()) { + message.warning(`请输入 Broker ${brokerNo} 地址`); return false; } - if (!mqtt.port) { - message.warning('请输入端口号'); + if (!item.port) { + message.warning(`请输入 Broker ${brokerNo} 端口号`); return false; } - if (!mqtt.topic || !mqtt.topic.trim()) { - message.warning('请输入 Topic'); + if (!item.topic || !item.topic.trim()) { + message.warning(`请输入 Broker ${brokerNo} Topic`); return false; } - if (!mqtt.client_id || !mqtt.client_id.trim()) { - message.warning('请输入 Client ID'); + if (!item.client_id || !item.client_id.trim()) { + message.warning(`请输入 Broker ${brokerNo} Client ID`); return false; } } - return sendCommand('setMqtt', { - enable, - mqtt, + if ( + mqtts?.[0]?.enable && + mqtts?.[1]?.enable && + (mqtts[0].broker || '').trim().toLowerCase() === + (mqtts[1].broker || '').trim().toLowerCase() + ) { + message.warning('Broker 1 与 Broker 2 的地址不能相同'); + return false; + } + + return sendCommand('setMqtt2', { + mqtts, batch: batch ?? false, interpolation: interpolation ?? 'copy' }); @@ -496,26 +521,41 @@ const useAdvancedSettings = () => { return false; } - const { enable, mqtt } = settings.mqttConfig || {}; - if (enable) { - if (!mqtt?.broker || !mqtt.broker.trim()) { - message.warning('请输入 Broker 地址'); + const mqtts = settings.mqttConfig?.mqtts || []; + for (let index = 0; index < mqtts.length; index++) { + const item = mqtts[index]; + if (!item?.enable) { + continue; + } + const brokerNo = index + 1; + if (!item.broker || !item.broker.trim()) { + message.warning(`请输入 Broker ${brokerNo} 地址`); return false; } - if (!mqtt.port) { - message.warning('请输入端口号'); + if (!item.port) { + message.warning(`请输入 Broker ${brokerNo} 端口号`); return false; } - if (!mqtt.topic || !mqtt.topic.trim()) { - message.warning('请输入 Topic'); + if (!item.topic || !item.topic.trim()) { + message.warning(`请输入 Broker ${brokerNo} Topic`); return false; } - if (!mqtt.client_id || !mqtt.client_id.trim()) { - message.warning('请输入 Client ID'); + if (!item.client_id || !item.client_id.trim()) { + message.warning(`请输入 Broker ${brokerNo} Client ID`); return false; } } + if ( + mqtts[0]?.enable && + mqtts[1]?.enable && + (mqtts[0].broker || '').trim().toLowerCase() === + (mqtts[1].broker || '').trim().toLowerCase() + ) { + message.warning('Broker 1 与 Broker 2 的地址不能相同'); + return false; + } + return true; }, [settings]); @@ -565,10 +605,9 @@ const useAdvancedSettings = () => { } }, { - cmd: 'setMqtt', + cmd: 'setMqtt2', values: { - enable: settings.mqttConfig.enable, - mqtt: settings.mqttConfig.mqtt, + mqtts: settings.mqttConfig.mqtts, batch: settings.mqttConfig.batch ?? false, interpolation: settings.mqttConfig.interpolation ?? 'copy' } @@ -633,7 +672,7 @@ const useAdvancedSettings = () => { for (const command of commands) { const result = await sendAndWaitOperate(command.cmd, command.values); if (!result.success) { - if (result.cmd === 'setMqtt' && !result.timeout) { + if (result.cmd === 'setMqtt2' && !result.timeout) { mqttConnectWarning = result; continue; } diff --git a/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md b/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md index 3a75ce4..350899f 100644 --- a/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md +++ b/client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md @@ -212,7 +212,7 @@ ```json { "_from": "setup", - "cmd":"getMqtt", + "cmd":"getMqtt2", "values": {} } ``` @@ -220,45 +220,67 @@ ```json { "_from": "dev", - "cmd": "getMqtt", + "cmd": "getMqtt2", "values": { - "mqtt": { - "broker": "218.3.126.49", - "port": 1883, - "topic": "wybb/zj/mqtt110_debug", - "username": "", - "password": "", - "client_id": "wybb_debug" - }, - "enable": true, + "mqtts": [ + { + "enable": true, + "broker": "218.3.126.49", + "port": 1883, + "topic": "wybb/hg/mqtt179_debug1", + "username": "", + "password": "", + "client_id": "wybb_hg_1" + }, + { + "enable": true, + "broker": "218.3.126.49", + "port": 1883, + "topic": "wybb/hg/mqtt179_debug2", + "username": "", + "password": "", + "client_id": "wybb_hg_2" + } + ], "batch": true, - "interpolation": "lowpass" + "interpolation": "copy" } } ``` 字段说明: -- `enable`:MQTT 上报总开关 +- `mqtts`:Broker 配置数组,支持 2 个 Broker,每项含 `enable`(该 Broker 上报开关)及 `broker`/`port`/`topic`/`username`/`password`/`client_id` - `batch`:每秒批量上报开关。开启后,每秒将多条数据合并为一个数组发送;关闭时数据格式不变(一秒内上报多次) -- `interpolation`:批量补全算法,`copy`=分段复制(现有算法,数据缺失时仍复制该条),`lowpass`=低通滤波插值(3Hz FIR 滤波器插值补全缺失数据) +- `interpolation`:批量补全算法,`copy`=分段复制(数据缺失时仍复制该条),`lowpass`=低通滤波插值(3Hz FIR 滤波器插值补全缺失数据) 12. mqtt上报设置 ```json { "_from": "setup", - "cmd": "setMqtt", + "cmd": "setMqtt2", "values": { - "mqtt": { - "broker": "218.3.126.49", - "port": 1883, - "topic": "wybb/zj/mqtt110_debug", - "username": "", - "password": "", - "client_id": "wybb_debug" - }, - "enable": true, + "mqtts": [ + { + "enable": true, + "broker": "218.3.126.49", + "port": 1883, + "topic": "wybb/hg/mqtt179_debug1", + "username": "", + "password": "", + "client_id": "wybb_hg_1" + }, + { + "enable": true, + "broker": "218.3.126.49", + "port": 1883, + "topic": "wybb/hg/mqtt179_debug2", + "username": "", + "password": "", + "client_id": "wybb_hg_2" + } + ], "batch": true, - "interpolation": "lowpass" + "interpolation": "copy" } } ``` @@ -266,7 +288,7 @@ ```json { "_from": "dev", - "cmd": "setMqtt", + "cmd": "setMqtt2", "values": { "operate": true }