diff --git a/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.jsx b/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.jsx index abd978d..e26f599 100644 --- a/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.jsx +++ b/src/renderer/src/components/AlgorithmSettings/AlgorithmSettings.jsx @@ -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 + * 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 }) { ]} > +
+ 💡 配置提示:窗口大小(X/Y):请输入一个大于或等于3的奇数。输入0或1表示该方向不启用中值滤波。 +
X方向: + {/* + 输入验证策略: + 1. onChange: 不验证,直接更新 state,支持多位数输入(如输入21时,先输入2再输入1) + 2. onBlur: 失去焦点时验证,不合法则提示并清空 + 这样可以避免在输入过程中因为中间值不合法而导致输入被拦截 + */} - 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 }} /> Y方向: + {/* Y方向使用与X方向相同的验证策略 */} - 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 }} /> - + 是否启用中值滤波算法: