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.

399 lines
9.7 KiB

8 months ago
  1. // src/websocket/socket.ts
  2. // 基础消息接口
  3. interface BaseMessage {
  4. messageType: 'Report' // 消息类型
  5. dataType: string // 数据类型
  6. timestamp: number // 时间戳
  7. }
  8. // 耗材状态消息
  9. interface OptScanModuleStateMessage extends BaseMessage {
  10. type: 'OptScanModuleState'
  11. messageType: 'Report'
  12. dataType: 'OptScanModuleState'
  13. data: {
  14. state: 'EMPTY' | 'OCCUPIED' // 状态
  15. isErrorPlate: boolean // 是否为错误板
  16. bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
  17. sampleBarcode: string // 样本条码
  18. userid: string // 用户ID
  19. projInfo: ProjectInfo // 项目信息
  20. sampleId: string // 样本ID
  21. projId: number // 项目ID
  22. }
  23. timestamp: number
  24. }
  25. // 提示信息接口
  26. interface PromptInfo {
  27. type: 'Warn' | 'Error'
  28. info: string
  29. detailInfos: string[]
  30. stackInfo: null
  31. }
  32. interface EventType {
  33. typeName: string
  34. timestamp: number
  35. prompt?: PromptInfo[]
  36. actionStep?: string
  37. actionStepName?: string
  38. }
  39. // 应用事件消息接口
  40. interface AppEventMessage extends BaseMessage {
  41. type: 'AppEvent'
  42. messageType: 'Report'
  43. dataType: 'AppEvent'
  44. data: EventType
  45. timestamp: number
  46. }
  47. // 设备工作状态消息
  48. interface DeviceWorkStateMessage extends BaseMessage {
  49. type: 'DeviceWorkState'
  50. messageType: 'Report'
  51. dataType: 'DeviceWorkState'
  52. data: {
  53. workState: 'IDLE' | 'RUNNING' | 'ERROR' | 'PAUSE' | 'STOP' // 设备工作状态
  54. fatalErrorFlag: boolean // 致命错误标志
  55. ecodeList: string[] // 错误代码列表
  56. pending: boolean // 待处理状态
  57. }
  58. timestamp: number // 时间戳
  59. }
  60. // 急诊位状态消息
  61. interface EmergencyPosStateMessage extends BaseMessage {
  62. type: 'EmergencyPosState'
  63. messageType: 'Report'
  64. dataType: 'EmergencyPosState'
  65. data: {
  66. tube: {
  67. sampleId: string | null // 样本ID
  68. pos: number // 位置
  69. isHighTube: boolean // 是否为高试管
  70. isEmergency: boolean // 是否为急诊
  71. bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
  72. sampleBarcode: string // 样本条码
  73. userid: string // 用户ID
  74. projInfo: ProjectInfo[] // 项目信息列表
  75. projIds: number[] // 项目ID列表
  76. state: string // 状态
  77. errors: string[] // 错误信息列表
  78. }
  79. }
  80. timestamp: number
  81. }
  82. // 试管信息接口
  83. // interface Tube {
  84. // sampleId: string | null // 样本ID
  85. // pos: number // 位置
  86. // isHighTube: boolean // 是否为高试管
  87. // isEmergency: boolean // 是否为急诊
  88. // bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
  89. // sampleBarcode: string // 样本条码
  90. // userid: string // 用户ID
  91. // projInfo: ProjectInfo[] // 项目信息列表
  92. // projIds: number[] // 项目ID列表
  93. // state: 'EMPTY' | 'OCCUPIED' // 状态
  94. // errors: string[] // 错误信息列表
  95. // }
  96. // 试管架状态消息
  97. interface TubeHolderStateMessage extends BaseMessage {
  98. type: 'TubeHolderState'
  99. messageType: 'Report'
  100. dataType: 'TubeHolderState'
  101. data: {
  102. tubeHolderType: string
  103. tubes: Array<{
  104. sampleId: string | null
  105. pos: number
  106. isHighTube: boolean
  107. isEmergency: boolean
  108. bloodType: string
  109. sampleBarcode: string
  110. userid: string
  111. projInfo: any[]
  112. projIds: number[]
  113. state: string
  114. errors: string[]
  115. }>
  116. state: string
  117. }
  118. timestamp: number
  119. }
  120. // 传感器状态消息
  121. interface SensorStateMessage extends BaseMessage {
  122. type: 'SensorState'
  123. messageType: 'Report'
  124. dataType: 'SensorState'
  125. data: {
  126. pboxTemperature: number // P盒温度
  127. incubateBoxTemperature: number // 孵育盒温度
  128. wasteBinFullFlag: boolean // 废物箱满标志
  129. }
  130. timestamp: number
  131. }
  132. // 项目信息接口
  133. interface ProjectInfo {
  134. projId: number
  135. projName: string
  136. projShortName: string
  137. color: string
  138. }
  139. // 子槽位信息接口
  140. interface Subtank {
  141. pos: string // 位置编号 SPACE01-SPACE20
  142. state: 'EMPTY' | 'OCCUPIED' // 槽位状态
  143. bloodType: 'WHOLE_BLOOD' | 'SERUM_OR_PLASMA' // 血液类型
  144. sampleBarcode: string // 样本条码
  145. userid: string // 用户ID
  146. projInfo: ProjectInfo // 项目信息
  147. sampleId: string // 样本ID
  148. projId: number // 项目ID
  149. startIncubatedTime: number // 开始孵育时间
  150. incubatedTimeSec: number // 孵育时间(秒)
  151. errors: string[] // 错误信息列表
  152. isPlaceholder?: boolean // 是否为占位符
  153. }
  154. // 孵育板状态消息
  155. interface IncubationPlateStateMessage extends BaseMessage {
  156. type: 'IncubationPlateState'
  157. messageType: 'Report'
  158. dataType: 'IncubationPlateState'
  159. data: {
  160. subtanks: Subtank[] // 20个子槽位信息
  161. }
  162. timestamp: number
  163. }
  164. // ��材组信息基础接口
  165. interface ConsumableGroupBase {
  166. projId: number | null
  167. projName: string | null
  168. projShortName: string | null
  169. lotId: string
  170. color: string
  171. num: number
  172. }
  173. // 小缓冲液接口
  174. interface LittleBottleGroup extends ConsumableGroupBase {
  175. type: string | null
  176. }
  177. // 大缓冲液接口
  178. interface LargeBottleGroup extends ConsumableGroupBase {
  179. isUse: boolean
  180. }
  181. // Tips信息接口
  182. interface TipInfo {
  183. tipNum: number
  184. }
  185. // 耗材状态消息接口
  186. interface ConsumablesStateMessage extends BaseMessage {
  187. type: 'ConsumablesState'
  188. messageType: 'Report'
  189. dataType: 'ConsumablesState'
  190. data: {
  191. scanDate: number
  192. tips: TipInfo[]
  193. reactionPlateGroup: ConsumableGroupBase[]
  194. littBottleGroup: LittleBottleGroup[]
  195. larBottleGroup: LargeBottleGroup[]
  196. }
  197. timestamp: number
  198. }
  199. // 消息类型联合
  200. type WebSocketMessage =
  201. | OptScanModuleStateMessage
  202. | DeviceWorkStateMessage
  203. | EmergencyPosStateMessage
  204. | TubeHolderStateMessage
  205. | SensorStateMessage
  206. | IncubationPlateStateMessage
  207. | AppEventMessage
  208. | ConsumablesStateMessage
  209. // 消息处理器类型
  210. type MessageHandler<T extends WebSocketMessage> = (data: T['data']) => void
  211. class WebSocketClient {
  212. private ws: WebSocket | null = null
  213. private url: string
  214. private reconnectAttempts: number = 0
  215. private maxReconnectAttempts: number = 5
  216. private reconnectInterval: number = 3000
  217. // 使用类型安全的消息处理器映射
  218. private messageHandlers: Map<
  219. WebSocketMessage['dataType'],
  220. Set<MessageHandler<any>>
  221. > = new Map()
  222. constructor(url: string) {
  223. this.url = url
  224. }
  225. // 类型安全的订阅方法
  226. subscribe<T extends WebSocketMessage>(
  227. messageType: T['dataType'],
  228. handler: MessageHandler<T>,
  229. ): void {
  230. console.log('messageType==', messageType)
  231. if (!this.messageHandlers.has(messageType)) {
  232. console.log(
  233. '🚀 ~ WebSocketClient ~ subscribe ~ messageType:',
  234. messageType,
  235. )
  236. this.messageHandlers.set(messageType, new Set())
  237. }
  238. this.messageHandlers.get(messageType)?.add(handler)
  239. }
  240. // 类型安全的取消订阅方法
  241. unsubscribe<T extends WebSocketMessage>(
  242. messageType: T['dataType'],
  243. handler: MessageHandler<T>,
  244. ): void {
  245. this.messageHandlers.get(messageType)?.delete(handler)
  246. }
  247. private handleMessage(message: WebSocketMessage): void {
  248. const handlers = this.messageHandlers.get(message.dataType)
  249. if (!handlers) {
  250. if (message.dataType == 'AppEvent') {
  251. console.log(
  252. '🚀 ~ WebSocketClient ~ handleMessage ~ handlers is undefined for message type:',
  253. message.dataType,
  254. )
  255. }
  256. return
  257. }
  258. handlers.forEach((handler) => {
  259. try {
  260. handler(message.data)
  261. } catch (error) {
  262. console.error(`处理 ${message.dataType} 消息时出错:`, error)
  263. }
  264. })
  265. }
  266. // 连接 WebSocket
  267. connect(): void {
  268. try {
  269. this.ws = new WebSocket(this.url)
  270. this.bindEvents()
  271. } catch (error) {
  272. console.error('WebSocket 连接失败:', error)
  273. this.reconnect()
  274. }
  275. }
  276. // 绑定事件
  277. private bindEvents(): void {
  278. if (!this.ws) return
  279. // 连接建立时的处理
  280. this.ws.onopen = () => {
  281. console.log('WebSocket 连接已建立')
  282. this.reconnectAttempts = 0 // 重置重连次数
  283. }
  284. // 接收消息的处理
  285. this.ws.onmessage = (event: MessageEvent) => {
  286. try {
  287. const data = JSON.parse(event.data)
  288. // console.log('🚀 ~ WebSocketClient ~ bindEvents ~ data:', data)
  289. this.handleMessage(data)
  290. } catch (error) {
  291. console.error('消息解析错误:', error)
  292. }
  293. }
  294. // 连接关闭时的处理
  295. this.ws.onclose = () => {
  296. console.log('WebSocket 连接已关闭')
  297. this.reconnect()
  298. }
  299. // 错误处理
  300. this.ws.onerror = (error) => {
  301. console.error('WebSocket 错误:', error)
  302. }
  303. }
  304. // 重连机制
  305. private reconnect(): void {
  306. if (this.reconnectAttempts >= this.maxReconnectAttempts) {
  307. console.log('达到最大重连次数,停止重连')
  308. return
  309. }
  310. setTimeout(() => {
  311. console.log(`尝试第 ${this.reconnectAttempts + 1} 次重连...`)
  312. this.reconnectAttempts++
  313. this.connect()
  314. }, this.reconnectInterval)
  315. }
  316. // 关闭连接
  317. disconnect(): void {
  318. if (this.ws) {
  319. this.ws.close()
  320. this.ws = null
  321. }
  322. }
  323. }
  324. // 创建单例
  325. // let wsInstance: WebSocketClient | null = null
  326. // 导出消息类型
  327. export type {
  328. WebSocketMessage,
  329. OptScanModuleStateMessage,
  330. DeviceWorkStateMessage,
  331. EmergencyPosStateMessage,
  332. TubeHolderStateMessage,
  333. SensorStateMessage,
  334. IncubationPlateStateMessage,
  335. ProjectInfo,
  336. Subtank,
  337. AppEventMessage,
  338. ConsumablesStateMessage,
  339. ConsumableGroupBase,
  340. LittleBottleGroup,
  341. LargeBottleGroup,
  342. TipInfo,
  343. }
  344. // 导出 WebSocket 客户端
  345. export const createWebSocket = (url: string): WebSocketClient => {
  346. // if (!wsInstance) {
  347. // wsInstance = new WebSocketClient(url)
  348. // }
  349. return new WebSocketClient(url)
  350. }
  351. // 使用示例:
  352. /*
  353. import { createWebSocket } from './websocket/socket';
  354. // 创建 WebSocket 连接
  355. const ws = createWebSocket('ws://your-websocket-server-url');
  356. ws.connect();
  357. // 在组件销毁时断开连接
  358. onUnmounted(() => {
  359. ws.disconnect();
  360. });
  361. */