diff --git a/package.json b/package.json index cde6f6e..681007e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "FlexometerSetup", - "version": "1.0.7", + "version": "1.0.8", "description": "An Electron application with React", "main": "./out/main/index.js", "author": "cles", diff --git a/src/main/ipcRouter.js b/src/main/ipcRouter.js index 06dc80a..44a33e9 100644 --- a/src/main/ipcRouter.js +++ b/src/main/ipcRouter.js @@ -191,13 +191,13 @@ export function registerIpRouter() { ipcMain.handle(IPC_EVENT.IMAGE_SEND_ENABLED, imageSendEnabledSet) ipcMain.handle(IPC_EVENT.IS_CLEAR_ZERO, isClearZeroSet) // 存储目录相关处理 - ipcMain.handle('open-directory', openDirectory) - ipcMain.handle('select-directory', selectDirectory) + ipcMain.handle(IPC_EVENT.OPEN_DIRECTORY, openDirectory) + ipcMain.handle(IPC_EVENT.SELECT_DIRECTORY, selectDirectory) // 文件操作相关处理 - ipcMain.handle('ensure-directory', ensureDirectory) - ipcMain.handle('append-to-file', appendToFile) - ipcMain.handle('check-file-exists', checkFileExists) - ipcMain.handle('write-csv-header', writeCSVHeader) + ipcMain.handle(IPC_EVENT.ENSURE_DIRECTORY, ensureDirectory) + ipcMain.handle(IPC_EVENT.APPEND_TO_FILE, appendToFile) + ipcMain.handle(IPC_EVENT.CHECK_FILE_EXISTS, checkFileExists) + ipcMain.handle(IPC_EVENT.WRITE_CSV_HEADER, writeCSVHeader) } // 搜索设备 const searchDevice = (event) => { diff --git a/src/renderer/src/common/ipcEvents.js b/src/renderer/src/common/ipcEvents.js index 1290c49..bdf3ef0 100644 --- a/src/renderer/src/common/ipcEvents.js +++ b/src/renderer/src/common/ipcEvents.js @@ -37,6 +37,14 @@ export const IPC_EVENT = { RECONNECT_CONFIG: 'reconnect:config', RECONNECT_STATUS: 'reconnect:status', + // 文件操作相关 + OPEN_DIRECTORY: 'file:open-directory', + SELECT_DIRECTORY: 'file:select-directory', + ENSURE_DIRECTORY: 'file:ensure-directory', + APPEND_TO_FILE: 'file:append-to-file', + CHECK_FILE_EXISTS: 'file:check-file-exists', + WRITE_CSV_HEADER: 'file:write-csv-header', + // 自动更新相关的value不要乱改,定义在这里是为了方便,这些都是标准的api事件名 UPDATE_AVAILABLE: 'update-available', UPDATE_NOT_AVAILABLE: 'update-not-available', diff --git a/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx b/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx index 4e93a1e..e36371c 100644 --- a/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx +++ b/src/renderer/src/components/AutoUpdater/AutoUpdater.jsx @@ -17,7 +17,7 @@ function AutoUpdater() { useEffect(() => { // 监听发现新版本 const handleUpdateAvailable = (event, info) => { - console.log('收到update-available事件:', info) + // console.log('收到update-available事件:', info) setUpdateInfo(info) setUpdateStatus('发现新版本') setIsModalVisible(true) @@ -28,14 +28,14 @@ function AutoUpdater() { // 监听下载进度 const handleDownloadProgress = (event, progressObj) => { - console.log('下载进度:', progressObj) + // console.log('下载进度:', progressObj) setDownloadProgress(Math.round(progressObj.percent)) setUpdateStatus(`正在下载新版本... ${Math.round(progressObj.percent)}%`) } // 监听下载完成 const handleUpdateDownloaded = () => { - console.log('下载完成') + // console.log('下载完成') setIsDownloading(false) setIsDownloaded(true) setUpdateStatus('新版本下载完成,准备安装') diff --git a/src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx b/src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx index 5faa048..3b9270c 100644 --- a/src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx +++ b/src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx @@ -40,7 +40,7 @@ function DeflectionCollection() { // 监听设备连接状态变化,设备连接时清空历史数据 useEffect(() => { if (connectedDevice) { - console.log('设备连接,清空历史数据:', connectedDevice) + // console.log('设备连接,清空历史数据:', connectedDevice) setSensorDataHistory([]) } }, [connectedDevice]) @@ -48,7 +48,7 @@ function DeflectionCollection() { 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)) { diff --git a/src/renderer/src/components/ImageCollection/ImageCollection.jsx b/src/renderer/src/components/ImageCollection/ImageCollection.jsx index a443cf2..6e047e4 100644 --- a/src/renderer/src/components/ImageCollection/ImageCollection.jsx +++ b/src/renderer/src/components/ImageCollection/ImageCollection.jsx @@ -22,7 +22,7 @@ function ImagePreview() { // 监听store中的矩形数据变化,转换为显示坐标 useEffect(() => { - console.log(rectangleData, 'rectangleData') + // console.log(rectangleData, 'rectangleData') if (rectangleData && image && imageScale) { // 将原始图片坐标转换为显示坐标 const displayRect = { @@ -109,16 +109,16 @@ function ImagePreview() { height: relativeRect.height / imageScale } - console.log('矩形绘制完成:') - console.log(' Stage坐标:', rectData) - console.log(' 相对于显示图片的坐标:', relativeRect) - console.log(' 相对于原始图片的坐标:', originalImageRect) - console.log( - `最终坐标: x=${Math.round(originalImageRect.x)}, y=${Math.round(originalImageRect.y)}` - ) - console.log( - `最终尺寸: width=${Math.round(originalImageRect.width)}, height=${Math.round(originalImageRect.height)}` - ) + // console.log('矩形绘制完成:') + // console.log(' Stage坐标:', rectData) + // console.log(' 相对于显示图片的坐标:', relativeRect) + // console.log(' 相对于原始图片的坐标:', originalImageRect) + // console.log( + // `最终坐标: x=${Math.round(originalImageRect.x)}, y=${Math.round(originalImageRect.y)}` + // ) + // console.log( + // `最终尺寸: width=${Math.round(originalImageRect.width)}, height=${Math.round(originalImageRect.height)}` + // ) // 将矩形数据保存到Zustand store const rectInfo = { diff --git a/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx b/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx index f0321dd..e058b81 100644 --- a/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx +++ b/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx @@ -233,10 +233,10 @@ function MeasurementPointSetting() { // 删除选中的传感器 setSensorList((prev) => prev.filter((sensor) => sensor.key !== selectedSensorKey)) setSelectedSensorKey(null) - console.log('删除传感器:', selectedSensorKey) + // console.log('删除传感器:', selectedSensorKey) }, onCancel: () => { - console.log('取消删除') + // console.log('取消删除') } }) } @@ -262,10 +262,10 @@ function MeasurementPointSetting() { // 清空传感器列表 setSensorList([]) setSelectedSensorKey(null) - console.log('清空传感器列表') + // console.log('清空传感器列表') }, onCancel: () => { - console.log('取消清空') + // console.log('取消清空') } }) } @@ -315,7 +315,7 @@ function MeasurementPointSetting() { }) if (result.success) { - console.log('传感器数据加载成功:', result.data) + // console.log('传感器数据加载成功:', result.data) // 检查返回的数据格式 if (result.data && result.data.values && Array.isArray(result.data.values)) { diff --git a/src/renderer/src/components/SiderHeader/SiderHeader.jsx b/src/renderer/src/components/SiderHeader/SiderHeader.jsx index 4943471..4b29dcb 100644 --- a/src/renderer/src/components/SiderHeader/SiderHeader.jsx +++ b/src/renderer/src/components/SiderHeader/SiderHeader.jsx @@ -150,7 +150,7 @@ function SiderHeader({ showSystemSettings = true }) { // 连接/断开设备 const handleConnectOrDisconnect = () => { - console.log('点击连接,当前selectedDevice:', selectedDevice, '端口:', devicePort) + // console.log('点击连接,当前selectedDevice:', selectedDevice, '端口:', devicePort) if (connected) { // 已连接,断开 @@ -171,7 +171,7 @@ function SiderHeader({ showSystemSettings = true }) { return } - console.log('发送连接请求:', { ip: selectedDevice, port: devicePort }) + // console.log('发送连接请求:', { ip: selectedDevice, port: devicePort }) setConnecting(true) // 开始 loading window.electron.ipcRenderer.send(IPC_EVENT.DEVICE_CONNECT, { ip: selectedDevice, diff --git a/src/renderer/src/components/SystemSettings/SystemSettings.jsx b/src/renderer/src/components/SystemSettings/SystemSettings.jsx index cb4a968..08f8870 100644 --- a/src/renderer/src/components/SystemSettings/SystemSettings.jsx +++ b/src/renderer/src/components/SystemSettings/SystemSettings.jsx @@ -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}`)