Browse Source

feat: 更新版本至 1.2.0,新增中值滤波算法配置功能及相关界面

master
cles 1 month ago
parent
commit
c66fac9af2
  1. 68
      CHANGELOGS/V1.2.0.md
  2. 2
      package.json
  3. 108
      src/main/ipcRouter.js
  4. 2
      src/renderer/index.html
  5. 6
      src/renderer/src/common/ipcEvents.js
  6. 173
      src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.jsx
  7. 10
      src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.module.css
  8. 39
      src/renderer/src/components/SystemSettings/SystemSettings.jsx

68
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 提交记录**

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "FlexometerSetup", "name": "FlexometerSetup",
"version": "1.1.0", "version": "1.2.0",
"description": "An Electron application with React", "description": "An Electron application with React",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "cles", "author": "cles",

108
src/main/ipcRouter.js

@ -207,7 +207,94 @@ export function registerIpRouter() {
// 配置保存与加载 // 配置保存与加载
ipcMain.handle(IPC_EVENT.SAVE_CONFIG, saveConfig) ipcMain.handle(IPC_EVENT.SAVE_CONFIG, saveConfig)
ipcMain.handle(IPC_EVENT.LOAD_CONFIG, loadConfig) 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) => { const loadConfig = async (event) => {
try { try {
@ -695,7 +782,26 @@ const handleTcpResponse = async (ip, msg) => {
lastHeartbeatTime.set(ip, Date.now()) lastHeartbeatTime.set(ip, Date.now())
// 心跳包不记录到日志,避免日志过多 // 心跳包不记录到日志,避免日志过多
break 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: default:
console.warn('unknown command:', `${msg.command}:${msg.type}`) console.warn('unknown command:', `${msg.command}:${msg.type}`)
} }

2
src/renderer/index.html

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>FlexometerSetup is developed by FS</title> <title>power by free-sun.co</title>
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"

6
src/renderer/src/common/ipcEvents.js

@ -60,5 +60,9 @@ export const IPC_EVENT = {
// 配置保存与加载 // 配置保存与加载
SAVE_CONFIG: 'save-config', SAVE_CONFIG: 'save-config',
LOAD_CONFIG: 'load-config' LOAD_CONFIG: 'load-config',
// 中值滤波算法配置相关
MEDIAN_FILTER_CONFIG_GET: 'win:get',
MEDIAN_FILTER_CONFIG_SET: 'win:set'
} }

173
src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.jsx

@ -0,0 +1,173 @@
import styles from './AlgorithmSettings.module.css'
import { Button, Flex, Input, Switch, Modal, List, message } from 'antd'
import { useState, useEffect } from 'react'
import useDeviceStore from '../../stores/deviceStore'
import { IPC_EVENT } from '../../common/ipcEvents'
function AlgorithmSettings({ visible, onClose }) {
const [loading, setLoading] = useState(false)
const connectedDevice = useDeviceStore((state) => 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 (
<Modal
title="算法设置详情"
open={visible}
onCancel={onClose}
onOk={onClose}
width={700}
footer={[
<Button key="save" type="primary" onClick={handleSave}>
保存
</Button>,
<Button key="close" onClick={onClose}>
关闭
</Button>
]}
>
<List
itemLayout="vertical"
dataSource={pointSettings}
renderItem={(item) => (
<List.Item>
<div style={{ width: '100%' }}>
<div style={{ fontWeight: 'bold', marginBottom: 12 }}>{item.pos}</div>
<Flex vertical gap={12}>
<Flex align="center" gap={8}>
<span style={{ minWidth: 100 }}>X方向:</span>
<Input
type="number"
value={item.xLen}
onChange={(e) =>
updatePointSetting(item.id, 'xLen', Number(e.target.value))
}
style={{ width: 150 }}
/>
</Flex>
<Flex align="center" gap={8}>
<span style={{ minWidth: 100 }}>Y方向:</span>
<Input
type="number"
value={item.yLen}
onChange={(e) =>
updatePointSetting(item.id, 'yLen', Number(e.target.value))
}
style={{ width: 150 }}
/>
</Flex>
<Flex align="center" gap={8} justify="space-between">
<span>是否启用中值滤波算法:</span>
<Switch
checked={item.enable}
onChange={(checked) => updatePointSetting(item.id, 'enable', checked)}
/>
</Flex>
</Flex>
</div>
</List.Item>
)}
/>
</Modal>
)
}
export default AlgorithmSettings

10
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;
}

39
src/renderer/src/components/SystemSettings/SystemSettings.jsx

@ -1,5 +1,5 @@
import styles from './SystemSettings.module.css' 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 { import {
SettingFilled, SettingFilled,
InfoCircleFilled, InfoCircleFilled,
@ -11,6 +11,7 @@ import {
import { useState, useEffect, useCallback, useRef } from 'react' import { useState, useEffect, useCallback, useRef } from 'react'
import { IPC_EVENT } from '../../common/ipcEvents' import { IPC_EVENT } from '../../common/ipcEvents'
import useDeviceStore from '../../stores/deviceStore' import useDeviceStore from '../../stores/deviceStore'
import AlgorithmSettings from '../AlgorithmSettings/AlgorithmSettings'
function SystemSettings() { function SystemSettings() {
// //
@ -25,9 +26,9 @@ function SystemSettings() {
// //
const [realtimeDataEnabled, setRealtimeDataEnabled] = useState(false) const [realtimeDataEnabled, setRealtimeDataEnabled] = useState(false)
const [alarmDataEnabled, setAlarmDataEnabled] = useState(false) const [alarmDataEnabled, setAlarmDataEnabled] = useState(false)
//
const [medianFilterEnabled, setMedianFilterEnabled] = useState(false) // // Modal
const [medianFilterParam, setMedianFilterParam] = useState('') // const [algorithmModalVisible, setAlgorithmModalVisible] = useState(false)
// //
const connectedDevice = useDeviceStore((state) => state.connectedDevice) const connectedDevice = useDeviceStore((state) => state.connectedDevice)
@ -869,28 +870,26 @@ function SystemSettings() {
报警数据 报警数据
</Checkbox> </Checkbox>
</div> </div>
{/* 算法设置 */} {/* 算法设置 */}
<div className={styles.subSection}> <div className={styles.subSection}>
<div className={styles.subSectionTitle}>算法设置</div> <div className={styles.subSectionTitle}>算法设置</div>
<Flex vertical gap={8}> <Flex vertical gap={8}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Button
<div>是否启用中值滤波算法</div> type="primary"
<Switch onClick={() => setAlgorithmModalVisible(true)}
checked={medianFilterEnabled} // disabled={!connectedDevice}
onChange={(checked) => setMedianFilterEnabled(checked)} >
/> 中值滤波算法配置
</div> </Button>
<Flex align="center" gap={8}>
<Input
addonBefore={'算法参数值'}
value={medianFilterParam}
onChange={(e) => setMedianFilterParam(e.target.value)}
placeholder="请输入算法参数值"
style={{ width: '100%' }}
/>
</Flex>
</Flex> </Flex>
</div> </div>
{/* 算法设置 Modal */}
<AlgorithmSettings
visible={algorithmModalVisible}
onClose={() => setAlgorithmModalVisible(false)}
/>
</Flex> </Flex>
) )
} }

Loading…
Cancel
Save