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

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' | 'SEUM_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' | 'SEUM_OR_PLASMA' // 血液类型
  72. sampleBarcode: string // 样本条码
  73. userid: string // 用户ID
  74. projInfo: ProjectInfo[] // 项目信息列表
  75. projIds: number[] // 项目ID列表
  76. state: 'EMPTY' | 'OCCUPIED' // 状态
  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' | 'SEUM_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: 'BloodTube' // 试管架类型
  103. tubes: Tube[] // 试管列表
  104. state: 'IDLE' | 'RUNNING' | 'ERROR' // 试管架状态
  105. }
  106. timestamp: number
  107. }
  108. // 传感器状态消息
  109. interface SensorStateMessage extends BaseMessage {
  110. type: 'SensorState'
  111. messageType: 'Report'
  112. dataType: 'SensorState'
  113. data: {
  114. pboxTemperature: number // P盒温度
  115. incubateBoxTemperature: number // 孵育盒温度
  116. wasteBinFullFlag: boolean // 废物箱满标志
  117. }
  118. timestamp: number
  119. }
  120. // 项目信息接口
  121. interface ProjectInfo {
  122. projId: number
  123. projName: string
  124. projShortName: string
  125. color: string
  126. }
  127. // 子槽位信息接口
  128. interface Subtank {
  129. pos: string // 位置编号 SPACE01-SPACE20
  130. state: 'EMPTY' | 'OCCUPIED' // 槽位状态
  131. bloodType: 'WHOLE_BLOOD' | 'SEUM_OR_PLASMA' // 血液类型
  132. sampleBarcode: string // 样本条码
  133. userid: string // 用户ID
  134. projInfo: ProjectInfo // 项目信息
  135. sampleId: string // 样本ID
  136. projId: number // 项目ID
  137. startIncubatedTime: number // 开始孵育时间
  138. incubatedTimeSec: number // 孵育时间(秒)
  139. errors: string[] // 错误信息列表
  140. }
  141. // 孵育板状态消息
  142. interface IncubationPlateStateMessage extends BaseMessage {
  143. type: 'IncubationPlateState'
  144. messageType: 'Report'
  145. dataType: 'IncubationPlateState'
  146. data: {
  147. subtanks: Subtank[] // 20个子槽位信息
  148. }
  149. timestamp: number
  150. }
  151. // 耗材组信息基础接口
  152. interface ConsumableGroupBase {
  153. projId: number | null
  154. projName: string | null
  155. projShortName: string | null
  156. lotId: string
  157. color: string
  158. num: number
  159. }
  160. // 小缓冲液接口
  161. interface LittleBottleGroup extends ConsumableGroupBase {
  162. type: string | null
  163. }
  164. // 大缓冲液接口
  165. interface LargeBottleGroup extends ConsumableGroupBase {
  166. isUse: boolean
  167. }
  168. // Tips信息接口
  169. interface TipInfo {
  170. tipNum: number
  171. }
  172. // 耗材状态消息接口
  173. interface ConsumablesStateMessage extends BaseMessage {
  174. type: 'ConsumablesState'
  175. messageType: 'Report'
  176. dataType: 'ConsumablesStateService'
  177. data: {
  178. scanDate: number
  179. tips: TipInfo[]
  180. reactionPlateGroup: ConsumableGroupBase[]
  181. littBottleGroup: LittleBottleGroup[]
  182. larBottleGroup: LargeBottleGroup[]
  183. }
  184. timestamp: number
  185. }
  186. // 消息类型联合
  187. type WebSocketMessage =
  188. | OptScanModuleStateMessage
  189. | DeviceWorkStateMessage
  190. | EmergencyPosStateMessage
  191. | TubeHolderStateMessage
  192. | SensorStateMessage
  193. | IncubationPlateStateMessage
  194. | AppEventMessage
  195. | ConsumablesStateMessage
  196. // 消息处理器类型
  197. type MessageHandler<T extends WebSocketMessage> = (data: T['data']) => void
  198. class WebSocketClient {
  199. private ws: WebSocket | null = null
  200. private url: string
  201. private reconnectAttempts: number = 0
  202. private maxReconnectAttempts: number = 5
  203. private reconnectInterval: number = 3000
  204. // 使用类型安全的消息处理器映射
  205. private messageHandlers: Map<
  206. WebSocketMessage['dataType'],
  207. Set<MessageHandler<any>>
  208. > = new Map()
  209. constructor(url: string) {
  210. this.url = url
  211. }
  212. // 类型安全的订阅方法
  213. subscribe<T extends WebSocketMessage>(
  214. messageType: T['dataType'],
  215. handler: MessageHandler<T>,
  216. ): void {
  217. if (!this.messageHandlers.has(messageType)) {
  218. this.messageHandlers.set(messageType, new Set())
  219. }
  220. this.messageHandlers.get(messageType)?.add(handler)
  221. }
  222. // 类型安全的取消订阅方法
  223. unsubscribe<T extends WebSocketMessage>(
  224. messageType: T['dataType'],
  225. handler: MessageHandler<T>,
  226. ): void {
  227. this.messageHandlers.get(messageType)?.delete(handler)
  228. }
  229. private handleMessage(message: WebSocketMessage): void {
  230. const handlers = this.messageHandlers.get(message.dataType)
  231. if (!handlers) {
  232. if (message.dataType == 'AppEvent') {
  233. console.log(
  234. '🚀 ~ WebSocketClient ~ handleMessage ~ handlers is undefined for message type:',
  235. message.dataType,
  236. )
  237. }
  238. return
  239. }
  240. handlers.forEach((handler) => {
  241. try {
  242. handler(message.data)
  243. } catch (error) {
  244. console.error(`处理 ${message.dataType} 消息时出错:`, error)
  245. }
  246. })
  247. }
  248. // 连接 WebSocket
  249. connect(): void {
  250. try {
  251. this.ws = new WebSocket(this.url)
  252. this.bindEvents()
  253. } catch (error) {
  254. console.error('WebSocket 连接失败:', error)
  255. this.reconnect()
  256. }
  257. }
  258. // 绑定事件
  259. private bindEvents(): void {
  260. if (!this.ws) return
  261. // 连接建立时的处理
  262. this.ws.onopen = () => {
  263. console.log('WebSocket 连接已建立')
  264. this.reconnectAttempts = 0 // 重置重连次数
  265. }
  266. // 接收消息的处理
  267. this.ws.onmessage = (event: MessageEvent) => {
  268. try {
  269. const data = JSON.parse(event.data)
  270. // console.log('🚀 ~ WebSocketClient ~ bindEvents ~ data:', data)
  271. this.handleMessage(data)
  272. } catch (error) {
  273. console.error('消息解析错误:', error)
  274. }
  275. }
  276. // 连接关闭时的处理
  277. this.ws.onclose = () => {
  278. console.log('WebSocket 连接已关闭')
  279. this.reconnect()
  280. }
  281. // 错误处理
  282. this.ws.onerror = (error) => {
  283. console.error('WebSocket 错误:', error)
  284. }
  285. }
  286. // 重连机制
  287. private reconnect(): void {
  288. if (this.reconnectAttempts >= this.maxReconnectAttempts) {
  289. console.log('达到最大重连次数,停止重连')
  290. return
  291. }
  292. setTimeout(() => {
  293. console.log(`尝试第 ${this.reconnectAttempts + 1} 次重连...`)
  294. this.reconnectAttempts++
  295. this.connect()
  296. }, this.reconnectInterval)
  297. }
  298. // 关闭连接
  299. disconnect(): void {
  300. if (this.ws) {
  301. this.ws.close()
  302. this.ws = null
  303. }
  304. }
  305. }
  306. // 创建单例
  307. // let wsInstance: WebSocketClient | null = null
  308. // 导出消息类型
  309. export type {
  310. WebSocketMessage,
  311. OptScanModuleStateMessage,
  312. DeviceWorkStateMessage,
  313. EmergencyPosStateMessage,
  314. TubeHolderStateMessage,
  315. SensorStateMessage,
  316. IncubationPlateStateMessage,
  317. ProjectInfo,
  318. Subtank,
  319. AppEventMessage,
  320. ConsumablesStateMessage,
  321. ConsumableGroupBase,
  322. LittleBottleGroup,
  323. LargeBottleGroup,
  324. TipInfo,
  325. }
  326. // 导出 WebSocket 客户端
  327. export const createWebSocket = (url: string): WebSocketClient => {
  328. // if (!wsInstance) {
  329. // wsInstance = new WebSocketClient(url)
  330. // }
  331. return new WebSocketClient(url)
  332. }
  333. // 使用示例:
  334. /*
  335. import { createWebSocket } from './websocket/socket';
  336. // 创建 WebSocket 连接
  337. const ws = createWebSocket('ws://your-websocket-server-url');
  338. ws.connect();
  339. // 在组件销毁时断开连接
  340. onUnmounted(() => {
  341. ws.disconnect();
  342. });
  343. */