forked from gzt/A8000
15 changed files with 576 additions and 171 deletions
-
4.env.development
-
2.env.production
-
4src/components/SimpleKeyboard.vue
-
204src/pages/Index/Index.vue
-
44src/pages/Index/Regular/Consumables.vue
-
25src/pages/Index/Regular/Emergency.vue
-
63src/pages/Index/Regular/Running.vue
-
3src/pages/Index/components/Consumables/MoveLiquidArea.vue
-
7src/pages/Index/components/Consumables/SpttingPlates.vue
-
2src/pages/Index/components/Running/LittleBufferDisplay.vue
-
8src/pages/Index/components/Running/PlateDisplay.vue
-
26src/pages/Index/components/Running/SampleDisplay.vue
-
5src/utils/axios.ts
-
22src/utils/getServerInfo.ts
-
326src/websocket/socket.ts
@ -1,3 +1,5 @@ |
|||
# 获取服务器信息 |
|||
VITE_USE_MOCK=true |
|||
# VITE_API_BASE_URL=http://localhost:5173 |
|||
VITE_API_BASE_URL=http://127.0.0.1:8080 |
|||
VITE_API_BASE_URL=http://127.0.0.1:8082 |
|||
VITE_WS_URL=ws://127.0.0.1:8082 |
@ -1,4 +1,4 @@ |
|||
VITE_USE_MOCK=false |
|||
# VITE_API_BASE_URL=http://localhost:5173 |
|||
VITE_API_BASE_URL=http://127.0.0.1:8080 |
|||
VITE_API_BASE_URL=http://127.0.0.1:8082 |
|||
# http://127.0.0.1:8081 |
@ -0,0 +1,22 @@ |
|||
export function getServerInfo() { |
|||
// 获取当前页面的 URL 对象
|
|||
const url = new URL(window.location.href) |
|||
|
|||
// 获取主机名(IP 或域名)和端口号
|
|||
const host = url.hostname // 例如: "192.168.1.100" 或 "localhost"
|
|||
const port = '8082' // 使用固定的后端端口
|
|||
|
|||
// 构建 WebSocket URL
|
|||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' |
|||
const wsUrl = `${wsProtocol}//${host}:${port}/api/v1/app/ws/state` |
|||
|
|||
// 构建 HTTP URL
|
|||
const httpUrl = `${window.location.protocol}//${host}:${port}` // 例如: "http://192.168.1.100:8082" 或 "http://localhost:8082"
|
|||
|
|||
return { |
|||
wsUrl, |
|||
httpUrl, |
|||
host, |
|||
port, |
|||
} |
|||
} |
@ -0,0 +1,326 @@ |
|||
// src/websocket/socket.ts
|
|||
|
|||
import { getServerInfo } from '../utils/getServerInfo' |
|||
const serverInfo = getServerInfo() |
|||
// 基础消息接口
|
|||
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 AppEventMessage extends BaseMessage { |
|||
type: 'AppEvent' |
|||
messageType: 'Report' |
|||
dataType: 'AppEvent' |
|||
data: { |
|||
typeName: 'AppPromptEvents' |
|||
timestamp: number |
|||
prompt: PromptInfo[] |
|||
} |
|||
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 |
|||
} |
|||
// 消息类型联合
|
|||
type WebSocketMessage = |
|||
| OptScanModuleStateMessage |
|||
| DeviceWorkStateMessage |
|||
| EmergencyPosStateMessage |
|||
| TubeHolderStateMessage |
|||
| SensorStateMessage |
|||
| IncubationPlateStateMessage |
|||
| AppEventMessage // 添加这一行
|
|||
|
|||
// 消息处理器类型
|
|||
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['type'], |
|||
Set<MessageHandler<any>> |
|||
> = new Map() |
|||
|
|||
constructor(url: string) { |
|||
this.url = url |
|||
} |
|||
|
|||
// 类型安全的订阅方法
|
|||
subscribe<T extends WebSocketMessage>( |
|||
messageType: T['type'], |
|||
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['type'], |
|||
handler: MessageHandler<T>, |
|||
): void { |
|||
this.messageHandlers.get(messageType)?.delete(handler) |
|||
} |
|||
|
|||
private handleMessage(message: WebSocketMessage): void { |
|||
const handlers = this.messageHandlers.get(message.type) |
|||
|
|||
if (handlers) { |
|||
handlers.forEach((handler) => { |
|||
try { |
|||
handler(message.data) |
|||
} catch (error) { |
|||
console.error(`处理 ${message.type} 消息时出错:`, error) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// 连接 WebSocket
|
|||
connect(): void { |
|||
try { |
|||
this.ws = new WebSocket(serverInfo.wsUrl) |
|||
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) |
|||
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, |
|||
} |
|||
|
|||
// 导出 WebSocket 客户端
|
|||
export const createWebSocket = (url: string): WebSocketClient => { |
|||
if (!wsInstance) { |
|||
wsInstance = new WebSocketClient(url) |
|||
} |
|||
return wsInstance |
|||
} |
|||
|
|||
// 使用示例:
|
|||
/* |
|||
import { createWebSocket } from './websocket/socket'; |
|||
|
|||
// 创建 WebSocket 连接
|
|||
const ws = createWebSocket('ws://your-websocket-server-url'); |
|||
ws.connect(); |
|||
|
|||
// 在组件销毁时断开连接
|
|||
onUnmounted(() => { |
|||
ws.disconnect(); |
|||
}); |
|||
*/ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue