// src/websocket/socket.ts import { eventBus } from "@/eventBus" // 基础消息接口 interface BaseMessage { messageType: 'Report' // 消息类型 dataType: string // 数据类型 timestamp: number // 时间戳 } export type OptScanModuleState = 'EMPTY' | 'PLATE_IS_READY' | 'SCANNING' | 'ERROR' // 耗材状态消息 interface OptScanModuleStateMessage extends BaseMessage { type: 'OptScanModuleState' messageType: 'Report' dataType: 'OptScanModuleState' data: { state: OptScanModuleState // 状态 isErrorPlate: boolean // 是否为错误板(标识当前光学模组中的反应板存在异常) bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型 sampleBarcode: string // 样本条码 userid: string // 用户ID projInfo: ProjectInfo // 项目信息 sampleId: string // 样本ID projId: number // 项目ID isEmergency: boolean // 是否急诊 } timestamp: number } export interface EventReport extends PromptAvailable { typeName: 'AppPromptEvent' timestamp: number eventId: string prompt: TextPrompt | FormsPrompt | TablePrompt } export interface IdCardReport { typeName: 'AppIDCardMountEvent' | 'AppIDCardUnmountEvent' projectInfo: IdCardPrompt } export interface PromptAvailable { eventId?: string prompt: EventReport['prompt'] } export type IdCardPrompt = { color: string, expiryDate: number, id: number, lotId: string, palteCode: number,//palteCode 字段,接口命名错了。应该是plateCode projId: number, projName: string, updateChipVersion: string, } export type TextPrompt = { messageLevel: MsgLevel title: string info: string detailInfoType: 'Text' detailInfo: string } export type FormsPrompt = { messageLevel: MsgLevel title: string info: string detailInfoType: 'Forms' detailInfo: { name: string; description: string }[] } export type TablePrompt = { messageLevel: MsgLevel title: string info: string detailInfoType: 'Table' detailInfo: { header: string[] types: string[] vars: TablePromptRecord[] } } export interface AlarmTextPrompt extends TextPrompt { triggeredStateName: string notTriggeredDisName: string flagType: string } export interface AlarmTablePrompt extends TablePrompt { triggeredStateName: string notTriggeredDisName: string flagType: string } export interface AlarmFormsPrompt extends FormsPrompt { triggeredStateName: string notTriggeredDisName: string flagType: string } export type TablePromptRecord = { messageLevel: MsgLevel val: string }[] // 应用事件消息接口 interface AppEventMessage extends BaseMessage { type: 'AppEvent' messageType: 'Report' dataType: 'AppEvent' data: EventReport | IdCardReport | ReactionRecordReport timestamp: number } export type MsgLevel = 'Info' | 'Warn' | 'Error' export type MsgItem = { time: number messageLevel: MsgLevel message: string } export interface FooterMessageState extends BaseMessage { type: 'MessageBoxState' messageType: 'Report' dataType: 'MessageBoxState' data: { topMessage: MsgItem messageBoxList: MsgItem[] } } export type BloodType = 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' | 'FECES' type WorkState = 'IDLE' | 'WORKING' | 'PAUSE' // 设备工作状态消息 interface DeviceWorkStateMessage extends BaseMessage { type: 'DeviceWorkState' messageType: 'Report' dataType: 'DeviceWorkState' data: { workState: WorkState // 设备工作状态 lastWorkState: WorkState fatalErrorFlag: boolean // 致命错误标志 ecodeList: string[] // 错误代码列表 pending: boolean // 待处理状态 consumeNotEnoughErrorFlag: boolean } timestamp: number // 时间戳 } export type LoginUser = { id: number account: string password: string usrRole: string isBuiltInUser: boolean } export interface DeviceContextStateMessage extends BaseMessage { type: 'DeviceContext' messageType: 'Report' dataType: 'DeviceContext' data: { loginFlag: boolean loginUser: LoginUser deviceInitedFlag: boolean } } export type EmergencyTubeState = | 'EMPTY' | 'TO_BE_PROCESSED' | 'PENDING' | 'RESOURCE_IS_READY' | 'PROCESSING' | 'PROCESS_COMPLETE' | 'ERROR' export const emergencyStateDesc = { EMPTY: '空', TO_BE_PROCESSED: '待处理', PENDING: '已挂起', RESOURCE_IS_READY: '已就绪', PROCESSING: '处理中', PROCESS_COMPLETE: '完成', ERROR: '出错', } export const emergencyStateColor = { EMPTY: '', TO_BE_PROCESSED: '#56ccab', PENDING: '#a793ec', RESOURCE_IS_READY: '#17b60d', PROCESSING: '#e35958', PROCESS_COMPLETE: '#773f16', ERROR: '#e93527', } // 急诊位状态消息 interface EmergencyPosStateMessage extends BaseMessage { type: 'EmergencyPosState' messageType: 'Report' dataType: 'EmergencyPosState' data: { tube: { sampleId: string | null // 样本ID pos: number // 位置 isHighTube: boolean // 是否为高试管 isEmergency: boolean // 是否为急诊 bloodType: BloodType // 血液类型 sampleBarcode: string // 样本条码 userid: string // 用户ID projInfo: ProjectInfo[] // 项目信息列表 projIds: number[] // 项目ID列表 state: EmergencyTubeState // 状态 errors: string[] // 错误信息列表 } } timestamp: number } export type RunningTubeState = | 'EMPTY' | 'TO_BE_PROCESSED' | 'PENDING' | 'RESOURCE_IS_READY' | 'PROCESSING' | 'PROCESSED' | 'PROCESS_COMPLETE' | 'ERROR' export type TubeHolderType = | 'BloodTube' | 'MiniTube' | 'MiniBlood' | 'BulletTube1P5' | 'BulletTube0P5' | 'StoolTestTube' // 试管架状态消息 interface TubeHolderStateMessage extends BaseMessage { type: 'TubeHolderState' messageType: 'Report' dataType: 'TubeHolderState' data: { tubeHolderType: TubeHolderType tubes: Array<{ sampleId: string | null pos: number isHighTube: boolean isEmergency: boolean bloodType: BloodType sampleBarcode: string userid: string projInfo: ProjectInfo[] projIds: number[] state: RunningTubeState errors: string[] erroInfo: string }> state: string } timestamp: number } export interface TubeSetting { tubeIndex: number userid: string sampleBarcode: string projId: number[] bloodType: BloodType } export interface TestTubeRack { uuid: string state: 'INACTIVE' | 'ACTIVE' | 'LOCKED' tubeSettings: TubeSetting[] } export interface TubeHolderSettingMessage extends BaseMessage { type: 'TubeHolderSetting' messageType: 'Report' dataType: 'TubeHolderSetting' data: TestTubeRack[] } // 传感器状态消息 interface SensorStateMessage extends BaseMessage { type: 'SensorState' messageType: 'Report' dataType: 'SensorState' data: { pboxTemperature: number //板夹仓温度 incubateBoxTemperature: number // 孵育盘温度 wasteBinFullFlag: boolean // 废废料仓状态 } timestamp: number } // 项目信息接口 interface ProjectInfo { projId: number projName: string projShortName: string color: string supportBloodTypes: string[] } export type SubTankState = | 'EMPTY' | 'RESERVED' | 'WAITING_FOR_DROP' | 'INCUBATING' | 'INCUBATION_COMPLETE' | 'ERROR' // 子槽位信息接口 export interface SubTank { pos: string // 位置编号 SPACE01-SPACE20 state: SubTankState // 槽位状态 bloodType: BloodType // 血液类型 sampleBarcode: string // 样本条码 lotId: string userid: string // 用户ID projInfo: ProjectInfo // 项目信息 sampleId: string // 样本ID projId: number // 项目ID startIncubatedTime: number // 开始孵育时间 incubatedTimeSec: number // 孵育时间(秒) remainTimeSec: number //剩余孵育时间(显示) errors: string[] // 错误信息列表 isEmergency: boolean //是否急诊位 errorInfo: SubTankError // isPlaceholder?: boolean // 是否为占位符 } interface DetailInfo { description: string name: string } interface SubTankError { detailInfo: DetailInfo[] detailInfoType: string info: string messageLevel: string title: string } // 孵育板状态消息 interface IncubationPlateStateMessage extends BaseMessage { type: 'IncubationPlateState' messageType: 'Report' dataType: 'IncubationPlateState' data: { subtanks: SubTank[] // 20个子槽位信息 } timestamp: number } // ��材组信息基础接口 export interface ConsumableGroupBase { projId: number projName: string projShortName: string lotId: string color: string num: number isInstall: boolean reserveNum: number group: string } export interface ReactionPlateGroup extends Partial {} // 小缓冲液接口 interface LittleBottleGroup extends Partial {} // 大缓冲液接口 interface LargeBottleGroup extends Partial {} // Tips信息接口 interface TipInfo { tipNum: number totalNum?: number } // 耗材状态消息接口 consumableStore interface ConsumablesStateMessage extends BaseMessage { type: 'ConsumablesState' messageType: 'Report' dataType: 'ConsumablesState' data: { tips: TipInfo[] reactionPlateGroup: ReactionPlateGroup[] littBottleGroup: LittleBottleGroup[] larBottleGroup: LargeBottleGroup[] } timestamp: number } // 消息类型联合 type WebSocketMessage = | OptScanModuleStateMessage | DeviceWorkStateMessage | DeviceContextStateMessage | EmergencyPosStateMessage | TubeHolderStateMessage | TubeHolderSettingMessage | SensorStateMessage | IncubationPlateStateMessage | AppEventMessage | FooterMessageState | ConsumablesStateMessage | AppFlagStateListMessage | PreReactionPosGroupStateMessage // 消息处理器类型 type MessageHandler = (data: T['data']) => void class WebSocketClient { private ws: WebSocket | null = null private url: string private reconnectAttempts: number = -1 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()) } console.log('messageType---', messageType) 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 === 'MessageBoxState') { // 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 { // WebSocket.CONNECTING (0) WebSocket.OPEN (1) if (this.ws && this.ws.readyState <= 1) { // 已连接 console.log(`${this.url} 正在连接或已连接,无需重复连接`) } else { 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 = () => { if (this.reconnectAttempts !== -1) { // 从断开连接 进入 恢复连接 this.reconnectAttempts = -1 eventBus.emit('socketClosed', -1) } console.log('WebSocket 连接已建立') this.reconnectAttempts = -1 // 重置重连次数 } // 接收消息的处理 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 连接已关闭') eventBus.emit('socketClosed', 0) this.reconnect() } // 错误处理 this.ws.onerror = (error) => { console.error('WebSocket 错误:', error) } } // 重连机制 private reconnect(): void { if (this.reconnectAttempts === -1) { this.reconnectAttempts = 0 } if (this.reconnectAttempts >= this.maxReconnectAttempts) { eventBus.emit('socketClosed', this.maxReconnectAttempts) console.log('达到最大重连次数,停止重连') this.reconnectAttempts = -1 return } setTimeout(() => { console.log(`尝试第 ${this.reconnectAttempts + 1} 次重连...`) this.reconnectAttempts++ this.connect() }, this.reconnectInterval) } // 关闭连接 disconnect(): void { if (this.ws) { this.ws.close() this.ws = null } } } const urlSocketMap = new Map() // 导出消息类型 // 导出消息类型 export type { WebSocketMessage, OptScanModuleStateMessage, DeviceWorkStateMessage, EmergencyPosStateMessage, TubeHolderStateMessage, SensorStateMessage, IncubationPlateStateMessage, ProjectInfo, AppEventMessage, ConsumablesStateMessage, LittleBottleGroup, LargeBottleGroup, TipInfo, AppFlagStateListMessage, PreReactionPosGroupStateMessage, } // 导出 WebSocket 客户端 export const createWebSocket = (url: string): WebSocketClient => { if (urlSocketMap.has(url)) { return urlSocketMap.get(url) } else { const client = new WebSocketClient(url) urlSocketMap.set(url, client) return client } } // 使用示例: /* import { createWebSocket } from './websocket/socket'; import { ProjectInfo } from '../types/Index/Emergency'; // 创建 WebSocket 连接 const ws = createWebSocket('ws://your-websocket-server-url'); ws.connect(); // 在组件销毁时断开连接 onUnmounted(() => { ws.disconnect(); }); */ export type WarningState = { state: boolean keyName: string cnName: string errorDetailInfo?: TextPrompt | FormsPrompt | TablePrompt errorPromptInfo: TextPrompt | FormsPrompt | TablePrompt triggeredStateName: string notTriggeredDisName: string flagType: string } export type AppFlagStateList = { hasErrorTriggerFlag: boolean hasWarningTriggerFlag: boolean states: WarningState[] } export type TankInfo = { state: string projId: string bloodType: string errorInfo?: ErrorPromptInfo } export type Table = { messageLevel: string, title: string, info: string, detailInfoType: string, detailInfo: TableDetailInfo } export type TableDetailInfo = { header: string[], types: string[], vars: vars[][] } type vars = { messageLevel: string val: string } type Forms = { name: string description: string } export type ErrorPromptInfo = { detailInfo: TableDetailInfo | Forms | string detailInfoType: string info: string messageLevel: string title: string state: string } interface AppFlagStateListMessage extends BaseMessage { type: 'AppFlagStateList' messageType: 'Report' dataType: 'AppFlagStateList' data: AppFlagStateList } interface SampleInfo { sampleId: string isHighTube: boolean isEmergency: boolean bloodType: string sampleBarcode: string userid: string } interface Grids { state: string consumableType?: string group: string posIndex: number startReactionTime: number reactionRemainingTime: number totalReactionTime: number sampleInfo: SampleInfo bindIncubatorPos?: string | null } interface ProjBriefInfo { projId: number projName: string projShortName: string color: string } export interface PreReactionPosGroupState { grids: Grids[] consumableType?: string group: string hasSomeGridInReacting: boolean hasSomeGridReactedCompleted: boolean isInstalled: boolean version: string projBriefInfo: ProjBriefInfo inReacting: boolean } export interface PreReactionPosGroupStateMap { [key: string]: PreReactionPosGroupState } interface PreReactionPosGroupStateMessage extends BaseMessage { type: 'PreReactionPosGroupState' messageType: 'Report' dataType: 'PreReactionPosGroupState' data: PreReactionPosGroupState } type reactionRecordItem = { id: string } export type ReactionRecordReport = { typeName: 'AppNewReactionRecordEvent', reactionRecord: reactionRecordItem } export type StatusDetailReport = TextPrompt | FormsPrompt | TablePrompt