|
|
@ -43,6 +43,9 @@ function SystemSettings() { |
|
|
|
const realtimeDataEnabledRef = useRef(realtimeDataEnabled) |
|
|
|
const connectedDeviceRef = useRef(connectedDevice) |
|
|
|
const storagePathRef = useRef(storagePath) |
|
|
|
const alarmDataEnabledRef = useRef(alarmDataEnabled) |
|
|
|
const alarmEnabledRef = useRef(alarmEnabled) |
|
|
|
const alarmLimitsRef = useRef(alarmLimits) |
|
|
|
|
|
|
|
// 更新ref值 |
|
|
|
useEffect(() => { |
|
|
@ -57,11 +60,23 @@ function SystemSettings() { |
|
|
|
storagePathRef.current = storagePath |
|
|
|
}, [storagePath]) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
alarmDataEnabledRef.current = alarmDataEnabled |
|
|
|
}, [alarmDataEnabled]) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
alarmEnabledRef.current = alarmEnabled |
|
|
|
}, [alarmEnabled]) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
alarmLimitsRef.current = alarmLimits |
|
|
|
}, [alarmLimits]) |
|
|
|
|
|
|
|
// 监听重连状态更新 |
|
|
|
useEffect(() => { |
|
|
|
const reconnectStatusHandler = (event, result) => { |
|
|
|
const { status } = result |
|
|
|
console.log('Received reconnect status:', result) |
|
|
|
// console.log('Received reconnect status:', result) |
|
|
|
|
|
|
|
switch (status) { |
|
|
|
case 'reconnecting': |
|
|
@ -125,7 +140,7 @@ function SystemSettings() { |
|
|
|
|
|
|
|
// 确保目录存在 |
|
|
|
const dirResult = await window.electron.ipcRenderer.invoke( |
|
|
|
'ensure-directory', |
|
|
|
IPC_EVENT.ENSURE_DIRECTORY, |
|
|
|
realtimeDataDir |
|
|
|
) |
|
|
|
if (!dirResult.success) { |
|
|
@ -135,19 +150,19 @@ function SystemSettings() { |
|
|
|
|
|
|
|
// 检查CSV文件是否存在,如果不存在则先写入表头 |
|
|
|
const fileExistsResult = await window.electron.ipcRenderer.invoke( |
|
|
|
'check-file-exists', |
|
|
|
IPC_EVENT.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) |
|
|
|
await window.electron.ipcRenderer.invoke(IPC_EVENT.WRITE_CSV_HEADER, csvFilePath, header) |
|
|
|
} |
|
|
|
|
|
|
|
// 追加数据行 |
|
|
|
const dataRow = sensorDataToCSVRow(data) |
|
|
|
const appendResult = await window.electron.ipcRenderer.invoke( |
|
|
|
'append-to-file', |
|
|
|
IPC_EVENT.APPEND_TO_FILE, |
|
|
|
csvFilePath, |
|
|
|
dataRow |
|
|
|
) |
|
|
@ -162,16 +177,97 @@ function SystemSettings() { |
|
|
|
[] // 空依赖数组,因为我们使用ref来访问最新的值 |
|
|
|
) |
|
|
|
|
|
|
|
// 新增:写入报警数据到CSV文件 |
|
|
|
const writeAlarmDataToCSV = useCallback( |
|
|
|
async (data, alarmSensors) => { |
|
|
|
// 使用ref获取最新状态值,避免闭包问题 |
|
|
|
if ( |
|
|
|
!alarmDataEnabledRef.current || |
|
|
|
!alarmEnabledRef.current || |
|
|
|
!connectedDeviceRef.current?.ip || |
|
|
|
!storagePathRef.current || |
|
|
|
!alarmSensors.length |
|
|
|
) { |
|
|
|
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 |
|
|
|
const alarmDataDir = `${storagePathRef.current}/${ipFolder}/报警数据` |
|
|
|
const csvFilePath = `${alarmDataDir}/${dateStr}.csv` |
|
|
|
|
|
|
|
// 确保目录存在 |
|
|
|
const dirResult = await window.electron.ipcRenderer.invoke( |
|
|
|
IPC_EVENT.ENSURE_DIRECTORY, |
|
|
|
alarmDataDir |
|
|
|
) |
|
|
|
if (!dirResult.success) { |
|
|
|
console.error('创建报警数据目录失败:', dirResult.error) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 检查CSV文件是否存在,如果不存在则先写入表头 |
|
|
|
const fileExistsResult = await window.electron.ipcRenderer.invoke( |
|
|
|
IPC_EVENT.CHECK_FILE_EXISTS, |
|
|
|
csvFilePath |
|
|
|
) |
|
|
|
if (fileExistsResult.success && !fileExistsResult.exists) { |
|
|
|
const header = createAlarmCSVHeader() |
|
|
|
await window.electron.ipcRenderer.invoke(IPC_EVENT.WRITE_CSV_HEADER, csvFilePath, header) |
|
|
|
} |
|
|
|
|
|
|
|
// 为每个报警传感器写入一行数据 |
|
|
|
for (const sensor of alarmSensors) { |
|
|
|
const dataRow = alarmSensorToCSVRow(data, sensor) |
|
|
|
const appendResult = await window.electron.ipcRenderer.invoke( |
|
|
|
IPC_EVENT.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) |
|
|
|
// console.log('收到主进程RESULT_REPLY:', data) |
|
|
|
|
|
|
|
// 检查数据格式 |
|
|
|
if (data && data.values && data.values.sensors && Array.isArray(data.values.sensors)) { |
|
|
|
// 直接调用写入函数,内部会检查是否启用实时数据记录 |
|
|
|
// 写入实时数据(如果启用) |
|
|
|
writeRealtimeDataToCSV(data) |
|
|
|
|
|
|
|
// 检查报警条件并写入报警数据(如果启用) |
|
|
|
// 使用ref获取最新状态值 |
|
|
|
if ( |
|
|
|
alarmDataEnabledRef.current && |
|
|
|
alarmEnabledRef.current && |
|
|
|
connectedDeviceRef.current?.ip && |
|
|
|
storagePathRef.current |
|
|
|
) { |
|
|
|
const alarmSensors = checkAlarmConditions(data.values.sensors, alarmLimitsRef.current) |
|
|
|
if (alarmSensors.length > 0) { |
|
|
|
// console.log('检测到报警数据:', alarmSensors) |
|
|
|
writeAlarmDataToCSV(data, alarmSensors) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
window.electron.ipcRenderer.on(IPC_EVENT.RESULT_REPLY, handler) |
|
|
@ -179,9 +275,7 @@ function SystemSettings() { |
|
|
|
window.electron.ipcRenderer.removeListener(IPC_EVENT.RESULT_REPLY, handler) |
|
|
|
} |
|
|
|
} |
|
|
|
}, [writeRealtimeDataToCSV]) |
|
|
|
|
|
|
|
// 打开存储目录 |
|
|
|
}, [writeRealtimeDataToCSV, writeAlarmDataToCSV]) // 移除其他依赖项,因为现在使用ref // 打开存储目录 |
|
|
|
const handleOpenStoragePath = async () => { |
|
|
|
if (!storagePath) { |
|
|
|
message.warning('存储路径未设置') |
|
|
@ -190,7 +284,7 @@ function SystemSettings() { |
|
|
|
|
|
|
|
try { |
|
|
|
// 使用 IPC 调用主进程打开目录 |
|
|
|
const result = await window.electron.ipcRenderer.invoke('open-directory', storagePath) |
|
|
|
const result = await window.electron.ipcRenderer.invoke(IPC_EVENT.OPEN_DIRECTORY, storagePath) |
|
|
|
if (!result.success) { |
|
|
|
message.error(`打开目录失败:${result.error}`) |
|
|
|
} |
|
|
@ -204,7 +298,7 @@ function SystemSettings() { |
|
|
|
const handleSelectStoragePath = async () => { |
|
|
|
try { |
|
|
|
// 使用 IPC 调用主进程选择目录 |
|
|
|
const result = await window.electron.ipcRenderer.invoke('select-directory', { |
|
|
|
const result = await window.electron.ipcRenderer.invoke(IPC_EVENT.SELECT_DIRECTORY, { |
|
|
|
title: '选择存储目录', |
|
|
|
defaultPath: storagePath |
|
|
|
}) |
|
|
@ -236,6 +330,22 @@ function SystemSettings() { |
|
|
|
return headers.join(',') + '\n' |
|
|
|
} |
|
|
|
|
|
|
|
// 创建报警数据CSV表头 |
|
|
|
const createAlarmCSVHeader = () => { |
|
|
|
const headers = [ |
|
|
|
'报警记录时间', |
|
|
|
'测点位置', |
|
|
|
'基准标靶', |
|
|
|
'计算系数', |
|
|
|
'xReal坐标', |
|
|
|
'yReal坐标', |
|
|
|
'报警类型', |
|
|
|
'阈值范围', |
|
|
|
'超出值' |
|
|
|
] |
|
|
|
return headers.join(',') + '\n' |
|
|
|
} |
|
|
|
|
|
|
|
// 将传感器数据转换为CSV行(动态处理所有测点) |
|
|
|
const sensorDataToCSVRow = (data) => { |
|
|
|
const timestamp = new Date().toLocaleString() |
|
|
@ -262,6 +372,81 @@ function SystemSettings() { |
|
|
|
return row.join(',') + '\n' |
|
|
|
} |
|
|
|
|
|
|
|
// 将报警传感器数据转换为CSV行 |
|
|
|
const alarmSensorToCSVRow = (data, alarmInfo) => { |
|
|
|
const timestamp = new Date().toLocaleString() |
|
|
|
const { sensor, alarmType, threshold, exceedValue } = alarmInfo |
|
|
|
|
|
|
|
const row = [ |
|
|
|
timestamp, |
|
|
|
sensor.pos || '', |
|
|
|
sensor.tar || '', |
|
|
|
sensor.arg || '', |
|
|
|
sensor.xReal !== null && sensor.xReal !== undefined ? sensor.xReal : '无数据', |
|
|
|
sensor.yReal !== null && sensor.yReal !== undefined ? sensor.yReal : '无数据', |
|
|
|
alarmType, |
|
|
|
threshold, |
|
|
|
exceedValue |
|
|
|
] |
|
|
|
|
|
|
|
return row.join(',') + '\n' |
|
|
|
} |
|
|
|
|
|
|
|
// 检查传感器数据是否超出阈值 |
|
|
|
const checkAlarmConditions = (sensors, limits) => { |
|
|
|
const alarmSensors = [] |
|
|
|
|
|
|
|
if (!sensors || !Array.isArray(sensors)) { |
|
|
|
return alarmSensors |
|
|
|
} |
|
|
|
|
|
|
|
sensors.forEach((sensor) => { |
|
|
|
if (!sensor) return |
|
|
|
|
|
|
|
const { xReal, yReal } = sensor |
|
|
|
|
|
|
|
// 检查X轴是否超出阈值 |
|
|
|
if (xReal !== null && xReal !== undefined) { |
|
|
|
if (xReal > limits.xUpper) { |
|
|
|
alarmSensors.push({ |
|
|
|
sensor, |
|
|
|
alarmType: 'X轴上限超出', |
|
|
|
threshold: `±${limits.xUpper}`, |
|
|
|
exceedValue: (xReal - limits.xUpper).toFixed(2) |
|
|
|
}) |
|
|
|
} else if (xReal < limits.xLower) { |
|
|
|
alarmSensors.push({ |
|
|
|
sensor, |
|
|
|
alarmType: 'X轴下限超出', |
|
|
|
threshold: `±${Math.abs(limits.xLower)}`, |
|
|
|
exceedValue: (limits.xLower - xReal).toFixed(2) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 检查Y轴是否超出阈值 |
|
|
|
if (yReal !== null && yReal !== undefined) { |
|
|
|
if (yReal > limits.yUpper) { |
|
|
|
alarmSensors.push({ |
|
|
|
sensor, |
|
|
|
alarmType: 'Y轴上限超出', |
|
|
|
threshold: `±${limits.yUpper}`, |
|
|
|
exceedValue: (yReal - limits.yUpper).toFixed(2) |
|
|
|
}) |
|
|
|
} else if (yReal < limits.yLower) { |
|
|
|
alarmSensors.push({ |
|
|
|
sensor, |
|
|
|
alarmType: 'Y轴下限超出', |
|
|
|
threshold: `±${Math.abs(limits.yLower)}`, |
|
|
|
exceedValue: (limits.yLower - yReal).toFixed(2) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
return alarmSensors |
|
|
|
} |
|
|
|
|
|
|
|
// 读取参数函数 |
|
|
|
const handleReadParam = async () => { |
|
|
|
if (!selectedParam) { |
|
|
@ -310,7 +495,7 @@ function SystemSettings() { |
|
|
|
|
|
|
|
if (result.success) { |
|
|
|
setParamValue(result.data.values || '') |
|
|
|
console.log(result.data, eventName) |
|
|
|
// console.log(result.data, eventName) |
|
|
|
message.success(`${selectedParam} 读取成功!`) |
|
|
|
} else { |
|
|
|
message.error(`读取失败:${result.error}`) |
|
|
|