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
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();
|
|
});
|
|
*/
|