Browse Source

feat: 数据修正系数可配置多个

master
liujiangyong 2 weeks ago
parent
commit
5781849140
  1. 306
      src/renderer/src/components/SystemSettings/SystemSettings.jsx
  2. 110
      src/renderer/src/components/SystemSettings/SystemSettings.module.css
  3. 43
      src/renderer/src/stores/deviceStore.js
  4. 141
      src/renderer/src/utils/deflectionCorrection.js

306
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 (
<Flex vertical gap={4} className={styles.container}>
<div className={styles.header}>
@ -881,42 +999,6 @@ function SystemSettings() {
</Flex>
</div>
<div className={styles.subSection}>
<div className={styles.subSectionTitle}>数据修正系数</div>
<Flex gap="middle" className={styles.correctionInputs} vertical>
<InputNumber
precision={6}
addonBefore={'X轴 K'}
className={styles.inputNumber}
value={correctionFactors.xK}
onChange={(value) => handleCorrectionFactorChange('xK', value)}
/>
<InputNumber
precision={6}
addonBefore={'X轴 B'}
className={styles.inputNumber}
value={correctionFactors.xB}
onChange={(value) => handleCorrectionFactorChange('xB', value)}
/>
<InputNumber
precision={6}
addonBefore={'Y轴 K'}
className={styles.inputNumber}
value={correctionFactors.yK}
onChange={(value) => handleCorrectionFactorChange('yK', value)}
/>
<InputNumber
precision={6}
addonBefore={'Y轴 B'}
className={styles.inputNumberLast}
value={correctionFactors.yB}
onChange={(value) => handleCorrectionFactorChange('yB', value)}
/>
</Flex>
</div>
</div>
{/* 存储设置 */}
@ -982,6 +1064,132 @@ function SystemSettings() {
</Flex>
</div>
<div className={styles.subSection}>
<Button
type="text"
className={styles.correctionPanelToggle}
onClick={() => setCorrectionPanelExpanded((prev) => !prev)}
>
{correctionPanelExpanded ? <DownOutlined /> : <RightOutlined />}
数据修正系数
</Button>
{correctionPanelExpanded ? (
<Flex vertical gap={8} className={styles.correctionPanelContent}>
<div className={styles.correctionPanelHint}>
当前应用距离{activeCorrectionDistance}
</div>
<Flex justify="flex-end">
<Button icon={<PlusOutlined />} type="primary" onClick={handleAddCorrectionProfile}>
新增距离配置
</Button>
</Flex>
<div className={styles.correctionCardList}>
{correctionProfiles.map((profile) => {
const isActive = profile.distance === activeCorrectionDistance
return (
<div
key={profile.distance}
className={`${styles.correctionCard} ${isActive ? styles.correctionCardActive : ''}`}
>
<div className={styles.correctionCardHeader}>
<div className={styles.correctionCardTitleRow}>
<span className={styles.correctionCardTitle}>距离配置</span>
{isActive ? (
<span className={styles.correctionCardBadge}>
<CheckCircleFilled />
当前应用
</span>
) : null}
</div>
<div className={styles.correctionCardActions}>
<Button
type={isActive ? 'default' : 'primary'}
size="small"
onClick={() => handleActivateCorrectionProfile(profile.distance)}
>
{isActive ? '已应用' : '应用'}
</Button>
<Button
danger
type="text"
size="small"
icon={<DeleteOutlined />}
onClick={() => handleDeleteCorrectionProfile(profile.distance)}
>
删除
</Button>
</div>
</div>
<div className={styles.correctionDistanceRow}>
<span className={styles.correctionFieldLabel}>距离()</span>
<InputNumber
min={1}
precision={0}
value={profile.distance}
onChange={(nextValue) =>
handleCorrectionProfileChange(profile.distance, 'distance', nextValue)
}
className={styles.correctionDistanceInput}
/>
</div>
<div className={styles.correctionFactorGrid}>
<div className={styles.correctionField}>
<span className={styles.correctionFieldLabel}>xK</span>
<InputNumber
precision={6}
value={profile.xK}
onChange={(nextValue) =>
handleCorrectionProfileChange(profile.distance, 'xK', nextValue)
}
className={styles.correctionFieldInput}
/>
</div>
<div className={styles.correctionField}>
<span className={styles.correctionFieldLabel}>xB</span>
<InputNumber
precision={6}
value={profile.xB}
onChange={(nextValue) =>
handleCorrectionProfileChange(profile.distance, 'xB', nextValue)
}
className={styles.correctionFieldInput}
/>
</div>
<div className={styles.correctionField}>
<span className={styles.correctionFieldLabel}>yK</span>
<InputNumber
precision={6}
value={profile.yK}
onChange={(nextValue) =>
handleCorrectionProfileChange(profile.distance, 'yK', nextValue)
}
className={styles.correctionFieldInput}
/>
</div>
<div className={styles.correctionField}>
<span className={styles.correctionFieldLabel}>yB</span>
<InputNumber
precision={6}
value={profile.yB}
onChange={(nextValue) =>
handleCorrectionProfileChange(profile.distance, 'yB', nextValue)
}
className={styles.correctionFieldInput}
/>
</div>
</div>
</div>
)
})}
</div>
</Flex>
) : null}
</div>
{/* 算法设置 Modal */}
<AlgorithmSettings
visible={algorithmModalVisible}

110
src/renderer/src/components/SystemSettings/SystemSettings.module.css

@ -120,6 +120,116 @@
margin-bottom: 6px;
}
.correctionPanelToggle {
width: 100%;
justify-content: flex-start;
padding-left: 0;
font-weight: bold;
}
.correctionPanelContent {
margin-top: 6px;
}
.correctionPanelHint {
font-size: 12px;
color: #666;
}
.correctionCardList {
display: flex;
flex-direction: column;
gap: 10px;
}
.correctionCard {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 10px;
background: #fafafa;
}
.correctionCardActive {
border-color: #1677ff;
background: #f0f7ff;
box-shadow: inset 0 0 0 1px rgba(22, 119, 255, 0.08);
}
.correctionCardHeader {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
margin-bottom: 10px;
}
.correctionCardTitleRow {
display: flex;
flex-direction: column;
gap: 4px;
}
.correctionCardTitle {
font-size: 13px;
font-weight: 600;
color: #222;
}
.correctionCardBadge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #1677ff;
}
.correctionCardActions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
.correctionDistanceRow {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 10px;
}
.correctionFactorGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 8px;
}
.correctionField {
display: flex;
flex-direction: column;
gap: 4px;
}
.correctionFieldLabel {
font-size: 12px;
color: #666;
}
.correctionDistanceInput,
.correctionFieldInput {
width: 100%;
}
@media (max-width: 900px) {
.correctionCardHeader {
flex-direction: column;
}
.correctionCardActions {
width: 100%;
justify-content: flex-end;
}
}
.storageSection {
border: 1px solid #eee;
border-radius: 6px;

43
src/renderer/src/stores/deviceStore.js

@ -1,5 +1,13 @@
import {
createDefaultCorrectionConfig,
getActiveCorrectionFactors,
sanitizeCorrectionProfiles
} from '../utils/deflectionCorrection'
import { create } from 'zustand'
const defaultCorrectionConfig = createDefaultCorrectionConfig()
const useDeviceStore = create((set) => ({
// 当前连接的设备信息
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: () => {

141
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

Loading…
Cancel
Save