From 66bdc1df5caddb901544a6acbddb4ac9b473af19 Mon Sep 17 00:00:00 2001
From: cles <208023732@qq.com>
Date: Tue, 9 Sep 2025 16:09:54 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6?=
=?UTF-8?q?=E6=93=8D=E4=BD=9C=E7=9B=B8=E5=85=B3=E5=A4=84=E7=90=86=EF=BC=8C?=
=?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A1=AE=E4=BF=9D=E7=9B=AE=E5=BD=95=E5=AD=98?=
=?UTF-8?q?=E5=9C=A8=E3=80=81=E8=BF=BD=E5=8A=A0=E6=96=87=E4=BB=B6=E5=86=85?=
=?UTF-8?q?=E5=AE=B9=E3=80=81=E6=A3=80=E6=9F=A5=E6=96=87=E4=BB=B6=E5=AD=98?=
=?UTF-8?q?=E5=9C=A8=E6=80=A7=E5=8F=8A=E5=86=99=E5=85=A5CSV=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E5=A4=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 1 -
src/main/ipcRouter.js | 69 ++++++++
src/renderer/index.html | 24 +--
src/renderer/src/assets/base.css | 2 +-
.../MeasurementPointSetting.module.css | 4 -
.../SystemSettings/SystemSettings.jsx | 160 +++++++++++++++++-
6 files changed, 239 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 61da68a..4dd1ccb 100644
--- a/README.md
+++ b/README.md
@@ -338,4 +338,3 @@ chore: 构建或工具相关
3. 提交更改 (`git commit -m 'feat: add amazing feature'`)
4. 推送到分支 (`git push origin feature/amazing-feature`)
5. 创建 Pull Request
-
diff --git a/src/main/ipcRouter.js b/src/main/ipcRouter.js
index 12d13e5..06dc80a 100644
--- a/src/main/ipcRouter.js
+++ b/src/main/ipcRouter.js
@@ -2,6 +2,8 @@ import { ipcMain } from 'electron'
import { dialog, shell } from 'electron'
import dgram from 'dgram'
import net from 'net'
+import { appendFileSync, mkdirSync, existsSync } from 'fs'
+import { dirname } from 'path'
import { IPC_EVENT } from '../renderer/src/common/ipcEvents.js'
import log from 'electron-log'
import ReconnectManager from './reconnectManager.js'
@@ -191,6 +193,11 @@ export function registerIpRouter() {
// 存储目录相关处理
ipcMain.handle('open-directory', openDirectory)
ipcMain.handle('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)
}
// 搜索设备
const searchDevice = (event) => {
@@ -1483,3 +1490,65 @@ const selectDirectory = async (event, options) => {
return { success: false, error: error.message }
}
}
+
+// 确保目录存在
+const ensureDirectory = async (event, dirPath) => {
+ try {
+ if (!existsSync(dirPath)) {
+ mkdirSync(dirPath, { recursive: true })
+ log.info(`Created directory: ${dirPath}`)
+ }
+ return { success: true }
+ } catch (error) {
+ log.error('Failed to create directory:', error)
+ return { success: false, error: error.message }
+ }
+}
+
+// 追加内容到文件
+const appendToFile = async (event, filePath, content) => {
+ try {
+ // 确保父目录存在
+ const dir = dirname(filePath)
+ if (!existsSync(dir)) {
+ mkdirSync(dir, { recursive: true })
+ }
+
+ appendFileSync(filePath, content, 'utf8')
+ return { success: true }
+ } catch (error) {
+ log.error('Failed to append to file:', error)
+ return { success: false, error: error.message }
+ }
+}
+
+// 写入CSV文件头(包含BOM)
+const writeCSVHeader = async (event, filePath, content) => {
+ try {
+ // 确保父目录存在
+ const dir = dirname(filePath)
+ if (!existsSync(dir)) {
+ mkdirSync(dir, { recursive: true })
+ }
+
+ // 添加UTF-8 BOM以确保Excel正确显示中文
+ const bom = '\uFEFF'
+ appendFileSync(filePath, bom + content, 'utf8')
+ log.info(`Created CSV file with header: ${filePath}`)
+ return { success: true }
+ } catch (error) {
+ log.error('Failed to write CSV header:', error)
+ return { success: false, error: error.message }
+ }
+}
+
+// 检查文件是否存在
+const checkFileExists = async (event, filePath) => {
+ try {
+ const exists = existsSync(filePath)
+ return { success: true, exists }
+ } catch (error) {
+ log.error('Failed to check file existence:', error)
+ return { success: false, error: error.message }
+ }
+}
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 76fb425..511a3b8 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -1,16 +1,16 @@
+
+
+ FlexometerSetup is developed by FS
+
+
-
-
- FlexometerSetup is developed by FS
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/renderer/src/assets/base.css b/src/renderer/src/assets/base.css
index bd21df9..0529393 100644
--- a/src/renderer/src/assets/base.css
+++ b/src/renderer/src/assets/base.css
@@ -1,4 +1,4 @@
-:root{
+:root {
--bg-color: #ebe9e9;
--border-color: #c4c2c2;
}
diff --git a/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.module.css b/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.module.css
index 8593129..912527a 100644
--- a/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.module.css
+++ b/src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.module.css
@@ -39,12 +39,10 @@
.label {
text-align: right;
margin-right: 8px;
-
}
.input {
height: 24px;
-
}
.select {
@@ -66,8 +64,6 @@
flex-direction: column;
}
-
-
.actionButtons {
margin-top: 8px;
gap: 8px;
diff --git a/src/renderer/src/components/SystemSettings/SystemSettings.jsx b/src/renderer/src/components/SystemSettings/SystemSettings.jsx
index 7be0edb..cb4a968 100644
--- a/src/renderer/src/components/SystemSettings/SystemSettings.jsx
+++ b/src/renderer/src/components/SystemSettings/SystemSettings.jsx
@@ -8,7 +8,7 @@ import {
EyeOutlined,
EditOutlined
} from '@ant-design/icons'
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useCallback, useRef } from 'react'
import { IPC_EVENT } from '../../common/ipcEvents'
import useDeviceStore from '../../stores/deviceStore'
@@ -22,6 +22,10 @@ function SystemSettings() {
const [clearZeroLoading, setClearZeroLoading] = useState(false)
const [storagePath, setStoragePath] = useState('')
+ // 新增:实时数据记录状态
+ const [realtimeDataEnabled, setRealtimeDataEnabled] = useState(false)
+ const [alarmDataEnabled, setAlarmDataEnabled] = useState(false)
+
// 获取设备连接状态和重连配置
const connectedDevice = useDeviceStore((state) => state.connectedDevice)
const reconnectEnabled = useDeviceStore((state) => state.reconnectEnabled)
@@ -35,6 +39,24 @@ function SystemSettings() {
const setAlarmEnabled = useDeviceStore((state) => state.setAlarmEnabled)
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(() => {
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 () => {
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 () => {
if (!selectedParam) {
@@ -522,10 +665,21 @@ function SystemSettings() {
placeholder="存储目录路径"
/>
-
+ setRealtimeDataEnabled(e.target.checked)}
+ >
实时数据
- 报警数据
+ setAlarmDataEnabled(e.target.checked)}
+ >
+ 报警数据
+
)