// src/websocket/socket.ts // 基础消息接口 interface BaseMessage { messageType: 'Report' // 消息类型 dataType: string // 数据类型 timestamp: number // 时间戳 } // 耗材状态消息 interface OptScanModuleStateMessage extends BaseMessage { type: 'OptScanModuleState' messageType: 'Report' dataType: 'OptScanModuleState' data: { state: 'EMPTY' | 'OCCUPIED' // 状态 isErrorPlate: boolean // 是否为错误板 bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型 sampleBarcode: string // 样本条码 userid: string // 用户ID projInfo: ProjectInfo // 项目信息 sampleId: string // 样本ID projId: number // 项目ID } timestamp: number } // 提示信息接口 interface PromptInfo { type: 'Warn' | 'Error' info: string detailInfos: string[] stackInfo: null } interface EventType { typeName: string timestamp: number prompt?: PromptInfo[] actionStep?: string actionStepName?: string } // 应用事件消息接口 interface AppEventMessage extends BaseMessage { type: 'AppEvent' messageType: 'Report' dataType: 'AppEvent' data: EventType timestamp: number } // 设备工作状态消息 interface DeviceWorkStateMessage extends BaseMessage { type: 'DeviceWorkState' messageType: 'Report' dataType: 'DeviceWorkState' data: { workState: 'IDLE' | 'RUNNING' | 'ERROR' | 'PAUSE' | 'STOP' // 设备工作状态 fatalErrorFlag: boolean // 致命错误标志 ecodeList: string[] // 错误代码列表 pending: boolean // 待处理状态 } timestamp: number // 时间戳 } // 急诊位状态消息 interface EmergencyPosStateMessage extends BaseMessage { type: 'EmergencyPosState' messageType: 'Report' dataType: 'EmergencyPosState' data: { tube: { sampleId: string | null // 样本ID pos: number // 位置 isHighTube: boolean // 是否为高试管 isEmergency: boolean // 是否为急诊 bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型 sampleBarcode: string // 样本条码 userid: string // 用户ID projInfo: ProjectInfo[] // 项目信息列表 projIds: number[] // 项目ID列表 state: string // 状态 errors: string[] // 错误信息列表 } } timestamp: number } // 试管信息接口 // interface Tube { // sampleId: string | null // 样本ID // pos: number // 位置 // isHighTube: boolean // 是否为高试管 // isEmergency: boolean // 是否为急诊 // bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型 // sampleBarcode: string // 样本条码 // userid: string // 用户ID // projInfo: ProjectInfo[] // 项目信息列表 // projIds: number[] // 项目ID列表 // state: 'EMPTY' | 'OCCUPIED' // 状态 // errors: string[] // 错误信息列表 // } // 试管架状态消息 interface TubeHolderStateMessage extends BaseMessage { type: 'TubeHolderState' messageType: 'Report' dataType: 'TubeHolderState' data: { tubeHolderType: string tubes: Array<{ sampleId: string | null pos: number isHighTube: boolean isEmergency: boolean bloodType: string sampleBarcode: string userid: string projInfo: any[] projIds: number[] state: string errors: string[] }> state: string } timestamp: number } // 传感器状态消息 interface SensorStateMessage extends BaseMessage { type: 'SensorState' messageType: 'Report' dataType: 'SensorState' data: { pboxTemperature: number // P盒温度 incubateBoxTemperature: number // 孵育盒温度 wasteBinFullFlag: boolean // 废物箱满标志 } timestamp: number } // 项目信息接口 interface ProjectInfo { projId: number projName: string projShortName: string color: string } // 子槽位信息接口 interface Subtank { pos: string // 位置编号 SPACE01-SPACE20 state: 'EMPTY' | 'OCCUPIED' // 槽位状态 bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型 sampleBarcode: string // 样本条码 userid: string // 用户ID projInfo: ProjectInfo // 项目信息 sampleId: string // 样本ID projId: number // 项目ID startIncubatedTime: number // 开始孵育时间 incubatedTimeSec: number // 孵育时间(秒) errors: string[] // 错误信息列表 isPlaceholder?: boolean // 是否为占位符 } // 孵育板状态消息 interface IncubationPlateStateMessage extends BaseMessage { type: 'IncubationPlateState' messageType: 'Report' dataType: 'IncubationPlateState' data: { subtanks: Subtank[] // 20个子槽位信息 } timestamp: number } // ��材组信息基础接口 interface ConsumableGroupBase { projId: number | null projName: string | null projShortName: string | null lotId: string color: string num: number } // 小缓冲液接口 interface LittleBottleGroup extends ConsumableGroupBase { type: string | null } // 大缓冲液接口 interface LargeBottleGroup extends ConsumableGroupBase { isInstall: boolean } // Tips信息接口 interface TipInfo { tipNum: number } // 耗材状态消息接口 interface ConsumablesStateMessage extends BaseMessage { type: 'ConsumablesState' messageType: 'Report' dataType: 'ConsumablesState' data: { scanDate: number tips: TipInfo[] reactionPlateGroup: ConsumableGroupBase[] littBottleGroup: LittleBottleGroup[] larBottleGroup: LargeBottleGroup[] } timestamp: number } // 消息类型联合 type WebSocketMessage = | OptScanModuleStateMessage | DeviceWorkStateMessage | EmergencyPosStateMessage | TubeHolderStateMessage | SensorStateMessage | IncubationPlateStateMessage | AppEventMessage | ConsumablesStateMessage // 消息处理器类型 type MessageHandler = (data: T['data']) => void class WebSocketClient { private ws: WebSocket | null = null private url: string private reconnectAttempts: number = 0 private maxReconnectAttempts: number = 5 private reconnectInterval: number = 3000 // 使用类型安全的消息处理器映射 private messageHandlers: Map< WebSocketMessage['dataType'], Set> > = new Map() constructor(url: string) { this.url = url } // 类型安全的订阅方法 subscribe( messageType: T['dataType'], handler: MessageHandler, ): void { if (!this.messageHandlers.has(messageType)) { console.log( '🚀 ~ WebSocketClient ~ subscribe ~ messageType:', messageType, ) this.messageHandlers.set(messageType, new Set()) } this.messageHandlers.get(messageType)?.add(handler) } // 类型安全的取消订阅方法 unsubscribe( messageType: T['dataType'], handler: MessageHandler, ): void { this.messageHandlers.get(messageType)?.delete(handler) } private handleMessage(message: WebSocketMessage): void { const handlers = this.messageHandlers.get(message.dataType) if (!handlers) { if (message.dataType == 'AppEvent') { console.log( '🚀 ~ WebSocketClient ~ handleMessage ~ handlers is undefined for message type:', message.dataType, ) } return } handlers.forEach((handler) => { try { handler(message.data) } catch (error) { console.error(`处理 ${message.dataType} 消息时出错:`, error) } }) } // 连接 WebSocket connect(): void { try { this.ws = new WebSocket(this.url) this.bindEvents() } catch (error) { console.error('WebSocket 连接失败:', error) this.reconnect() } } // 绑定事件 private bindEvents(): void { if (!this.ws) return // 连接建立时的处理 this.ws.onopen = () => { console.log('WebSocket 连接已建立') this.reconnectAttempts = 0 // 重置重连次数 } // 接收消息的处理 this.ws.onmessage = (event: MessageEvent) => { try { const data = JSON.parse(event.data) // console.log('🚀 ~ WebSocketClient ~ bindEvents ~ data:', data) this.handleMessage(data) } catch (error) { console.error('消息解析错误:', error) } } // 连接关闭时的处理 this.ws.onclose = () => { console.log('WebSocket 连接已关闭') this.reconnect() } // 错误处理 this.ws.onerror = (error) => { console.error('WebSocket 错误:', error) } } // 重连机制 private reconnect(): void { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('达到最大重连次数,停止重连') return } setTimeout(() => { console.log(`尝试第 ${this.reconnectAttempts + 1} 次重连...`) this.reconnectAttempts++ this.connect() }, this.reconnectInterval) } // 关闭连接 disconnect(): void { if (this.ws) { this.ws.close() this.ws = null } } } // 创建单例 // let wsInstance: WebSocketClient | null = null // 导出消息类型 export type { WebSocketMessage, OptScanModuleStateMessage, DeviceWorkStateMessage, EmergencyPosStateMessage, TubeHolderStateMessage, SensorStateMessage, IncubationPlateStateMessage, ProjectInfo, Subtank, AppEventMessage, ConsumablesStateMessage, ConsumableGroupBase, LittleBottleGroup, LargeBottleGroup, TipInfo, } // 导出 WebSocket 客户端 export const createWebSocket = (url: string): WebSocketClient => { // if (!wsInstance) { // wsInstance = new WebSocketClient(url) // } return new WebSocketClient(url) } // 使用示例: /* import { createWebSocket } from './websocket/socket'; // 创建 WebSocket 连接 const ws = createWebSocket('ws://your-websocket-server-url'); ws.connect(); // 在组件销毁时断开连接 onUnmounted(() => { ws.disconnect(); }); */