diff --git a/src/renderer/src/components/SystemSettings/SystemSettings.jsx b/src/renderer/src/components/SystemSettings/SystemSettings.jsx index 02088b4..7713261 100644 --- a/src/renderer/src/components/SystemSettings/SystemSettings.jsx +++ b/src/renderer/src/components/SystemSettings/SystemSettings.jsx @@ -6,7 +6,12 @@ import { ControlFilled, FolderFilled, EyeOutlined, - EditOutlined + EditOutlined, + DownOutlined, + RightOutlined, + DeleteOutlined, + PlusOutlined, + CheckCircleFilled } from '@ant-design/icons' import { useState, useEffect, useCallback, useRef } from 'react' import { IPC_EVENT } from '../../common/ipcEvents' @@ -14,9 +19,14 @@ import useDeviceStore from '../../stores/deviceStore' import AlgorithmSettings from '../AlgorithmSettings/AlgorithmSettings' import { applyCorrectionToSensor, - DEFAULT_CORRECTION_FACTORS, + createDefaultCorrectionConfig, + createDefaultCorrectionProfile, + DEFAULT_CORRECTION_DISTANCE, DEFLECTION_CORRECTION_STORAGE_KEY, - sanitizeCorrectionFactors + getActiveCorrectionFactors, + removeCorrectionProfileWithFallback, + sanitizeCorrectionConfig, + sanitizeCorrectionProfiles } from '../../utils/deflectionCorrection' function SystemSettings() { @@ -35,6 +45,7 @@ function SystemSettings() { // 算法设置 Modal 状态 const [algorithmModalVisible, setAlgorithmModalVisible] = useState(false) + const [correctionPanelExpanded, setCorrectionPanelExpanded] = useState(false) // 获取设备连接状态和重连配置 const connectedDevice = useDeviceStore((state) => state.connectedDevice) @@ -44,12 +55,17 @@ function SystemSettings() { const alarmEnabled = useDeviceStore((state) => state.alarmEnabled) const alarmLimits = useDeviceStore((state) => state.alarmLimits) const correctionFactors = useDeviceStore((state) => state.correctionFactors) + const correctionProfiles = useDeviceStore((state) => state.correctionProfiles) + const activeCorrectionDistance = useDeviceStore((state) => state.activeCorrectionDistance) const setReconnectEnabled = useDeviceStore((state) => state.setReconnectEnabled) const setReconnectInterval = useDeviceStore((state) => state.setReconnectInterval) const setReconnecting = useDeviceStore((state) => state.setReconnecting) const setAlarmEnabled = useDeviceStore((state) => state.setAlarmEnabled) const setAlarmLimits = useDeviceStore((state) => state.setAlarmLimits) const setCorrectionFactors = useDeviceStore((state) => state.setCorrectionFactors) + const setCorrectionProfiles = useDeviceStore((state) => state.setCorrectionProfiles) + const setActiveCorrectionDistance = useDeviceStore((state) => state.setActiveCorrectionDistance) + const setActiveCorrectionByDistance = useDeviceStore((state) => state.setActiveCorrectionByDistance) // 使用ref来存储最新的状态值,避免闭包问题 const realtimeDataEnabledRef = useRef(realtimeDataEnabled) @@ -136,18 +152,31 @@ function SystemSettings() { const savedFactors = localStorage.getItem(DEFLECTION_CORRECTION_STORAGE_KEY) if (!savedFactors) { - setCorrectionFactors(DEFAULT_CORRECTION_FACTORS) + const defaultConfig = createDefaultCorrectionConfig() + setCorrectionProfiles(defaultConfig.profiles) + setActiveCorrectionDistance(defaultConfig.activeDistance) + setCorrectionFactors(getActiveCorrectionFactors(defaultConfig)) return } try { const parsedFactors = JSON.parse(savedFactors) - setCorrectionFactors(sanitizeCorrectionFactors(parsedFactors)) + const normalizedConfig = sanitizeCorrectionConfig(parsedFactors) + setCorrectionProfiles(normalizedConfig.profiles) + setActiveCorrectionDistance(normalizedConfig.activeDistance) + setCorrectionFactors(getActiveCorrectionFactors(normalizedConfig)) } catch (error) { console.error('读取数据修正系数失败:', error) - setCorrectionFactors(DEFAULT_CORRECTION_FACTORS) + const defaultConfig = createDefaultCorrectionConfig() + setCorrectionProfiles(defaultConfig.profiles) + setActiveCorrectionDistance(defaultConfig.activeDistance) + setCorrectionFactors(getActiveCorrectionFactors(defaultConfig)) } - }, [setCorrectionFactors]) + }, [ + setCorrectionFactors, + setCorrectionProfiles, + setActiveCorrectionDistance + ]) // 新增:写入实时数据到CSV文件 const writeRealtimeDataToCSV = useCallback( @@ -677,21 +706,110 @@ function SystemSettings() { } } - const handleCorrectionFactorChange = (field, value) => { + const persistCorrectionConfig = (profiles, activeDistance) => { + const normalizedProfiles = sanitizeCorrectionProfiles(profiles) + const nextConfig = sanitizeCorrectionConfig({ + profiles: normalizedProfiles, + activeDistance + }) + + setCorrectionProfiles(nextConfig.profiles) + setActiveCorrectionDistance(nextConfig.activeDistance) + setCorrectionFactors(getActiveCorrectionFactors(nextConfig)) + localStorage.setItem(DEFLECTION_CORRECTION_STORAGE_KEY, JSON.stringify(nextConfig)) + + return nextConfig + } + + const handleAddCorrectionProfile = () => { + const existingDistanceSet = new Set(correctionProfiles.map((profile) => profile.distance)) + let nextDistance = DEFAULT_CORRECTION_DISTANCE + + while (existingDistanceSet.has(nextDistance)) { + nextDistance += 1 + } + + const nextProfiles = sanitizeCorrectionProfiles([ + ...correctionProfiles, + createDefaultCorrectionProfile(nextDistance) + ]) + + persistCorrectionConfig(nextProfiles, activeCorrectionDistance) + message.success(`已新增 ${nextDistance} 米修正系数`) + } + + const handleCorrectionProfileChange = (distance, field, value) => { + if (field === 'distance') { + const normalizedDistance = Number(value) + if (!Number.isInteger(normalizedDistance) || normalizedDistance <= 0) { + return + } + + const duplicateDistance = correctionProfiles.some( + (profile) => profile.distance === normalizedDistance && profile.distance !== distance + ) + if (duplicateDistance) { + message.error(`已存在 ${normalizedDistance} 米配置,请使用其他距离`) + return + } + + const nextProfiles = correctionProfiles.map((profile) => + profile.distance === distance ? { ...profile, distance: normalizedDistance } : profile + ) + const nextActiveDistance = + activeCorrectionDistance === distance ? normalizedDistance : activeCorrectionDistance + + persistCorrectionConfig(nextProfiles, nextActiveDistance) + message.success(`已更新 ${normalizedDistance} 米距离配置`) + return + } + if (value === null || value === undefined || !Number.isFinite(Number(value))) { return } - const nextFactors = { - ...correctionFactors, - [field]: Number(value) + const nextProfiles = correctionProfiles.map((profile) => + profile.distance === distance ? { ...profile, [field]: Number(value) } : profile + ) + + const nextConfig = persistCorrectionConfig(nextProfiles, activeCorrectionDistance) + + if (nextConfig.activeDistance === distance) { + setActiveCorrectionByDistance(distance) } - setCorrectionFactors(nextFactors) - localStorage.setItem(DEFLECTION_CORRECTION_STORAGE_KEY, JSON.stringify(nextFactors)) message.success(`修正系数 ${field} 已更新`) } + const handleActivateCorrectionProfile = (distance) => { + const hasProfile = correctionProfiles.some((profile) => profile.distance === distance) + if (!hasProfile) { + return + } + + const nextConfig = persistCorrectionConfig(correctionProfiles, distance) + setActiveCorrectionByDistance(nextConfig.activeDistance) + message.success(`已应用 ${distance} 米修正系数`) + } + + const handleDeleteCorrectionProfile = (distance) => { + const nextConfig = removeCorrectionProfileWithFallback( + correctionProfiles, + activeCorrectionDistance, + distance + ) + + persistCorrectionConfig(nextConfig.profiles, nextConfig.activeDistance) + setActiveCorrectionByDistance(nextConfig.activeDistance) + + if (nextConfig.activeDistance === DEFAULT_CORRECTION_DISTANCE && distance === activeCorrectionDistance) { + message.success(`已删除 ${distance} 米配置,当前已切换到 ${DEFAULT_CORRECTION_DISTANCE} 米`) + return + } + + message.success(`已删除 ${distance} 米修正系数`) + } + return (
@@ -881,42 +999,6 @@ function SystemSettings() {
-
-
数据修正系数
- - handleCorrectionFactorChange('xK', value)} - /> - - handleCorrectionFactorChange('xB', value)} - /> - - handleCorrectionFactorChange('yK', value)} - /> - - handleCorrectionFactorChange('yB', value)} - /> - -
{/* 存储设置 */} @@ -982,6 +1064,132 @@ function SystemSettings() {
+
+ + + {correctionPanelExpanded ? ( + +
+ 当前应用距离:{activeCorrectionDistance} 米 +
+ + + +
+ {correctionProfiles.map((profile) => { + const isActive = profile.distance === activeCorrectionDistance + + return ( +
+
+
+ 距离配置 + {isActive ? ( + + + 当前应用 + + ) : null} +
+
+ + +
+
+ +
+ 距离(米) + + handleCorrectionProfileChange(profile.distance, 'distance', nextValue) + } + className={styles.correctionDistanceInput} + /> +
+ +
+
+ xK + + handleCorrectionProfileChange(profile.distance, 'xK', nextValue) + } + className={styles.correctionFieldInput} + /> +
+
+ xB + + handleCorrectionProfileChange(profile.distance, 'xB', nextValue) + } + className={styles.correctionFieldInput} + /> +
+
+ yK + + handleCorrectionProfileChange(profile.distance, 'yK', nextValue) + } + className={styles.correctionFieldInput} + /> +
+
+ yB + + handleCorrectionProfileChange(profile.distance, 'yB', nextValue) + } + className={styles.correctionFieldInput} + /> +
+
+
+ ) + })} +
+
+ ) : null} +
+ {/* 算法设置 Modal */} ({ // 当前连接的设备信息 connectedDevice: null, @@ -27,12 +35,9 @@ const useDeviceStore = create((set) => ({ }, // 数据修正系数 - correctionFactors: { - xK: 1, - xB: 0, - yK: 1, - yB: 0 - }, + correctionFactors: getActiveCorrectionFactors(defaultCorrectionConfig), + correctionProfiles: defaultCorrectionConfig.profiles, + activeCorrectionDistance: defaultCorrectionConfig.activeDistance, // 设置连接的设备 setConnectedDevice: (device) => set({ connectedDevice: device }), @@ -65,6 +70,32 @@ const useDeviceStore = create((set) => ({ ...factors } })), + setCorrectionProfiles: (profiles) => + set({ + correctionProfiles: sanitizeCorrectionProfiles(profiles) + }), + setActiveCorrectionDistance: (distance) => + set({ + activeCorrectionDistance: distance + }), + setActiveCorrectionByDistance: (distance) => + set((state) => { + const activeProfile = state.correctionProfiles.find((profile) => profile.distance === distance) + + if (!activeProfile) { + return {} + } + + return { + activeCorrectionDistance: distance, + correctionFactors: { + xK: activeProfile.xK, + xB: activeProfile.xB, + yK: activeProfile.yK, + yB: activeProfile.yB + } + } + }), // 获取当前连接的IP getConnectedIp: () => { diff --git a/src/renderer/src/utils/deflectionCorrection.js b/src/renderer/src/utils/deflectionCorrection.js index 8b5ef19..8d4b8de 100644 --- a/src/renderer/src/utils/deflectionCorrection.js +++ b/src/renderer/src/utils/deflectionCorrection.js @@ -7,11 +7,32 @@ export const DEFAULT_CORRECTION_FACTORS = { yB: 0 } +export const DEFAULT_CORRECTION_DISTANCE = 10 + +export const createDefaultCorrectionProfile = (distance = DEFAULT_CORRECTION_DISTANCE) => ({ + distance, + ...DEFAULT_CORRECTION_FACTORS +}) + +export const createDefaultCorrectionConfig = () => ({ + profiles: [createDefaultCorrectionProfile()], + activeDistance: DEFAULT_CORRECTION_DISTANCE +}) + const toFiniteNumber = (value) => { const num = Number(value) return Number.isFinite(num) ? num : null } +const toPositiveInteger = (value) => { + const num = Number(value) + if (!Number.isInteger(num) || num <= 0) { + return null + } + + return num +} + export const sanitizeCorrectionFactors = (value) => { const next = value && typeof value === 'object' ? value : {} @@ -23,6 +44,126 @@ export const sanitizeCorrectionFactors = (value) => { } } +export const sanitizeCorrectionProfile = (value) => { + const next = value && typeof value === 'object' ? value : {} + const distance = toPositiveInteger(next.distance) + + if (distance === null) { + return null + } + + return { + distance, + ...sanitizeCorrectionFactors(next) + } +} + +export const sanitizeCorrectionProfiles = (profiles) => { + if (!Array.isArray(profiles)) { + return [] + } + + const profileMap = new Map() + + profiles.forEach((profile) => { + const normalizedProfile = sanitizeCorrectionProfile(profile) + if (normalizedProfile) { + profileMap.set(normalizedProfile.distance, normalizedProfile) + } + }) + + return Array.from(profileMap.values()) +} + +export const sanitizeCorrectionConfig = (value) => { + const next = value && typeof value === 'object' ? value : {} + const profiles = sanitizeCorrectionProfiles(next.profiles) + + if (profiles.length === 0) { + return createDefaultCorrectionConfig() + } + + const requestedActiveDistance = toPositiveInteger(next.activeDistance) + const hasRequestedProfile = profiles.some((profile) => profile.distance === requestedActiveDistance) + const activeDistance = hasRequestedProfile ? requestedActiveDistance : profiles[0].distance + + return { + profiles, + activeDistance + } +} + +export const getCorrectionProfileByDistance = (profiles, distance) => { + const normalizedDistance = toPositiveInteger(distance) + if (normalizedDistance === null) { + return null + } + + return profiles.find((profile) => profile.distance === normalizedDistance) || null +} + +export const getActiveCorrectionFactors = (config) => { + const normalizedConfig = sanitizeCorrectionConfig(config) + const activeProfile = getCorrectionProfileByDistance( + normalizedConfig.profiles, + normalizedConfig.activeDistance + ) + + return activeProfile + ? sanitizeCorrectionFactors(activeProfile) + : { ...DEFAULT_CORRECTION_FACTORS } +} + +export const ensureDefaultDistanceProfile = (profiles) => { + const normalizedProfiles = sanitizeCorrectionProfiles(profiles) + const hasDefaultDistance = normalizedProfiles.some( + (profile) => profile.distance === DEFAULT_CORRECTION_DISTANCE + ) + + if (hasDefaultDistance) { + return normalizedProfiles + } + + return [ + ...normalizedProfiles, + createDefaultCorrectionProfile(DEFAULT_CORRECTION_DISTANCE) + ] +} + +export const removeCorrectionProfileWithFallback = (profiles, activeDistance, distanceToDelete) => { + const normalizedProfiles = sanitizeCorrectionProfiles(profiles) + const normalizedDeleteDistance = toPositiveInteger(distanceToDelete) + const normalizedActiveDistance = toPositiveInteger(activeDistance) + + const remainingProfiles = normalizedProfiles.filter( + (profile) => profile.distance !== normalizedDeleteDistance + ) + + const nextProfiles = + remainingProfiles.length > 0 ? remainingProfiles : [createDefaultCorrectionProfile()] + + const deletingActiveProfile = + normalizedDeleteDistance !== null && normalizedDeleteDistance === normalizedActiveDistance + + if (!deletingActiveProfile) { + const safeActiveDistance = getCorrectionProfileByDistance(nextProfiles, normalizedActiveDistance) + ? normalizedActiveDistance + : nextProfiles[0].distance + + return { + profiles: nextProfiles, + activeDistance: safeActiveDistance + } + } + + const profilesWithDefault = ensureDefaultDistanceProfile(nextProfiles) + + return { + profiles: profilesWithDefault, + activeDistance: DEFAULT_CORRECTION_DISTANCE + } +} + export const correctAxisValue = (originalValue, k, b) => { if (originalValue === null || originalValue === undefined || originalValue === '') { return null