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.
 
 
 
 

733 lines
17 KiB

// 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<ConsumableGroupBase> {}
// 小缓冲液接口
interface LittleBottleGroup extends Partial<ConsumableGroupBase> {}
// 大缓冲液接口
interface LargeBottleGroup extends Partial<ConsumableGroupBase> {}
// 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<T extends WebSocketMessage> = (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<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)) {
console.log(
'🚀 ~ WebSocketClient ~ subscribe ~ messageType:',
messageType,
)
this.messageHandlers.set(messageType, new Set())
}
console.log('messageType---', messageType)
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 === '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