A8000
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

381 lines
9.3 KiB

// 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' | 'SEUM_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' | 'SEUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo[] // 项目信息列表
projIds: number[] // 项目ID列表
state: 'EMPTY' | 'OCCUPIED' // 状态
errors: string[] // 错误信息列表
}
}
timestamp: number
}
// 试管信息接口
interface Tube {
sampleId: string | null // 样本ID
pos: number // 位置
isHighTube: boolean // 是否为高试管
isEmergency: boolean // 是否为急诊
bloodType: 'WHOLE_BLOOD' | 'SEUM_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: 'BloodTube' // 试管架类型
tubes: Tube[] // 试管列表
state: 'IDLE' | 'RUNNING' | 'ERROR' // 试管架状态
}
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' | 'SEUM_OR_PLASMA' // 血液类型
sampleBarcode: string // 样本条码
userid: string // 用户ID
projInfo: ProjectInfo // 项目信息
sampleId: string // 样本ID
projId: number // 项目ID
startIncubatedTime: number // 开始孵育时间
incubatedTimeSec: number // 孵育时间(秒)
errors: string[] // 错误信息列表
}
// 孵育板状态消息
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 {
isUse: boolean
}
// Tips信息接口
interface TipInfo {
tipNum: number
}
// 耗材状态消息接口
interface ConsumablesStateMessage extends BaseMessage {
type: 'ConsumablesState'
messageType: 'Report'
dataType: 'ConsumablesStateService'
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<T extends WebSocketMessage> = (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<MessageHandler<any>>
> = new Map()
constructor(url: string) {
this.url = url
}
// 类型安全的订阅方法
subscribe<T extends WebSocketMessage>(
messageType: T['dataType'],
handler: MessageHandler<T>,
): void {
if (!this.messageHandlers.has(messageType)) {
this.messageHandlers.set(messageType, new Set())
}
this.messageHandlers.get(messageType)?.add(handler)
}
// 类型安全的取消订阅方法
unsubscribe<T extends WebSocketMessage>(
messageType: T['dataType'],
handler: MessageHandler<T>,
): 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();
});
*/