diff --git a/CHANGELOGS/V1.2.0.md b/CHANGELOGS/V1.2.0.md new file mode 100644 index 0000000..d20f7c9 --- /dev/null +++ b/CHANGELOGS/V1.2.0.md @@ -0,0 +1,68 @@ +# V1.2.0 更新日志 + +- **更新日期**: 2025年12月3日 +- **版本号**: 1.2.0 + +## 新增功能 + +### 1. 中值滤波算法配置 +- **新增中值滤波算法配置界面**:在系统设置中新增"中值滤波算法配置"功能模块 +- **按需加载机制**:优化数据加载逻辑,仅在打开配置弹窗时才加载算法配置数据,提升性能 +- **可视化配置**:支持对每个测点进行独立的中值滤波参数配置 + - X方向窗口大小配置 + - Y方向窗口大小配置 + - 算法启用/禁用开关 +- **实时同步**:配置数据可实时同步到设备端 +- **数据持久化**:配置保存到设备后自动加载,支持配置的读取和设置 + +## Bug 修复 + +### 1. 算法配置加载优化 +- **修复数据加载时机问题**:避免组件加载时不必要的数据请求 +- **优化 Modal 打开逻辑**:确保只在用户主动打开配置界面时才触发数据加载 + +## 🔧 优化改进 + +### 1. 组件架构优化 +- **解耦组件职责**:将算法设置按钮移至父组件,优化组件结构 +- **受控组件模式**:AlgorithmSettings 改为完全受控的 Modal 组件 +- **状态管理优化**:通过 props 传递控制状态,提升代码可维护性 + +### 2. 用户体验提升 +- **设备连接状态检测**:按钮在设备未连接时自动禁用,避免无效操作 +- **加载状态反馈**:添加加载动画,提供更好的用户反馈 +- **错误提示优化**:完善错误提示信息,帮助用户快速定位问题 + +## 技术细节 + +### IPC 通信 +- 新增 IPC 事件: + - `MEDIAN_FILTER_CONFIG_GET (win:get)`:获取中值滤波算法配置 + - `MEDIAN_FILTER_CONFIG_SET (win:set)`:设置中值滤波算法配置 + +### 数据格式 +```json +{ + "type": "median", + "sensors": [ + { + "enable": false, + "pos": "1", + "xLen": 0, + "yLen": 0 + } + ] +} +``` + +## 依赖更新 +- 无依赖包更新 + +## 注意事项 +1. 确保设备固件版本支持中值滤波算法配置功能 +2. 配置参数的有效范围请参考设备手册 +3. 建议在修改配置前备份当前配置 + +--- + +**完整更新内容请查看项目 Git 提交记录** diff --git a/package.json b/package.json index 8b0b9b6..17a3eba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "FlexometerSetup", - "version": "1.1.0", + "version": "1.2.0", "description": "An Electron application with React", "main": "./out/main/index.js", "author": "cles", diff --git a/src/main/ipcRouter.js b/src/main/ipcRouter.js index f2c5aab..2389289 100644 --- a/src/main/ipcRouter.js +++ b/src/main/ipcRouter.js @@ -207,7 +207,94 @@ export function registerIpRouter() { // 配置保存与加载 ipcMain.handle(IPC_EVENT.SAVE_CONFIG, saveConfig) ipcMain.handle(IPC_EVENT.LOAD_CONFIG, loadConfig) + + // 中值滤波算法配置相关 + ipcMain.handle(IPC_EVENT.MEDIAN_FILTER_CONFIG_GET, getMedianFilterConfig) + ipcMain.handle(IPC_EVENT.MEDIAN_FILTER_CONFIG_SET, setMedianFilterConfig) } + +const setMedianFilterConfig = async (event, { ip, values }) => { + return new Promise((resolve, reject) => { + const connectionInfo = tcpClients.get(ip) + if (!connectionInfo) { + resolve({ success: false, error: '设备未连接' }) + return + } + + const requestKey = `${ip}${IPC_EVENT.MEDIAN_FILTER_CONFIG_SET}` + + const timeout = setTimeout(() => { + pendingRequests.delete(requestKey) + resolve({ success: false, error: '请求超时' }) + }, TIMEOUT) + + pendingRequests.set(requestKey, { + resolve: (result) => { + clearTimeout(timeout) + resolve(result) + }, + reject: (error) => { + clearTimeout(timeout) + reject(error) + }, + timestamp: Date.now() + }) + + const command = { command: 'win', type: 'set', values: values ?? '' } + const message = JSON.stringify(command) + END_SEQUENCE + logIPCCommand(ip, command) + + connectionInfo.client.write(message, (err) => { + if (err) { + pendingRequests.delete(requestKey) + clearTimeout(timeout) + resolve({ success: false, error: err.message }) + } + }) + }) +} + +const getMedianFilterConfig = async (event, { ip }) => { + return new Promise((resolve, reject) => { + const connectionInfo = tcpClients.get(ip) + if (!connectionInfo) { + resolve({ success: false, error: '设备未连接' }) + return + } + + const requestKey = `${ip}${IPC_EVENT.MEDIAN_FILTER_CONFIG_GET}` + + const timeout = setTimeout(() => { + pendingRequests.delete(requestKey) + resolve({ success: false, error: '请求超时' }) + }, TIMEOUT) + + pendingRequests.set(requestKey, { + resolve: (result) => { + clearTimeout(timeout) + resolve(result) + }, + reject: (error) => { + clearTimeout(timeout) + reject(error) + }, + timestamp: Date.now() + }) + + const command = { command: 'win', type: 'get'} + const message = JSON.stringify(command) + END_SEQUENCE + logIPCCommand(ip, command) + + connectionInfo.client.write(message, (err) => { + if (err) { + pendingRequests.delete(requestKey) + clearTimeout(timeout) + resolve({ success: false, error: err.message }) + } + }) + }) +} + // 导入配置文件 const loadConfig = async (event) => { try { @@ -695,7 +782,26 @@ const handleTcpResponse = async (ip, msg) => { lastHeartbeatTime.set(ip, Date.now()) // 心跳包不记录到日志,避免日志过多 break - + case IPC_EVENT.MEDIAN_FILTER_CONFIG_GET: + { + const medianRequest = pendingRequests.get(`${ip}${IPC_EVENT.MEDIAN_FILTER_CONFIG_GET}`) + if (medianRequest) { + await delay() + medianRequest.resolve({ success: true, data: msg }) + pendingRequests.delete(`${ip}${IPC_EVENT.MEDIAN_FILTER_CONFIG_GET}`) + } + } + break + case IPC_EVENT.MEDIAN_FILTER_CONFIG_SET: + { + const medianSetRequest = pendingRequests.get(`${ip}${IPC_EVENT.MEDIAN_FILTER_CONFIG_SET}`) + if (medianSetRequest) { + await delay() + medianSetRequest.resolve({ success: true, data: msg }) + pendingRequests.delete(`${ip}${IPC_EVENT.MEDIAN_FILTER_CONFIG_SET}`) + } + } + break default: console.warn('unknown command:', `${msg.command}:${msg.type}`) } diff --git a/src/renderer/index.html b/src/renderer/index.html index 511a3b8..f332fb1 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -2,7 +2,7 @@ - FlexometerSetup is developed by FS + power by free-sun.co state.connectedDevice) + console.log('当前连接的设备:', connectedDevice) + // 点位配置列表数据 + const [pointSettings, setPointSettings] = useState([]) + + // 只在 Modal 打开时获取中值滤波算法配置 + useEffect(() => { + const loadMedianFilterConfig = async () => { + if (!visible) { + // Modal 未打开,不加载数据 + return + } + + if (!connectedDevice) { + console.log('未连接设备,跳过加载中值滤波配置') + return + } + + setLoading(true) + try { + const result = await window.electron.ipcRenderer.invoke(IPC_EVENT.MEDIAN_FILTER_CONFIG_GET, { + ip: connectedDevice + }) + + if (result.success && result.data) { + console.log('中值滤波算法配置加载成功:', result.data) + const data = result.data.values + + // 根据返回的数据更新 pointSettings + if (data && data.sensors && Array.isArray(data.sensors)) { + const updatedSettings = data.sensors.map((sensor, index) => ({ + id: index + 1, + pos: `点位${sensor.pos}`, + xLen: sensor.xLen || null, + yLen: sensor.yLen || null, + enable: sensor.enable || false + })) + setPointSettings(updatedSettings) + message.success('算法配置加载成功') + } else { + message.warning('配置数据格式异常') + } + } else { + console.error('加载中值滤波配置失败:', result.error) + message.error(result.error || '加载配置失败') + } + } catch (error) { + console.error('加载中值滤波配置异常:', error) + message.error('加载配置异常') + } finally { + setLoading(false) + } + } + loadMedianFilterConfig() + }, [visible, connectedDevice]) // 依赖 visible,当 Modal 打开时触发加载 + + // 更新点位配置 + const updatePointSetting = (id, field, value) => { + setPointSettings((prev) => + prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)) + ) + } + + // 保存配置 + const handleSave = async () => { + if (!connectedDevice) { + message.error('设备未连接') + return + } + + try { + // 转换为设备需要的数据格式 + const values = { + type: 'median', + sensors: pointSettings.map((setting) => ({ + enable: setting.enable, + pos: setting.pos.replace('点位', ''), // 从"点位1"中提取"1" + xLen: setting.xLen, + yLen: setting.yLen + })) + } + + console.log('保存配置:', values) + + const result = await window.electron.ipcRenderer.invoke(IPC_EVENT.MEDIAN_FILTER_CONFIG_SET, { + ip: connectedDevice, + values: values + }) + + if (result.success) { + message.success('配置保存成功!') + onClose && onClose() + } else { + message.error(result.error || '配置保存失败') + } + } catch (error) { + console.error('保存配置异常:', error) + message.error('配置保存异常') + } + } + + return ( + + 保存 + , + + ]} + > + ( + +
+
{item.pos}
+ + + X方向: + + updatePointSetting(item.id, 'xLen', Number(e.target.value)) + } + style={{ width: 150 }} + /> + + + Y方向: + + updatePointSetting(item.id, 'yLen', Number(e.target.value)) + } + style={{ width: 150 }} + /> + + + 是否启用中值滤波算法: + updatePointSetting(item.id, 'enable', checked)} + /> + + +
+
+ )} + /> +
+ ) +} + +export default AlgorithmSettings diff --git a/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.module.css b/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.module.css new file mode 100644 index 0000000..d93eb99 --- /dev/null +++ b/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.module.css @@ -0,0 +1,10 @@ +.subSection { + margin-bottom: 6px; + border: 1px solid #eee; + padding: 4px; +} + +.subSectionTitle { + font-weight: bold; + margin-bottom: 6px; +} diff --git a/src/renderer/src/components/SystemSettings/SystemSettings.jsx b/src/renderer/src/components/SystemSettings/SystemSettings.jsx index 48289e2..3f61001 100644 --- a/src/renderer/src/components/SystemSettings/SystemSettings.jsx +++ b/src/renderer/src/components/SystemSettings/SystemSettings.jsx @@ -1,5 +1,5 @@ import styles from './SystemSettings.module.css' -import { Flex, InputNumber, Select, Button, Input, Checkbox, message, Switch } from 'antd' +import { Flex, InputNumber, Select, Button, Input, Checkbox, message } from 'antd' import { SettingFilled, InfoCircleFilled, @@ -11,6 +11,7 @@ import { import { useState, useEffect, useCallback, useRef } from 'react' import { IPC_EVENT } from '../../common/ipcEvents' import useDeviceStore from '../../stores/deviceStore' +import AlgorithmSettings from '../AlgorithmSettings/AlgorithmSettings' function SystemSettings() { // 状态管理 @@ -25,9 +26,9 @@ function SystemSettings() { // 实时数据记录状态 const [realtimeDataEnabled, setRealtimeDataEnabled] = useState(false) const [alarmDataEnabled, setAlarmDataEnabled] = useState(false) - // 算法设置状态 - const [medianFilterEnabled, setMedianFilterEnabled] = useState(false) // 默认关闭 - const [medianFilterParam, setMedianFilterParam] = useState('') // 默认空字符串 + + // 算法设置 Modal 状态 + const [algorithmModalVisible, setAlgorithmModalVisible] = useState(false) // 获取设备连接状态和重连配置 const connectedDevice = useDeviceStore((state) => state.connectedDevice) @@ -869,28 +870,26 @@ function SystemSettings() { 报警数据 + {/* 算法设置 */}
算法设置
-
-
是否启用中值滤波算法
- setMedianFilterEnabled(checked)} - /> -
- - setMedianFilterParam(e.target.value)} - placeholder="请输入算法参数值" - style={{ width: '100%' }} - /> - +
+ + {/* 算法设置 Modal */} + setAlgorithmModalVisible(false)} + /> ) }