|
|
|
@ -4,13 +4,59 @@ import { useState, useEffect } from 'react' |
|
|
|
import useDeviceStore from '../../stores/deviceStore' |
|
|
|
import { IPC_EVENT } from '../../common/ipcEvents' |
|
|
|
|
|
|
|
/** |
|
|
|
* 中值滤波算法配置组件 |
|
|
|
* |
|
|
|
* @description |
|
|
|
* 该组件用于配置设备的中值滤波算法参数,支持对每个测点进行独立配置。 |
|
|
|
* |
|
|
|
* @业务规则 |
|
|
|
* 1. 窗口大小(xLen/yLen)必须满足以下条件之一: |
|
|
|
* - 输入 0 或 1:表示该方向不启用中值滤波 |
|
|
|
* - 输入 >= 3 的奇数(如 3, 5, 7, 9...):表示启用中值滤波,数值越大滤波效果越强 |
|
|
|
* - 取值范围:0-50 |
|
|
|
* |
|
|
|
* 2. 数据加载时机: |
|
|
|
* - 只在 Modal 打开时才从设备加载配置数据(按需加载) |
|
|
|
* - 避免组件挂载时的不必要请求 |
|
|
|
* |
|
|
|
* 3. 输入验证时机: |
|
|
|
* - onChange:允许自由输入,不进行验证(支持多位数输入如 21、31) |
|
|
|
* - onBlur:失去焦点时才进行验证,不合法的值会被清空并提示 |
|
|
|
* |
|
|
|
* @props |
|
|
|
* @param {boolean} visible - Modal 是否可见 |
|
|
|
* @param {function} onClose - 关闭 Modal 的回调函数 |
|
|
|
* |
|
|
|
* @example |
|
|
|
* <AlgorithmSettings |
|
|
|
* visible={modalVisible} |
|
|
|
* onClose={() => setModalVisible(false)} |
|
|
|
* /> |
|
|
|
*/ |
|
|
|
function AlgorithmSettings({ visible, onClose }) { |
|
|
|
const [loading, setLoading] = useState(false) |
|
|
|
|
|
|
|
const connectedDevice = useDeviceStore((state) => state.connectedDevice) |
|
|
|
console.log('当前连接的设备:', connectedDevice) |
|
|
|
|
|
|
|
// 点位配置列表数据 |
|
|
|
const [pointSettings, setPointSettings] = useState([]) |
|
|
|
// 数据结构:{ id, pos, xLen, yLen, enable } |
|
|
|
const [pointSettings, setPointSettings] = useState([ |
|
|
|
{ |
|
|
|
id: 1, |
|
|
|
pos: '点位1', |
|
|
|
xLen: null, |
|
|
|
yLen: null, |
|
|
|
enable: false |
|
|
|
},{ |
|
|
|
id: 2, |
|
|
|
pos: '点位2', |
|
|
|
xLen: null, |
|
|
|
yLen: null, |
|
|
|
enable: false |
|
|
|
} |
|
|
|
]) |
|
|
|
|
|
|
|
// 只在 Modal 打开时获取中值滤波算法配置 |
|
|
|
useEffect(() => { |
|
|
|
@ -63,14 +109,61 @@ function AlgorithmSettings({ visible, onClose }) { |
|
|
|
loadMedianFilterConfig() |
|
|
|
}, [visible, connectedDevice]) // 依赖 visible,当 Modal 打开时触发加载 |
|
|
|
|
|
|
|
// 更新点位配置 |
|
|
|
/** |
|
|
|
* 验证窗口大小输入是否合法 |
|
|
|
* |
|
|
|
* @param {number|null|string} value - 要验证的值 |
|
|
|
* @returns {boolean} - 是否通过验证 |
|
|
|
* |
|
|
|
* @规则 |
|
|
|
* - 允许空值(null 或 '') |
|
|
|
* - 允许 0 或 1(表示不启用该方向的中值滤波) |
|
|
|
* - 允许大于等于 3 的奇数(3, 5, 7, 9, 11...) |
|
|
|
* - 取值范围:0-50 |
|
|
|
* - 不允许负数、偶数(2, 4, 6...除了0) |
|
|
|
*/ |
|
|
|
const validateWindowSize = (value) => { |
|
|
|
// 允许空值 |
|
|
|
if (value === null || value === '') return true |
|
|
|
|
|
|
|
const num = Number(value) |
|
|
|
|
|
|
|
// 检查是否超出范围 |
|
|
|
if (num < 0 || num > 50) return false |
|
|
|
|
|
|
|
// 0或1表示不启用 |
|
|
|
if (num === 0 || num === 1) return true |
|
|
|
|
|
|
|
// 必须是大于等于3的奇数 |
|
|
|
if (num >= 3 && num % 2 === 1) return true |
|
|
|
|
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 更新点位配置 |
|
|
|
* 主要用于 Switch 组件的 onChange 事件 |
|
|
|
* |
|
|
|
* @param {number} id - 点位 ID |
|
|
|
* @param {string} field - 要更新的字段名('enable', 'xLen', 'yLen') |
|
|
|
* @param {any} value - 新值 |
|
|
|
*/ |
|
|
|
const updatePointSetting = (id, field, value) => { |
|
|
|
setPointSettings((prev) => |
|
|
|
prev.map((item) => (item.id === id ? { ...item, [field]: value } : item)) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// 保存配置 |
|
|
|
/** |
|
|
|
* 保存配置到设备 |
|
|
|
* |
|
|
|
* @description |
|
|
|
* 将前端配置数据转换为设备需要的格式并发送到设备端 |
|
|
|
* |
|
|
|
* @数据转换 |
|
|
|
* 前端格式:{ id, pos: "点位1", xLen, yLen, enable } |
|
|
|
* 设备格式:{ type: "median", sensors: [{ pos: "1", xLen, yLen, enable }] } |
|
|
|
*/ |
|
|
|
const handleSave = async () => { |
|
|
|
if (!connectedDevice) { |
|
|
|
message.error('设备未连接') |
|
|
|
@ -84,8 +177,8 @@ function AlgorithmSettings({ visible, onClose }) { |
|
|
|
sensors: pointSettings.map((setting) => ({ |
|
|
|
enable: setting.enable, |
|
|
|
pos: setting.pos.replace('点位', ''), // 从"点位1"中提取"1" |
|
|
|
xLen: setting.xLen, |
|
|
|
yLen: setting.yLen |
|
|
|
xLen: setting.xLen || 0, |
|
|
|
yLen: setting.yLen || 0 |
|
|
|
})) |
|
|
|
} |
|
|
|
|
|
|
|
@ -124,6 +217,17 @@ function AlgorithmSettings({ visible, onClose }) { |
|
|
|
</Button> |
|
|
|
]} |
|
|
|
> |
|
|
|
<div style={{ |
|
|
|
padding: '12px', |
|
|
|
marginBottom: '16px', |
|
|
|
backgroundColor: '#e6f4ff', |
|
|
|
border: '1px solid #91caff', |
|
|
|
borderRadius: '4px', |
|
|
|
color: '#0958d9', |
|
|
|
fontSize: '14px' |
|
|
|
}}> |
|
|
|
💡 <strong>配置提示:</strong>窗口大小(X/Y):请输入一个大于或等于3的奇数。输入0或1表示该方向不启用中值滤波。 |
|
|
|
</div> |
|
|
|
<List |
|
|
|
itemLayout="vertical" |
|
|
|
dataSource={pointSettings} |
|
|
|
@ -134,27 +238,92 @@ function AlgorithmSettings({ visible, onClose }) { |
|
|
|
<Flex vertical gap={12}> |
|
|
|
<Flex align="center" gap={8}> |
|
|
|
<span style={{ minWidth: 100 }}>X方向:</span> |
|
|
|
{/* |
|
|
|
输入验证策略: |
|
|
|
1. onChange: 不验证,直接更新 state,支持多位数输入(如输入21时,先输入2再输入1) |
|
|
|
2. onBlur: 失去焦点时验证,不合法则提示并清空 |
|
|
|
这样可以避免在输入过程中因为中间值不合法而导致输入被拦截 |
|
|
|
*/} |
|
|
|
<Input |
|
|
|
type="number" |
|
|
|
value={item.xLen} |
|
|
|
onChange={(e) => |
|
|
|
updatePointSetting(item.id, 'xLen', Number(e.target.value)) |
|
|
|
} |
|
|
|
min={0} |
|
|
|
max={50} |
|
|
|
placeholder="0/1或>=3的奇数" |
|
|
|
onChange={(e) => { |
|
|
|
// onChange 时直接更新,不验证,允许输入过程 |
|
|
|
const value = e.target.value === '' ? null : Number(e.target.value) |
|
|
|
setPointSettings((prev) => |
|
|
|
prev.map((setting) => |
|
|
|
setting.id === item.id ? { ...setting, xLen: value } : setting |
|
|
|
) |
|
|
|
) |
|
|
|
}} |
|
|
|
onBlur={(e) => { |
|
|
|
// onBlur 时才验证 |
|
|
|
const value = e.target.value === '' ? null : Number(e.target.value) |
|
|
|
if (value !== null && !validateWindowSize(value)) { |
|
|
|
const num = Number(value) |
|
|
|
if (num > 50) { |
|
|
|
message.warning('窗口大小不能超过50') |
|
|
|
} else if (num < 0) { |
|
|
|
message.warning('窗口大小不能为负数') |
|
|
|
} else { |
|
|
|
message.warning('窗口大小必须是大于等于3的奇数,或者输入0/1表示不启用') |
|
|
|
} |
|
|
|
// 恢复为上一次有效值或 null |
|
|
|
setPointSettings((prev) => |
|
|
|
prev.map((setting) => |
|
|
|
setting.id === item.id ? { ...setting, xLen: null } : setting |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
}} |
|
|
|
style={{ width: 150 }} |
|
|
|
/> |
|
|
|
</Flex> |
|
|
|
<Flex align="center" gap={8}> |
|
|
|
<span style={{ minWidth: 100 }}>Y方向:</span> |
|
|
|
{/* Y方向使用与X方向相同的验证策略 */} |
|
|
|
<Input |
|
|
|
type="number" |
|
|
|
value={item.yLen} |
|
|
|
onChange={(e) => |
|
|
|
updatePointSetting(item.id, 'yLen', Number(e.target.value)) |
|
|
|
} |
|
|
|
max={50} |
|
|
|
min={0} |
|
|
|
placeholder="0/1或>=3的奇数" |
|
|
|
onChange={(e) => { |
|
|
|
// onChange 时直接更新,不验证,允许输入过程 |
|
|
|
const value = e.target.value === '' ? null : Number(e.target.value) |
|
|
|
setPointSettings((prev) => |
|
|
|
prev.map((setting) => |
|
|
|
setting.id === item.id ? { ...setting, yLen: value } : setting |
|
|
|
) |
|
|
|
) |
|
|
|
}} |
|
|
|
onBlur={(e) => { |
|
|
|
// onBlur 时才验证 |
|
|
|
const value = e.target.value === '' ? null : Number(e.target.value) |
|
|
|
if (value !== null && !validateWindowSize(value)) { |
|
|
|
const num = Number(value) |
|
|
|
if (num > 50) { |
|
|
|
message.warning('窗口大小不能超过50') |
|
|
|
} else if (num < 0) { |
|
|
|
message.warning('窗口大小不能为负数') |
|
|
|
} else { |
|
|
|
message.warning('窗口大小必须是大于等于3的奇数,或者输入0/1表示不启用') |
|
|
|
} |
|
|
|
// 恢复为上一次有效值或 null |
|
|
|
setPointSettings((prev) => |
|
|
|
prev.map((setting) => |
|
|
|
setting.id === item.id ? { ...setting, yLen: null } : setting |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
}} |
|
|
|
style={{ width: 150 }} |
|
|
|
/> |
|
|
|
</Flex> |
|
|
|
<Flex align="center" gap={8} justify="space-between"> |
|
|
|
<Flex align="center" gap={8}> |
|
|
|
<span>是否启用中值滤波算法:</span> |
|
|
|
<Switch |
|
|
|
checked={item.enable} |
|
|
|
|