|
@ -8,7 +8,7 @@ import { |
|
|
EyeOutlined, |
|
|
EyeOutlined, |
|
|
EditOutlined |
|
|
EditOutlined |
|
|
} from '@ant-design/icons' |
|
|
} from '@ant-design/icons' |
|
|
import { useState, useEffect } 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' |
|
|
|
|
|
|
|
@ -22,6 +22,10 @@ function SystemSettings() { |
|
|
const [clearZeroLoading, setClearZeroLoading] = useState(false) |
|
|
const [clearZeroLoading, setClearZeroLoading] = useState(false) |
|
|
const [storagePath, setStoragePath] = useState('') |
|
|
const [storagePath, setStoragePath] = useState('') |
|
|
|
|
|
|
|
|
|
|
|
// 新增:实时数据记录状态 |
|
|
|
|
|
const [realtimeDataEnabled, setRealtimeDataEnabled] = useState(false) |
|
|
|
|
|
const [alarmDataEnabled, setAlarmDataEnabled] = useState(false) |
|
|
|
|
|
|
|
|
// 获取设备连接状态和重连配置 |
|
|
// 获取设备连接状态和重连配置 |
|
|
const connectedDevice = useDeviceStore((state) => state.connectedDevice) |
|
|
const connectedDevice = useDeviceStore((state) => state.connectedDevice) |
|
|
const reconnectEnabled = useDeviceStore((state) => state.reconnectEnabled) |
|
|
const reconnectEnabled = useDeviceStore((state) => state.reconnectEnabled) |
|
@ -35,6 +39,24 @@ function SystemSettings() { |
|
|
const setAlarmEnabled = useDeviceStore((state) => state.setAlarmEnabled) |
|
|
const setAlarmEnabled = useDeviceStore((state) => state.setAlarmEnabled) |
|
|
const setAlarmLimits = useDeviceStore((state) => state.setAlarmLimits) |
|
|
const setAlarmLimits = useDeviceStore((state) => state.setAlarmLimits) |
|
|
|
|
|
|
|
|
|
|
|
// 使用ref来存储最新的状态值,避免闭包问题 |
|
|
|
|
|
const realtimeDataEnabledRef = useRef(realtimeDataEnabled) |
|
|
|
|
|
const connectedDeviceRef = useRef(connectedDevice) |
|
|
|
|
|
const storagePathRef = useRef(storagePath) |
|
|
|
|
|
|
|
|
|
|
|
// 更新ref值 |
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
realtimeDataEnabledRef.current = realtimeDataEnabled |
|
|
|
|
|
}, [realtimeDataEnabled]) |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
connectedDeviceRef.current = connectedDevice |
|
|
|
|
|
}, [connectedDevice]) |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
storagePathRef.current = storagePath |
|
|
|
|
|
}, [storagePath]) |
|
|
|
|
|
|
|
|
// 监听重连状态更新 |
|
|
// 监听重连状态更新 |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
const reconnectStatusHandler = (event, result) => { |
|
|
const reconnectStatusHandler = (event, result) => { |
|
@ -78,6 +100,87 @@ function SystemSettings() { |
|
|
} |
|
|
} |
|
|
}, []) |
|
|
}, []) |
|
|
|
|
|
|
|
|
|
|
|
// 新增:写入实时数据到CSV文件 |
|
|
|
|
|
const writeRealtimeDataToCSV = useCallback( |
|
|
|
|
|
async (data) => { |
|
|
|
|
|
if ( |
|
|
|
|
|
!realtimeDataEnabledRef.current || |
|
|
|
|
|
!connectedDeviceRef.current?.ip || |
|
|
|
|
|
!storagePathRef.current |
|
|
|
|
|
) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 构建文件路径: 存储路径/IP地址/实时数据/YYYY-MM-DD.csv |
|
|
|
|
|
const today = new Date() |
|
|
|
|
|
const dateStr = |
|
|
|
|
|
today.getFullYear() + |
|
|
|
|
|
String(today.getMonth() + 1).padStart(2, '0') + |
|
|
|
|
|
String(today.getDate()).padStart(2, '0') |
|
|
|
|
|
|
|
|
|
|
|
const ipFolder = connectedDeviceRef.current.ip // 直接使用IP地址作为文件夹名 |
|
|
|
|
|
const realtimeDataDir = `${storagePathRef.current}/${ipFolder}/实时数据` |
|
|
|
|
|
const csvFilePath = `${realtimeDataDir}/${dateStr}.csv` |
|
|
|
|
|
|
|
|
|
|
|
// 确保目录存在 |
|
|
|
|
|
const dirResult = await window.electron.ipcRenderer.invoke( |
|
|
|
|
|
'ensure-directory', |
|
|
|
|
|
realtimeDataDir |
|
|
|
|
|
) |
|
|
|
|
|
if (!dirResult.success) { |
|
|
|
|
|
console.error('创建实时数据目录失败:', dirResult.error) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查CSV文件是否存在,如果不存在则先写入表头 |
|
|
|
|
|
const fileExistsResult = await window.electron.ipcRenderer.invoke( |
|
|
|
|
|
'check-file-exists', |
|
|
|
|
|
csvFilePath |
|
|
|
|
|
) |
|
|
|
|
|
if (fileExistsResult.success && !fileExistsResult.exists) { |
|
|
|
|
|
const sensorCount = data.values.sensors ? data.values.sensors.length : 0 |
|
|
|
|
|
const header = createCSVHeader(sensorCount) |
|
|
|
|
|
await window.electron.ipcRenderer.invoke('write-csv-header', csvFilePath, header) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 追加数据行 |
|
|
|
|
|
const dataRow = sensorDataToCSVRow(data) |
|
|
|
|
|
const appendResult = await window.electron.ipcRenderer.invoke( |
|
|
|
|
|
'append-to-file', |
|
|
|
|
|
csvFilePath, |
|
|
|
|
|
dataRow |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (!appendResult.success) { |
|
|
|
|
|
console.error('写入CSV文件失败:', appendResult.error) |
|
|
|
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('写入实时数据失败:', error) |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
[] // 空依赖数组,因为我们使用ref来访问最新的值 |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// 监听实时数据并记录到CSV |
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
if (window?.electron?.ipcRenderer?.on && IPC_EVENT?.RESULT_REPLY) { |
|
|
|
|
|
const handler = (event, data) => { |
|
|
|
|
|
console.log('收到主进程RESULT_REPLY:', data) |
|
|
|
|
|
|
|
|
|
|
|
// 检查数据格式 |
|
|
|
|
|
if (data && data.values && data.values.sensors && Array.isArray(data.values.sensors)) { |
|
|
|
|
|
// 直接调用写入函数,内部会检查是否启用实时数据记录 |
|
|
|
|
|
writeRealtimeDataToCSV(data) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
window.electron.ipcRenderer.on(IPC_EVENT.RESULT_REPLY, handler) |
|
|
|
|
|
return () => { |
|
|
|
|
|
window.electron.ipcRenderer.removeListener(IPC_EVENT.RESULT_REPLY, handler) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, [writeRealtimeDataToCSV]) |
|
|
|
|
|
|
|
|
// 打开存储目录 |
|
|
// 打开存储目录 |
|
|
const handleOpenStoragePath = async () => { |
|
|
const handleOpenStoragePath = async () => { |
|
|
if (!storagePath) { |
|
|
if (!storagePath) { |
|
@ -119,6 +222,46 @@ function SystemSettings() { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//创建CSV表头(动态根据测点数量) |
|
|
|
|
|
const createCSVHeader = (sensorCount = 0) => { |
|
|
|
|
|
const headers = ['数据记录时间'] |
|
|
|
|
|
for (let i = 1; i <= sensorCount; i++) { |
|
|
|
|
|
headers.push( |
|
|
|
|
|
`测点 ${i} 基准标靶`, |
|
|
|
|
|
`测点 ${i} 计算系数`, |
|
|
|
|
|
`测点 ${i}xReal 坐标`, |
|
|
|
|
|
`测点 ${i}yReal 坐标` |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
return headers.join(',') + '\n' |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 将传感器数据转换为CSV行(动态处理所有测点) |
|
|
|
|
|
const sensorDataToCSVRow = (data) => { |
|
|
|
|
|
const timestamp = new Date().toLocaleString() |
|
|
|
|
|
const sensors = data.values.sensors || [] |
|
|
|
|
|
|
|
|
|
|
|
const row = [timestamp] |
|
|
|
|
|
|
|
|
|
|
|
// 动态处理所有测点的数据 |
|
|
|
|
|
for (let i = 0; i < sensors.length; i++) { |
|
|
|
|
|
const sensor = sensors[i] |
|
|
|
|
|
if (sensor) { |
|
|
|
|
|
row.push( |
|
|
|
|
|
sensor.tar || '', |
|
|
|
|
|
sensor.arg || '', |
|
|
|
|
|
sensor.xReal !== null && sensor.xReal !== undefined ? sensor.xReal : '无数据', |
|
|
|
|
|
sensor.yReal !== null && sensor.yReal !== undefined ? sensor.yReal : '无数据' |
|
|
|
|
|
) |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果没有对应测点数据,填入空值 |
|
|
|
|
|
row.push('', '', '无数据', '无数据') |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return row.join(',') + '\n' |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 读取参数函数 |
|
|
// 读取参数函数 |
|
|
const handleReadParam = async () => { |
|
|
const handleReadParam = async () => { |
|
|
if (!selectedParam) { |
|
|
if (!selectedParam) { |
|
@ -522,10 +665,21 @@ function SystemSettings() { |
|
|
placeholder="存储目录路径" |
|
|
placeholder="存储目录路径" |
|
|
/> |
|
|
/> |
|
|
</Flex> |
|
|
</Flex> |
|
|
<Checkbox className={styles.checkboxRight} disabled={!connectedDevice}> |
|
|
<Checkbox |
|
|
|
|
|
className={styles.checkboxRight} |
|
|
|
|
|
disabled={!connectedDevice} |
|
|
|
|
|
checked={realtimeDataEnabled} |
|
|
|
|
|
onChange={(e) => setRealtimeDataEnabled(e.target.checked)} |
|
|
|
|
|
> |
|
|
实时数据 |
|
|
实时数据 |
|
|
</Checkbox> |
|
|
</Checkbox> |
|
|
<Checkbox disabled={!connectedDevice}>报警数据</Checkbox> |
|
|
<Checkbox |
|
|
|
|
|
disabled={!connectedDevice} |
|
|
|
|
|
checked={alarmDataEnabled} |
|
|
|
|
|
onChange={(e) => setAlarmDataEnabled(e.target.checked)} |
|
|
|
|
|
> |
|
|
|
|
|
报警数据 |
|
|
|
|
|
</Checkbox> |
|
|
</div> |
|
|
</div> |
|
|
</Flex> |
|
|
</Flex> |
|
|
) |
|
|
) |
|
|