Browse Source

feat:实现带有配置和状态管理的重连功能

- 添加了 ReconnectManager 类,用于处理重连逻辑和状态
- 引入了用于重连配置和状态更新的 IPC 事件
- 增强了设备连接处理,支持断开连接时自动重连
- 集成了带有可配置限制和启用选项的告警功能
- 更新了 UI 组件,以反映连接状态并根据设备连接性启用 / 禁用输入
- 实现了心跳检查,用于监控设备连接状态并在必要时触发重连
- 重构了现有代码,以适应新的重连功能并提高可维护性
master
cles 1 week ago
parent
commit
9c8bd39a5b
  1. 2
      .editorconfig
  2. 319
      src/main/ipcRouter.js
  3. 221
      src/main/reconnectManager.js
  4. 4
      src/renderer/src/common/ipcEvents.js
  5. 108
      src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx
  6. 40
      src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx
  7. 63
      src/renderer/src/components/SiderHeader/SiderHeader.jsx
  8. 126
      src/renderer/src/components/SystemSettings/SystemSettings.jsx
  9. 27
      src/renderer/src/stores/deviceStore.js

2
.editorconfig

@ -6,4 +6,4 @@ indent_style = space
indent_size = 2 indent_size = 2
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true

319
src/main/ipcRouter.js

@ -3,12 +3,160 @@ import dgram from 'dgram'
import net from 'net' import net from 'net'
import { IPC_EVENT } from '../renderer/src/common/ipcEvents.js' import { IPC_EVENT } from '../renderer/src/common/ipcEvents.js'
import log from 'electron-log' import log from 'electron-log'
import ReconnectManager from './reconnectManager.js'
const TIMEOUT = 10000 // 10秒超时 const TIMEOUT = 10000 // 10秒超时
const END_SEQUENCE = '\n\n' // 消息结束标志 const END_SEQUENCE = '\n\n' // 消息结束标志
// 全局保存所有TCP连接和相关信息 // 全局保存所有TCP连接和相关信息
const tcpClients = new Map() const tcpClients = new Map()
// 保存待处理的请求,用于关联响应 // 保存待处理的请求,用于关联响应
const pendingRequests = new Map() const pendingRequests = new Map()
// 重连配置和状态管理
const reconnectConfig = {
enabled: false,
interval: 5 // 默认5秒
}
// 创建重连管理器实例
const reconnectManager = new ReconnectManager()
// 配置重连管理器的事件处理
reconnectManager.on('attempt-reconnect', (ip, callback) => {
// 获取重连信息
const connectionData = reconnectManager.getReconnectStatus(ip)
if (!connectionData.config.enabled) {
callback(false, 'Reconnect disabled')
return
}
log.info(`Executing reconnect attempt for ${ip}`)
const client = new net.Socket()
// 设置连接超时为重连间隔的80%,确保在下次重连前完成
const reconnectInterval = reconnectManager.getConfig().interval * 1000
const connectionTimeout = Math.max(3000, Math.floor(reconnectInterval * 0.8))
client.setTimeout(connectionTimeout)
log.debug(
`Connection timeout set to ${connectionTimeout}ms (reconnect interval: ${reconnectInterval}ms)`
)
// 需要从某处获取端口和eventSender - 这里需要存储这些信息
const storedConnectionData = tcpClients.get(ip) || tcpConnectionData.get(ip)
if (!storedConnectionData) {
callback(false, 'Connection data not found')
return
}
const connectionInfo = {
client,
eventSender: storedConnectionData.eventSender,
ip: ip,
port: storedConnectionData.port
}
client.connect(Number(storedConnectionData.port), ip, () => {
log.info(`Reconnected successfully to ${ip}:${storedConnectionData.port}`)
// 连接成功,保存连接信息
tcpClients.set(ip, connectionInfo)
// 启动心跳检测
startHeartbeatCheck(ip)
// 通知渲染进程重连成功
storedConnectionData.eventSender.send(IPC_EVENT.DEVICE_CONNECT_REPLY, {
success: true,
ip: ip,
reconnected: true
})
callback(true)
})
// 设置数据处理
let buffer = ''
client.on('data', (data) => {
buffer += data.toString()
let index
while ((index = buffer.indexOf('\n')) !== -1) {
const line = buffer.slice(0, index)
buffer = buffer.slice(index + 1)
if (!line.trim()) continue
let msg
try {
msg = JSON.parse(line)
} catch (e) {
log.error('TCP reconnect data parse error:', e)
continue
}
if (!msg || !msg.command) continue
handleTcpResponse(ip, msg)
}
})
client.on('error', (err) => {
log.warn(`Reconnect attempt failed for ${ip}:`, err.message)
client.destroy()
callback(false, err)
})
client.on('timeout', () => {
log.warn(`Reconnect attempt timeout for ${ip}`)
client.destroy()
callback(false, new Error('Connection timeout'))
})
client.on('close', () => {
log.debug(`Reconnect attempt connection closed for ${ip}`)
// 连接关闭由心跳检测处理
})
})
// 处理重连状态更新
reconnectManager.on('reconnect-status', (status) => {
// 通知所有渲染进程重连状态变化
const storedConnectionData = tcpConnectionData.get(status.ip)
if (storedConnectionData && storedConnectionData.eventSender) {
log.info(
`Sending reconnect status update for ${status.ip}: ${status.isReconnecting ? 'reconnecting' : 'idle'}`
)
storedConnectionData.eventSender.send(IPC_EVENT.RECONNECT_STATUS, {
ip: status.ip,
status: status.isReconnecting ? 'reconnecting' : 'idle',
attempts: status.attempts,
maxAttempts: status.maxAttempts,
lastError: status.lastError
})
} else {
log.warn(
`No stored connection data found for ${status.ip}, cannot send reconnect status update`
)
// 如果找不到存储的连接数据,尝试从活动连接中获取
const activeConnection = tcpClients.get(status.ip)
if (activeConnection && activeConnection.eventSender) {
log.info(`Using active connection data to send reconnect status for ${status.ip}`)
activeConnection.eventSender.send(IPC_EVENT.RECONNECT_STATUS, {
ip: status.ip,
status: status.isReconnecting ? 'reconnecting' : 'idle',
attempts: status.attempts,
maxAttempts: status.maxAttempts,
lastError: status.lastError
})
}
}
})
// 存储连接数据以供重连使用
const tcpConnectionData = new Map() // 存储连接参数
// 心跳检测相关配置
const HEARTBEAT_INTERVAL = 5000 // 心跳检测间隔 5秒(设备每2秒发送,我们5秒检测一次)
const HEARTBEAT_TIMEOUT = 10000 // 心跳超时时间 10秒(设备每2秒发送,超过10秒没收到就认为断开)
const heartbeatTimers = new Map() // 保存每个IP的心跳检测定时器
const lastHeartbeatTime = new Map() // 保存每个IP的最后心跳时间
// 记录IPC命令的通用函数(事件类型自动拼接) // 记录IPC命令的通用函数(事件类型自动拼接)
const logIPCCommand = (ip, command) => { const logIPCCommand = (ip, command) => {
@ -20,6 +168,7 @@ export function registerIpRouter() {
ipcMain.on(IPC_EVENT.DEVICE_SEARCH, searchDevice) ipcMain.on(IPC_EVENT.DEVICE_SEARCH, searchDevice)
ipcMain.on(IPC_EVENT.DEVICE_CONNECT, connectDevice) ipcMain.on(IPC_EVENT.DEVICE_CONNECT, connectDevice)
ipcMain.on(IPC_EVENT.DEVICE_DISCONNECT, disconnectDevice) ipcMain.on(IPC_EVENT.DEVICE_DISCONNECT, disconnectDevice)
ipcMain.on(IPC_EVENT.RECONNECT_CONFIG, handleReconnectConfig)
ipcMain.handle(IPC_EVENT.SENSORS_GET, sensorLoad) // 获取传感器 ipcMain.handle(IPC_EVENT.SENSORS_GET, sensorLoad) // 获取传感器
ipcMain.handle(IPC_EVENT.SENSORS_SET, sensorSet) // 传感器设置处理 ipcMain.handle(IPC_EVENT.SENSORS_SET, sensorSet) // 传感器设置处理
ipcMain.handle(IPC_EVENT.IMAGE_SEND_TIME_GET, imageSendTimeGet) ipcMain.handle(IPC_EVENT.IMAGE_SEND_TIME_GET, imageSendTimeGet)
@ -48,17 +197,44 @@ const searchDevice = (event) => {
const BROADCAST_ADDR = '255.255.255.255' const BROADCAST_ADDR = '255.255.255.255'
const udpClient = dgram.createSocket('udp4') const udpClient = dgram.createSocket('udp4')
const UDP_SEARCH_TIMEOUT = 5000 // 5秒总超时时间
const UDP_RESPONSE_DELAY = 1000 // 收到响应后等1秒收集更多设备
let timer = null let timer = null
let globalTimer = null
const resultMap = new Map() const resultMap = new Map()
// 清理资源的函数
const cleanup = () => {
if (timer) clearTimeout(timer)
if (globalTimer) clearTimeout(globalTimer)
if (!udpClient.destroyed) {
udpClient.close()
}
}
// 发送结果并清理
const sendResultsAndCleanup = () => {
const results = Array.from(resultMap.values())
log.info(`UDP search completed, found ${results.length} devices`)
cleanup()
event.reply && event.reply(IPC_EVENT.DEVICE_SEARCH_REPLY, results)
}
// 设置全局超时 - 无论如何5秒后结束搜索
globalTimer = setTimeout(() => {
log.info('UDP search timeout - no devices found within timeout period')
sendResultsAndCleanup()
}, UDP_SEARCH_TIMEOUT)
udpClient.bind(() => { udpClient.bind(() => {
udpClient.setBroadcast(true) udpClient.setBroadcast(true)
udpClient.send(message, 0, message.length, PORT, BROADCAST_ADDR, (err) => { udpClient.send(message, 0, message.length, PORT, BROADCAST_ADDR, (err) => {
if (err) { if (err) {
log.error('UDP send failed', err) log.error('UDP send failed', err)
udpClient.close() sendResultsAndCleanup()
} else { } else {
log.info('UDP send successful') log.info('UDP broadcast sent, waiting for responses...')
} }
}) })
}) })
@ -69,21 +245,22 @@ const searchDevice = (event) => {
from: rinfo.address, from: rinfo.address,
data: msg.toString() data: msg.toString()
}) })
log.info(`UDP response from ${rinfo.address}:`, msg.toString())
} catch (e) { } catch (e) {
log.error('UDP message parse error', e) log.error('UDP message parse error', e)
} }
// 每次收到消息后,重置响应延迟定时器
if (timer) clearTimeout(timer) if (timer) clearTimeout(timer)
timer = setTimeout(() => { timer = setTimeout(() => {
udpClient.close() log.info('UDP response collection timeout reached')
log.info('UDP socket closed after timeout') sendResultsAndCleanup()
event.reply && event.reply(IPC_EVENT.DEVICE_SEARCH_REPLY, Array.from(resultMap.values())) }, UDP_RESPONSE_DELAY)
}, 1000)
}) })
udpClient.on('error', (err) => { udpClient.on('error', (err) => {
log.error('UDP error:', err) log.error('UDP error:', err)
udpClient.close() sendResultsAndCleanup()
event.reply && event.reply(IPC_EVENT.DEVICE_SEARCH_REPLY, Array.from(resultMap.values()))
}) })
} }
@ -111,6 +288,9 @@ const connectDevice = (event, { ip, port }) => {
client.connect(Number(port), ip, () => { client.connect(Number(port), ip, () => {
event.reply(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: true, ip }) event.reply(IPC_EVENT.DEVICE_CONNECT_REPLY, { success: true, ip })
tcpClients.set(ip, connectionInfo) tcpClients.set(ip, connectionInfo)
// 启动心跳检测
startHeartbeatCheck(ip)
}) })
let buffer = '' let buffer = ''
@ -152,8 +332,23 @@ const connectDevice = (event, { ip, port }) => {
client.on('close', () => { client.on('close', () => {
log.info(`TCP connection to ${ip} closed`) log.info(`TCP connection to ${ip} closed`)
tcpClients.delete(ip) const connectionInfo = tcpClients.get(ip)
clearPendingRequestsByIp(ip) if (connectionInfo) {
tcpClients.delete(ip)
clearPendingRequestsByIp(ip)
// 只有在非主动断开的情况下才启动重连
// 主动断开会先调用 disconnectDevice 函数
if (reconnectConfig.enabled) {
log.info(`Connection lost to ${ip}, starting reconnect...`)
// 保存连接数据供重连使用
tcpConnectionData.set(ip, {
port: port,
eventSender: connectionInfo.eventSender
})
reconnectManager.startReconnect(ip, 'Connection lost')
}
}
}) })
} }
@ -161,8 +356,13 @@ const connectDevice = (event, { ip, port }) => {
const disconnectDevice = (event, { ip }) => { const disconnectDevice = (event, { ip }) => {
const connectionInfo = tcpClients.get(ip) const connectionInfo = tcpClients.get(ip)
if (connectionInfo) { if (connectionInfo) {
// 停止重连和心跳检测
reconnectManager.stopReconnect(ip)
stopHeartbeatCheck(ip)
connectionInfo.client.destroy() connectionInfo.client.destroy()
tcpClients.delete(ip) tcpClients.delete(ip)
tcpConnectionData.delete(ip) // 清除连接数据
clearPendingRequestsByIp(ip) clearPendingRequestsByIp(ip)
event.reply(IPC_EVENT.DEVICE_DISCONNECT_REPLY, { success: true }) event.reply(IPC_EVENT.DEVICE_DISCONNECT_REPLY, { success: true })
} else { } else {
@ -382,7 +582,9 @@ const handleTcpResponse = async (ip, msg) => {
} }
break break
case IPC_EVENT.HEARTBEAT_REPLY: case IPC_EVENT.HEARTBEAT_REPLY:
// 心跳处理 // 更新心跳时间戳(设备每2秒发送心跳包)
lastHeartbeatTime.set(ip, Date.now())
// 心跳包不记录到日志,避免日志过多
break break
default: default:
@ -1150,3 +1352,98 @@ const clearPendingRequestsByIp = (ip) => {
const delay = () => { const delay = () => {
return new Promise((resolve) => setTimeout(resolve, 1000)) return new Promise((resolve) => setTimeout(resolve, 1000))
} }
// 处理重连配置
const handleReconnectConfig = (event, { enabled, interval }) => {
reconnectConfig.enabled = enabled
reconnectConfig.interval = Math.max(4, interval) // 最小4秒
// 更新重连管理器配置
reconnectManager.updateConfig({
enabled,
interval: reconnectConfig.interval
})
log.info('Reconnect config updated:', { enabled, interval: reconnectConfig.interval })
}
// 启动心跳检测
const startHeartbeatCheck = (ip) => {
// 清除之前的心跳检测定时器
stopHeartbeatCheck(ip)
// 初始化心跳时间
lastHeartbeatTime.set(ip, Date.now())
// 设置心跳检测定时器
const timer = setInterval(() => {
checkHeartbeat(ip)
}, HEARTBEAT_INTERVAL)
heartbeatTimers.set(ip, timer)
log.info(`Started heartbeat check for ${ip}`)
}
// 检查心跳超时
const checkHeartbeat = (ip) => {
const lastTime = lastHeartbeatTime.get(ip)
const currentTime = Date.now()
if (!lastTime) {
log.warn(`No heartbeat recorded for ${ip}`)
return
}
const timeSinceLastHeartbeat = currentTime - lastTime
if (timeSinceLastHeartbeat > HEARTBEAT_TIMEOUT) {
log.warn(
`Heartbeat timeout for ${ip}: ${timeSinceLastHeartbeat}ms since last heartbeat (timeout: ${HEARTBEAT_TIMEOUT}ms)`
)
// 心跳超时,主动断开连接并启动重连
const connectionInfo = tcpClients.get(ip)
if (connectionInfo) {
log.info(`Force disconnecting ${ip} due to heartbeat timeout`)
// 停止心跳检测
stopHeartbeatCheck(ip)
// 销毁连接
connectionInfo.client.destroy()
tcpClients.delete(ip)
clearPendingRequestsByIp(ip)
// 通知渲染进程连接断开
connectionInfo.eventSender.send(IPC_EVENT.DEVICE_DISCONNECT_REPLY, {
success: true,
reason: 'heartbeat_timeout'
})
// 启动重连(如果启用)
if (reconnectConfig.enabled) {
log.info(`Connection lost to ${ip} due to heartbeat timeout, starting reconnect...`)
// 保存连接数据供重连使用
tcpConnectionData.set(ip, {
port: connectionInfo.port,
eventSender: connectionInfo.eventSender
})
reconnectManager.startReconnect(ip, 'Heartbeat timeout')
}
}
} else {
// 心跳正常,记录调试信息(可选)
log.debug(`Heartbeat OK for ${ip}: ${timeSinceLastHeartbeat}ms since last heartbeat`)
}
}
// 停止心跳检测
const stopHeartbeatCheck = (ip) => {
const timer = heartbeatTimers.get(ip)
if (timer) {
clearInterval(timer)
heartbeatTimers.delete(ip)
}
lastHeartbeatTime.delete(ip)
log.info(`Stopped heartbeat check for ${ip}`)
}

221
src/main/reconnectManager.js

@ -0,0 +1,221 @@
import { EventEmitter } from 'events'
const log = require('electron-log')
class ReconnectManager extends EventEmitter {
constructor() {
super()
this.reconnectTimers = new Map() // IP -> timer对象
this.reconnectAttempts = new Map() // IP -> 当前重连次数
this.reconnectConfig = {
enabled: false,
interval: 5, // 秒
maxAttempts: 10
}
this.isReconnecting = new Map() // IP -> boolean,防止重复重连
}
// 更新重连配置
updateConfig(config) {
this.reconnectConfig = {
...this.reconnectConfig,
...config,
interval: Math.max(4, config.interval || this.reconnectConfig.interval) // 最小4秒
}
log.info('Reconnect config updated:', this.reconnectConfig)
}
// 启动重连
startReconnect(ip, reason = 'Connection lost') {
if (!this.reconnectConfig.enabled) {
log.info(`Reconnect disabled for ${ip}`)
return
}
// 防止重复启动重连
if (this.isReconnecting.get(ip)) {
log.warn(`Reconnect already in progress for ${ip}`)
return
}
this.isReconnecting.set(ip, true)
// 如果没有当前重连次数,初始化为0
if (!this.reconnectAttempts.has(ip)) {
this.reconnectAttempts.set(ip, 0)
}
log.info(`${reason}, starting reconnect for ${ip}...`)
// 发送重连状态更新
this.emit('reconnect-status', {
ip,
isReconnecting: true
})
// 设置重连定时器
this.scheduleNextAttempt(ip)
}
// 安排下次重连尝试
scheduleNextAttempt(ip) {
// 递增重连次数
const currentAttempts = this.reconnectAttempts.get(ip) || 0
const newAttempts = currentAttempts + 1
this.reconnectAttempts.set(ip, newAttempts)
if (newAttempts > this.reconnectConfig.maxAttempts) {
log.error(
`Max reconnect attempts reached for ${ip} (${newAttempts}/${this.reconnectConfig.maxAttempts})`
)
this.stopReconnect(ip, 'Max attempts reached')
return
}
log.info(
`Scheduling reconnect attempt ${newAttempts}/${this.reconnectConfig.maxAttempts} for ${ip}`
)
const intervalMs = this.reconnectConfig.interval * 1000
log.info(`Reconnect timer set for ${ip} in ${this.reconnectConfig.interval}s (${intervalMs}ms)`)
const timer = setTimeout(() => {
log.info(`Reconnect timer fired for ${ip}, executing attempt ${newAttempts}`)
this.executeReconnectAttempt(ip)
}, intervalMs)
// 清除之前的定时器
this.clearTimer(ip)
this.reconnectTimers.set(ip, timer)
}
// 执行重连尝试
executeReconnectAttempt(ip) {
const attempts = this.reconnectAttempts.get(ip) || 0
log.info(
`Executing reconnect attempt ${attempts}/${this.reconnectConfig.maxAttempts} for ${ip}`
)
// 发出重连请求事件
this.emit('attempt-reconnect', ip, (success, error) => {
if (success) {
log.info(`Reconnect attempt ${attempts} successful for ${ip}`)
this.onReconnectSuccess(ip)
} else {
log.warn(`Reconnect attempt ${attempts} failed for ${ip}: ${error?.message || error}`)
this.onReconnectFailure(ip, error)
}
})
}
// 重连成功处理
onReconnectSuccess(ip) {
const attempts = this.reconnectAttempts.get(ip) || 0
log.info(`Reconnect attempt ${attempts} successful for ${ip}, clearing reconnect state`)
// 停止重连
this.stopReconnect(ip, 'Reconnect successful')
}
// 重连失败处理
onReconnectFailure(ip, error) {
const attempts = this.reconnectAttempts.get(ip) || 0
log.warn(
`Reconnect attempt ${attempts} failed for ${ip}, scheduling next attempt in ${this.reconnectConfig.interval}s`,
error
)
// 发送重连状态更新
this.emit('reconnect-status', {
ip,
isReconnecting: true
})
// 安排下次重连
this.scheduleNextAttempt(ip)
}
// 停止重连
stopReconnect(ip, reason = 'Manual stop') {
log.info(`Stopping reconnect for ${ip}: ${reason}`)
this.clearTimer(ip)
this.reconnectAttempts.delete(ip)
this.isReconnecting.set(ip, false)
// 发送重连状态更新
this.emit('reconnect-status', {
ip,
isReconnecting: false
})
}
// 清除重连定时器
clearTimer(ip) {
const timer = this.reconnectTimers.get(ip)
if (timer) {
clearTimeout(timer)
this.reconnectTimers.delete(ip)
log.debug(`Cleared reconnect timer for ${ip}`)
}
}
// 重置重连计数
resetAttempts(ip) {
this.reconnectAttempts.set(ip, 0)
log.debug(`Reset reconnect attempts for ${ip}`)
}
// 检查是否正在重连
isReconnectInProgress(ip) {
return this.isReconnecting.get(ip) || false
}
// 获取重连状态
getReconnectStatus(ip) {
return {
isReconnecting: this.isReconnecting.get(ip) || false,
attempts: this.reconnectAttempts.get(ip) || 0,
maxAttempts: this.reconnectConfig.maxAttempts,
config: this.reconnectConfig
}
}
// 获取所有重连状态
getAllReconnectStatus() {
const status = {}
for (const [ip] of this.isReconnecting) {
status[ip] = this.getReconnectStatus(ip)
}
return status
}
// 清理指定IP的所有重连状态
cleanup(ip) {
log.info(`Cleaning up reconnect state for ${ip}`)
this.clearTimer(ip)
this.reconnectAttempts.delete(ip)
this.isReconnecting.delete(ip)
}
// 清理所有重连状态
cleanupAll() {
log.info('Cleaning up all reconnect states')
// 清除所有定时器
for (const [ip] of this.reconnectTimers) {
this.clearTimer(ip)
}
// 清除所有状态
this.reconnectAttempts.clear()
this.isReconnecting.clear()
}
// 获取配置
getConfig() {
return { ...this.reconnectConfig }
}
}
export default ReconnectManager

4
src/renderer/src/common/ipcEvents.js

@ -33,6 +33,10 @@ export const IPC_EVENT = {
THRESHOLD_SET: 'threshold:set', THRESHOLD_SET: 'threshold:set',
INVALID_DATA_COUNT_SET: 'invalidDataCount:set', INVALID_DATA_COUNT_SET: 'invalidDataCount:set',
// 重连相关
RECONNECT_CONFIG: 'reconnect:config',
RECONNECT_STATUS: 'reconnect:status',
// 自动更新相关的value不要乱改,定义在这里是为了方便,这些都是标准的api事件名 // 自动更新相关的value不要乱改,定义在这里是为了方便,这些都是标准的api事件名
UPDATE_AVAILABLE: 'update-available', UPDATE_AVAILABLE: 'update-available',
UPDATE_NOT_AVAILABLE: 'update-not-available', UPDATE_NOT_AVAILABLE: 'update-not-available',

108
src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx

@ -22,6 +22,8 @@ function DeflectionCollection() {
// //
const connectedDevice = useDeviceStore((state) => state.connectedDevice) const connectedDevice = useDeviceStore((state) => state.connectedDevice)
const alarmEnabled = useDeviceStore((state) => state.alarmEnabled)
const alarmLimits = useDeviceStore((state) => state.alarmLimits)
// //
const targetColors = [ const targetColors = [
@ -96,35 +98,93 @@ function DeflectionCollection() {
// Y // Y
const dataY = { const dataY = {
labels: timeLabels, labels: timeLabels,
datasets: allSensors.map((sensor) => ({ datasets: [
label: `测点${sensor.pos}`, // 线
data: sensorDataHistory.map((dataPoint) => { ...allSensors.map((sensor) => ({
const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos) label: `测点${sensor.pos}`,
return sensorData ? Number(sensorData.yReal) : null data: sensorDataHistory.map((dataPoint) => {
}), const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos)
borderColor: sensor.color, return sensorData ? Number(sensorData.yReal) : null
backgroundColor: sensor.color.replace('1)', '0.2)'), }),
tension: 0, borderColor: sensor.color,
pointRadius: 3, backgroundColor: sensor.color.replace('1)', '0.2)'),
pointHoverRadius: 5 tension: 0,
})) pointRadius: 3,
pointHoverRadius: 5,
borderDash: [] // 线
})),
// 线
...(alarmEnabled
? [
{
label: 'Y轴上限',
data: timeLabels.map(() => alarmLimits.yUpper),
borderColor: 'rgba(255, 0, 0, 0.8)',
backgroundColor: 'rgba(255, 0, 0, 0.1)',
borderDash: [5, 5], // 线
pointRadius: 0,
tension: 0,
fill: false
},
{
label: 'Y轴下限',
data: timeLabels.map(() => alarmLimits.yLower),
borderColor: 'rgba(255, 0, 0, 0.8)',
backgroundColor: 'rgba(255, 0, 0, 0.1)',
borderDash: [5, 5], // 线
pointRadius: 0,
tension: 0,
fill: false
}
]
: [])
]
} }
// X // X
const dataX = { const dataX = {
labels: timeLabels, labels: timeLabels,
datasets: allSensors.map((sensor) => ({ datasets: [
label: `测点${sensor.pos}`, // 线
data: sensorDataHistory.map((dataPoint) => { ...allSensors.map((sensor) => ({
const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos) label: `测点${sensor.pos}`,
return sensorData ? Number(sensorData.xReal) : null data: sensorDataHistory.map((dataPoint) => {
}), const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos)
borderColor: sensor.color, return sensorData ? Number(sensorData.xReal) : null
backgroundColor: sensor.color.replace('1)', '0.2)'), }),
tension: 0, borderColor: sensor.color,
pointRadius: 3, backgroundColor: sensor.color.replace('1)', '0.2)'),
pointHoverRadius: 5 tension: 0,
})) pointRadius: 3,
pointHoverRadius: 5,
borderDash: [] // 线
})),
// 线
...(alarmEnabled
? [
{
label: 'X轴上限',
data: timeLabels.map(() => alarmLimits.xUpper),
borderColor: 'rgba(255, 0, 0, 0.8)',
backgroundColor: 'rgba(255, 0, 0, 0.1)',
borderDash: [5, 5], // 线
pointRadius: 0,
tension: 0,
fill: false
},
{
label: 'X轴下限',
data: timeLabels.map(() => alarmLimits.xLower),
borderColor: 'rgba(255, 0, 0, 0.8)',
backgroundColor: 'rgba(255, 0, 0, 0.1)',
borderDash: [5, 5], // 线
pointRadius: 0,
tension: 0,
fill: false
}
]
: [])
]
} }
const options = { const options = {

40
src/renderer/src/components/MeasurementPointSetting/MeasurementPointSetting.jsx

@ -422,6 +422,7 @@ function MeasurementPointSetting() {
value={formData.location} value={formData.location}
onChange={(value) => handleFormChange('location', value)} onChange={(value) => handleFormChange('location', value)}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
@ -432,6 +433,7 @@ function MeasurementPointSetting() {
value={formData.description} value={formData.description}
onChange={(e) => handleFormChange('description', e.target.value)} onChange={(e) => handleFormChange('description', e.target.value)}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
@ -453,6 +455,7 @@ function MeasurementPointSetting() {
value={formData.baseTarget} value={formData.baseTarget}
onChange={(value) => handleFormChange('baseTarget', value)} onChange={(value) => handleFormChange('baseTarget', value)}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
options={[ options={[
{ value: 'y', label: 'y' }, { value: 'y', label: 'y' },
{ value: 'n', label: 'n' } { value: 'n', label: 'n' }
@ -471,6 +474,7 @@ function MeasurementPointSetting() {
className={styles.numberInput} className={styles.numberInput}
value={rectangleData?.x || 0} value={rectangleData?.x || 0}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
<Flex className={styles.formRow} style={{ flex: 1 }}> <Flex className={styles.formRow} style={{ flex: 1 }}>
@ -479,6 +483,7 @@ function MeasurementPointSetting() {
className={styles.numberInput} className={styles.numberInput}
value={rectangleData?.width || 0} value={rectangleData?.width || 0}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
</Flex> </Flex>
@ -490,6 +495,7 @@ function MeasurementPointSetting() {
className={styles.numberInput} className={styles.numberInput}
value={rectangleData?.y || 0} value={rectangleData?.y || 0}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
<Flex className={styles.formRow} style={{ flex: 1 }}> <Flex className={styles.formRow} style={{ flex: 1 }}>
@ -498,6 +504,7 @@ function MeasurementPointSetting() {
className={styles.numberInput} className={styles.numberInput}
value={rectangleData?.height || 0} value={rectangleData?.height || 0}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
</Flex> </Flex>
@ -518,6 +525,7 @@ function MeasurementPointSetting() {
style={{ flex: 1 }} style={{ flex: 1 }}
status={!lensDistance || lensDistance <= 0 ? 'error' : ''} status={!lensDistance || lensDistance <= 0 ? 'error' : ''}
placeholder="请输入大于0的数值" placeholder="请输入大于0的数值"
disabled={!connectedDevice}
/> />
</Flex> </Flex>
@ -529,6 +537,7 @@ function MeasurementPointSetting() {
onChange={handleMeasureDistanceChange} onChange={handleMeasureDistanceChange}
precision={4} precision={4}
style={{ flex: 1 }} style={{ flex: 1 }}
disabled={!connectedDevice}
/> />
</Flex> </Flex>
</div> </div>
@ -551,6 +560,7 @@ function MeasurementPointSetting() {
type: 'radio', type: 'radio',
selectedRowKeys: selectedSensorKey ? [selectedSensorKey] : [], selectedRowKeys: selectedSensorKey ? [selectedSensorKey] : [],
onChange: (selectedRowKeys) => { onChange: (selectedRowKeys) => {
if (!connectedDevice) return
// //
const selectedKey = selectedRowKeys[0] const selectedKey = selectedRowKeys[0]
if (selectedKey && !selectedKey.includes('-')) { if (selectedKey && !selectedKey.includes('-')) {
@ -559,7 +569,7 @@ function MeasurementPointSetting() {
}, },
getCheckboxProps: (record) => ({ getCheckboxProps: (record) => ({
// //
disabled: record.key.includes('-') disabled: !connectedDevice || record.key.includes('-')
}), }),
hideSelectAll: true, hideSelectAll: true,
renderCell: (checked, record, index, originNode) => { renderCell: (checked, record, index, originNode) => {
@ -572,13 +582,14 @@ function MeasurementPointSetting() {
}} }}
onRow={(record) => ({ onRow={(record) => ({
onClick: () => { onClick: () => {
if (!connectedDevice) return
// //
if (!record.key.includes('-')) { if (!record.key.includes('-')) {
handleSensorSelect(record.key) handleSensorSelect(record.key)
} }
}, },
style: { style: {
cursor: record.key.includes('-') ? 'default' : 'pointer', cursor: !connectedDevice || record.key.includes('-') ? 'default' : 'pointer',
backgroundColor: selectedSensorKey === record.key ? '#e6f7ff' : 'transparent' backgroundColor: selectedSensorKey === record.key ? '#e6f7ff' : 'transparent'
} }
})} })}
@ -587,13 +598,28 @@ function MeasurementPointSetting() {
{/* 操作按钮 */} {/* 操作按钮 */}
<Flex className={styles.actionButtons} justify="center"> <Flex className={styles.actionButtons} justify="center">
<Tooltip title="添加传感器"> <Tooltip title="添加传感器">
<Button icon={<PlusOutlined />} shape="circle" onClick={handleAddSensor} /> <Button
icon={<PlusOutlined />}
shape="circle"
onClick={handleAddSensor}
disabled={!connectedDevice}
/>
</Tooltip> </Tooltip>
<Tooltip title="删除传感器"> <Tooltip title="删除传感器">
<Button icon={<MinusOutlined />} shape="circle" onClick={handleDel} /> <Button
icon={<MinusOutlined />}
shape="circle"
onClick={handleDel}
disabled={!connectedDevice}
/>
</Tooltip> </Tooltip>
<Tooltip title="清空列表"> <Tooltip title="清空列表">
<Button icon={<DeleteOutlined />} shape="circle" onClick={handleClear} /> <Button
icon={<DeleteOutlined />}
shape="circle"
onClick={handleClear}
disabled={!connectedDevice}
/>
</Tooltip> </Tooltip>
<Tooltip title="加载传感器"> <Tooltip title="加载传感器">
<Button <Button
@ -601,7 +627,7 @@ function MeasurementPointSetting() {
shape="circle" shape="circle"
onClick={handleLoad} onClick={handleLoad}
loading={loadingSensors} loading={loadingSensors}
disabled={loadingSensors || settingSensors} disabled={!connectedDevice || loadingSensors || settingSensors}
/> />
</Tooltip> </Tooltip>
<Tooltip title="设置传感器"> <Tooltip title="设置传感器">
@ -610,7 +636,7 @@ function MeasurementPointSetting() {
shape="circle" shape="circle"
onClick={handleSet} onClick={handleSet}
loading={settingSensors} loading={settingSensors}
disabled={loadingSensors || settingSensors} disabled={!connectedDevice || loadingSensors || settingSensors}
/> />
</Tooltip> </Tooltip>
</Flex> </Flex>

63
src/renderer/src/components/SiderHeader/SiderHeader.jsx

@ -16,6 +16,7 @@ import PropTypes from 'prop-types'
function SiderHeader({ showSystemSettings = true }) { function SiderHeader({ showSystemSettings = true }) {
const [searching, setSearching] = useState(false) const [searching, setSearching] = useState(false)
const [connecting, setConnecting] = useState(false)
const [deviceList, setDeviceList] = useState([]) const [deviceList, setDeviceList] = useState([])
const [selectedDevice, setSelectedDevice] = useState(undefined) const [selectedDevice, setSelectedDevice] = useState(undefined)
const [devicePort, setDevicePort] = useState('2230') const [devicePort, setDevicePort] = useState('2230')
@ -25,6 +26,9 @@ function SiderHeader({ showSystemSettings = true }) {
const setConnectedDevice = useDeviceStore((state) => state.setConnectedDevice) const setConnectedDevice = useDeviceStore((state) => state.setConnectedDevice)
const clearConnectedDevice = useDeviceStore((state) => state.clearConnectedDevice) const clearConnectedDevice = useDeviceStore((state) => state.clearConnectedDevice)
const setDeviceList_store = useDeviceStore((state) => state.setDeviceList) const setDeviceList_store = useDeviceStore((state) => state.setDeviceList)
const setReconnecting = useDeviceStore((state) => state.setReconnecting)
const incrementReconnectAttempts = useDeviceStore((state) => state.incrementReconnectAttempts)
const resetReconnectAttempts = useDeviceStore((state) => state.resetReconnectAttempts)
// //
useEffect(() => { useEffect(() => {
@ -52,6 +56,7 @@ function SiderHeader({ showSystemSettings = true }) {
// / // /
useEffect(() => { useEffect(() => {
const connectHandler = (event, result) => { const connectHandler = (event, result) => {
setConnecting(false) // loading
if (result.success) { if (result.success) {
setConnected(result.ip) setConnected(result.ip)
// store // store
@ -68,6 +73,7 @@ function SiderHeader({ showSystemSettings = true }) {
} }
} }
const disconnectHandler = (event, result) => { const disconnectHandler = (event, result) => {
setConnecting(false) // loading
setConnected(null) setConnected(null)
// store // store
clearConnectedDevice() clearConnectedDevice()
@ -88,6 +94,52 @@ function SiderHeader({ showSystemSettings = true }) {
} }
}, [devicePort, setConnectedDevice, clearConnectedDevice]) }, [devicePort, setConnectedDevice, clearConnectedDevice])
//
useEffect(() => {
const reconnectStatusHandler = (event, result) => {
const { ip, status, attempts } = result
switch (status) {
case 'reconnecting':
setReconnecting(true)
incrementReconnectAttempts()
message.info(`设备 ${ip} 连接断开,正在尝试重连... (${attempts}/10)`)
break
case 'connected':
setReconnecting(false)
resetReconnectAttempts()
setConnected(ip)
setConnectedDevice({
ip,
port: devicePort,
connectedAt: new Date().toISOString(),
reconnected: true
})
message.success(`设备 ${ip} 重连成功!`)
break
case 'failed':
setReconnecting(false)
resetReconnectAttempts()
message.error(`设备 ${ip} 重连失败,已达到最大重连次数`)
break
}
}
window.electron.ipcRenderer.on(IPC_EVENT.RECONNECT_STATUS, reconnectStatusHandler)
return () => {
window.electron.ipcRenderer.removeListener(IPC_EVENT.RECONNECT_STATUS, reconnectStatusHandler)
}
}, [
devicePort,
setConnectedDevice,
setReconnecting,
incrementReconnectAttempts,
resetReconnectAttempts
])
// //
const handleSearchDevice = () => { const handleSearchDevice = () => {
setSearching(true) setSearching(true)
@ -102,6 +154,7 @@ function SiderHeader({ showSystemSettings = true }) {
if (connected) { if (connected) {
// //
setConnecting(true) // loading
window.electron.ipcRenderer.send(IPC_EVENT.DEVICE_DISCONNECT, { ip: connected }) window.electron.ipcRenderer.send(IPC_EVENT.DEVICE_DISCONNECT, { ip: connected })
} else { } else {
// //
@ -119,6 +172,7 @@ function SiderHeader({ showSystemSettings = true }) {
} }
console.log('发送连接请求:', { ip: selectedDevice, port: devicePort }) console.log('发送连接请求:', { ip: selectedDevice, port: devicePort })
setConnecting(true) // loading
window.electron.ipcRenderer.send(IPC_EVENT.DEVICE_CONNECT, { window.electron.ipcRenderer.send(IPC_EVENT.DEVICE_CONNECT, {
ip: selectedDevice, ip: selectedDevice,
port: devicePort port: devicePort
@ -127,7 +181,7 @@ function SiderHeader({ showSystemSettings = true }) {
} }
// UI // UI
const uiDisabled = !!connected const uiDisabled = !!connected || connecting
return ( return (
<Flex vertical> <Flex vertical>
@ -187,9 +241,14 @@ function SiderHeader({ showSystemSettings = true }) {
icon={<ApiFilled />} icon={<ApiFilled />}
danger={!!connected} danger={!!connected}
type={connected ? 'primary' : 'default'} type={connected ? 'primary' : 'default'}
loading={connecting}
disabled={connecting}
onClick={handleConnectOrDisconnect} onClick={handleConnectOrDisconnect}
> >
{connected ? '断开连接' : '连接设备'} {connecting
? (connected ? '正在断开...' : '正在连接...')
: (connected ? '断开连接' : '连接设备')
}
</Button> </Button>
</Flex> </Flex>
</Flex> </Flex>

126
src/renderer/src/components/SystemSettings/SystemSettings.jsx

@ -8,21 +8,61 @@ import {
EyeOutlined, EyeOutlined,
EditOutlined EditOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { useState } from 'react' import { useState, useEffect } from 'react'
import { IPC_EVENT } from '../../common/ipcEvents' import { IPC_EVENT } from '../../common/ipcEvents'
import useDeviceStore from '../../stores/deviceStore' import useDeviceStore from '../../stores/deviceStore'
function SystemSettings() { function SystemSettings() {
// //
const [selectedParam, setSelectedParam] = useState('') const [selectedParam, setSelectedParam] = useState('imageSendTime')
const [paramValue, setParamValue] = useState('') const [paramValue, setParamValue] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [settingLoading, setSettingLoading] = useState(false) const [settingLoading, setSettingLoading] = useState(false)
const [imageControlLoading, setImageControlLoading] = useState(false) const [imageControlLoading, setImageControlLoading] = useState(false)
const [clearZeroLoading, setClearZeroLoading] = useState(false) const [clearZeroLoading, setClearZeroLoading] = useState(false)
// //
const connectedDevice = useDeviceStore((state) => state.connectedDevice) const connectedDevice = useDeviceStore((state) => state.connectedDevice)
const reconnectEnabled = useDeviceStore((state) => state.reconnectEnabled)
const reconnectInterval = useDeviceStore((state) => state.reconnectInterval)
const isReconnecting = useDeviceStore((state) => state.isReconnecting)
const alarmEnabled = useDeviceStore((state) => state.alarmEnabled)
const alarmLimits = useDeviceStore((state) => state.alarmLimits)
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)
//
useEffect(() => {
const reconnectStatusHandler = (event, result) => {
const { status } = result
console.log('Received reconnect status:', result)
switch (status) {
case 'reconnecting':
setReconnecting(true)
break
case 'idle':
case 'connected':
case 'failed':
setReconnecting(false)
break
default:
console.warn('Unknown reconnect status:', status)
break
}
}
window.electron.ipcRenderer.on(IPC_EVENT.RECONNECT_STATUS, reconnectStatusHandler)
return () => {
window.electron.ipcRenderer.removeListener(IPC_EVENT.RECONNECT_STATUS, reconnectStatusHandler)
}
}, [setReconnecting])
// //
const handleReadParam = async () => { const handleReadParam = async () => {
@ -222,6 +262,7 @@ function SystemSettings() {
placeholder="请选择基本参数" placeholder="请选择基本参数"
value={selectedParam} value={selectedParam}
onChange={setSelectedParam} onChange={setSelectedParam}
disabled={!connectedDevice}
options={[ options={[
{ value: 'imageSendTime', label: 'imageSendTime' }, { value: 'imageSendTime', label: 'imageSendTime' },
{ value: 'zeroCount', label: 'zeroCount' }, { value: 'zeroCount', label: 'zeroCount' },
@ -237,6 +278,7 @@ function SystemSettings() {
value={paramValue} value={paramValue}
onChange={(e) => setParamValue(e.target.value)} onChange={(e) => setParamValue(e.target.value)}
placeholder="参数值" placeholder="参数值"
disabled={!connectedDevice}
/> />
<Flex gap={12}> <Flex gap={12}>
<Button <Button
@ -300,44 +342,94 @@ function SystemSettings() {
{/* 重连功能 */} {/* 重连功能 */}
<div className={styles.subSection}> <div className={styles.subSection}>
<div className={styles.subSectionTitle}>重连功能</div> <div className={styles.subSectionTitle}>
<Checkbox className={styles.checkbox}>是否使能重连功能</Checkbox> 重连功能
{isReconnecting && <span style={{ color: '#1890ff', marginLeft: 8 }}>(重连中...)</span>}
</div>
<Checkbox
className={styles.checkbox}
checked={reconnectEnabled}
disabled={!connectedDevice}
onChange={(e) => {
setReconnectEnabled(e.target.checked)
//
window.electron.ipcRenderer.send(IPC_EVENT.RECONNECT_CONFIG, {
enabled: e.target.checked,
interval: reconnectInterval
})
}}
>
是否使能重连功能
</Checkbox>
<div className={styles.reconnectInterval}> <div className={styles.reconnectInterval}>
<InputNumber addonBefore={'重连间隔'} className={styles.inputNumber} /> <InputNumber
addonBefore={'重连间隔'}
className={styles.inputNumber}
value={reconnectInterval}
min={5}
max={60}
suffix="秒"
disabled={!connectedDevice}
onChange={(value) => {
if (value && value >= 4) {
setReconnectInterval(value)
//
window.electron.ipcRenderer.send(IPC_EVENT.RECONNECT_CONFIG, {
enabled: reconnectEnabled,
interval: value
})
}
}}
/>
</div> </div>
</div> </div>
{/* 报警功能 */} {/* 报警功能 */}
<div className={styles.subSection}> <div className={styles.subSection}>
<div className={styles.subSectionTitle}>报警功能</div> <div className={styles.subSectionTitle}>报警功能</div>
<Checkbox className={styles.checkboxLarge}>是否使能报警功能</Checkbox> <Checkbox
className={styles.checkboxLarge}
checked={alarmEnabled}
disabled={!connectedDevice}
onChange={(e) => setAlarmEnabled(e.target.checked)}
>
是否使能报警功能
</Checkbox>
<Flex gap="middle" className={styles.alarmInputs} vertical> <Flex gap="middle" className={styles.alarmInputs} vertical>
<InputNumber <InputNumber
precision={2} precision={2}
addonBefore={'X轴上限'} addonBefore={'X轴上限'}
className={styles.inputNumber} className={styles.inputNumber}
defaultValue="15.00" value={alarmLimits.xUpper}
disabled={!connectedDevice}
onChange={(value) => setAlarmLimits({ ...alarmLimits, xUpper: value || 15 })}
/> />
<InputNumber <InputNumber
precision={2} precision={2}
addonBefore={'X轴下限'} addonBefore={'X轴下限'}
className={styles.inputNumber} className={styles.inputNumber}
defaultValue="15.00" value={alarmLimits.xLower}
disabled={!connectedDevice}
onChange={(value) => setAlarmLimits({ ...alarmLimits, xLower: value || -15 })}
/> />
<InputNumber <InputNumber
precision={2} precision={2}
addonBefore={'Y轴上限'} addonBefore={'Y轴上限'}
className={styles.inputNumber} className={styles.inputNumber}
defaultValue="15.00" value={alarmLimits.yUpper}
disabled={!connectedDevice}
onChange={(value) => setAlarmLimits({ ...alarmLimits, yUpper: value || 15 })}
/> />
<InputNumber <InputNumber
precision={2} precision={2}
addonBefore={'Y轴下限'} addonBefore={'Y轴下限'}
className={styles.inputNumberLast} className={styles.inputNumberLast}
defaultValue="15.00" value={alarmLimits.yLower}
disabled={!connectedDevice}
onChange={(value) => setAlarmLimits({ ...alarmLimits, yLower: value || -15 })}
/> />
</Flex> </Flex>
</div> </div>
@ -352,16 +444,18 @@ function SystemSettings() {
存储目录 存储目录
</div> </div>
<div> <div>
<Button icon={<EyeOutlined />} type="text"></Button> <Button icon={<EyeOutlined />} type="text" disabled={!connectedDevice}></Button>
<Button icon={<EditOutlined />} type="text"></Button> <Button icon={<EditOutlined />} type="text" disabled={!connectedDevice}></Button>
</div> </div>
</Flex> </Flex>
<Flex align="center" gap={8} className={styles.storageInputRow}> <Flex align="center" gap={8} className={styles.storageInputRow}>
<Input className={styles.storageInput} /> <Input className={styles.storageInput} disabled={!connectedDevice} />
</Flex> </Flex>
<Checkbox className={styles.checkboxRight}>实时数据</Checkbox> <Checkbox className={styles.checkboxRight} disabled={!connectedDevice}>
<Checkbox>报警数据</Checkbox> 实时数据
</Checkbox>
<Checkbox disabled={!connectedDevice}>报警数据</Checkbox>
</div> </div>
</Flex> </Flex>
) )

27
src/renderer/src/stores/deviceStore.js

@ -10,6 +10,22 @@ const useDeviceStore = create((set) => ({
// 连接状态 // 连接状态
isConnecting: false, isConnecting: false,
// 重连配置
reconnectEnabled: false,
reconnectInterval: 5, // 默认5秒
// 重连状态
isReconnecting: false,
// 报警配置
alarmEnabled: false,
alarmLimits: {
xUpper: 15,
xLower: -15,
yUpper: 15,
yLower: -15
},
// 设置连接的设备 // 设置连接的设备
setConnectedDevice: (device) => set({ connectedDevice: device }), setConnectedDevice: (device) => set({ connectedDevice: device }),
@ -22,6 +38,17 @@ const useDeviceStore = create((set) => ({
// 设置连接状态 // 设置连接状态
setConnecting: (status) => set({ isConnecting: status }), setConnecting: (status) => set({ isConnecting: status }),
// 重连配置管理
setReconnectEnabled: (enabled) => set({ reconnectEnabled: enabled }),
setReconnectInterval: (interval) => set({ reconnectInterval: Math.max(4, interval) }), // 最小4秒
// 重连状态管理
setReconnecting: (status) => set({ isReconnecting: status }),
// 报警配置管理
setAlarmEnabled: (enabled) => set({ alarmEnabled: enabled }),
setAlarmLimits: (limits) => set({ alarmLimits: limits }),
// 获取当前连接的IP // 获取当前连接的IP
getConnectedIp: () => { getConnectedIp: () => {
const state = useDeviceStore.getState() const state = useDeviceStore.getState()

Loading…
Cancel
Save