Browse Source

fix:兼容Websocket;修改用户管理部分的bug;

feature/history-20250108
gzt 8 months ago
parent
commit
76ec9c611b
  1. 54
      src/pages/Index/Index.vue
  2. 35
      src/pages/Index/Regular/Consumables.vue
  3. 8
      src/pages/Index/Regular/Emergency.vue
  4. 61
      src/pages/Index/Regular/TestTube.vue
  5. 163
      src/pages/Index/Settings/Users.vue
  6. 133
      src/pages/Index/TestTube/ChangeUser.vue
  7. 55
      src/pages/Index/components/Consumables/MoveLiquidArea.vue
  8. 48
      src/pages/Index/components/Consumables/ProjectSelector.vue
  9. 10
      src/pages/Index/components/Running/PlateDisplay.vue
  10. 60
      src/pages/Index/components/Setting/DelMessage.vue
  11. 14
      src/pages/Index/components/TestTube/ProjectSetting.vue
  12. 53
      src/pages/Index/components/TestTube/TestTubeRack.vue
  13. 5
      src/services/Index/Test-tube/test-tube.ts
  14. 34
      src/services/Index/emergency.ts
  15. 1
      src/services/Index/idCard.ts
  16. 9
      src/services/Index/user-manage.ts
  17. 5
      src/types/Index/Consumables.ts
  18. 4
      src/utils/getServerInfo.ts
  19. 111
      src/websocket/socket.ts

54
src/pages/Index/Index.vue

@ -85,9 +85,10 @@ import { ref, onMounted, onBeforeUnmount } from 'vue';
import { Time, InitWarn, LoadingModal } from './components/Consumables';
import { getCheckList, startWork, pauseWork, continueWork, stopWork, getInitState, initDevice, saveMountedCardInfo, openBuzzer, closeBuzzer } from '../../services/index';
import { CheckItem, User } from '../../types/Index';
// import { useConsumablesStore } from '../../store';
import { useConsumablesStore } from '../../store';
import { createWebSocket } from '../../websocket/socket';
import type { AppEventMessage } from '../../websocket/socket';
import { getServerInfo } from '../../utils/getServerInfo';
const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规');
const lineWidth = ref(0);
const lineLeft = ref(0);
@ -97,7 +98,7 @@ const showAlreadyModal = ref(false);
const showFailModal = ref(false);
const checkData = ref<CheckItem[]>([]);
const failItems = ref<CheckItem[]>([]);
// const consumableStore = useConsumablesStore();
const consumableStore = useConsumablesStore();
//
const user = ref<User>(JSON.parse(sessionStorage.getItem('token') || '{}') as unknown as User)
const isTesting = ref(false); //
@ -111,23 +112,16 @@ const showWarnModal = ref(false)
const ErrorMessage = ref<string>('')
const showErrorModal = ref(false)
const WarnMessage = ref<string>('')
// interface EventType {
// typeName: string,
// timestamp: number
// prompt?: {
// type: string,
// info: string,
// detailInfos: string[],
// stackInfo: null
// }
// actionStep?: string,
// actionStepName?: string
// }
// WebSocket
const ws = createWebSocket("/api/v1/app/ws/event");
const { wsUrl } = getServerInfo('/api/v1/app/ws/event')
const ws = createWebSocket(wsUrl)
console.log("🚀 ~ ws:", ws)
//
const handleAppEvent = (data: AppEventMessage['data']) => {
console.log('🚀 ~ handleAppEvent ~ data:', data)
if (data.typeName === 'AppPromptEvents' && data.prompt) {
data.prompt.forEach(async (item) => {
if (item.type === 'Error') {
@ -140,6 +134,21 @@ const handleAppEvent = (data: AppEventMessage['data']) => {
}
});
}
else if (data.typeName === 'AppIDCardMountEvent') {
consumableStore.isIdCardInserted = true;
idCardInserted.value = true;
EventText.value = "id卡已插入"
} else if (data.typeName === 'AppIDCardUnmountEvent') {
consumableStore.isIdCardInserted = false;
idCardInserted.value = false;
EventText.value = "id卡已拔出"
} else if (data.typeName === 'AppTubeholderSettingUpdateEvent') {
EventText.value = "试管架配置更新"
} else if (data.typeName === "DoA8kStepActionEvent") {
EventText.value = data.actionStepName!
} else {
EventText.value = "闲置..."
}
};
// const getEventText = (data: EventType | EventType[]): string => {
@ -209,6 +218,7 @@ const confirmError = async () => {
showErrorModal.value = false
//
await closeBuzzer()
EventText.value = "闲置..."
}
//
const confirmWarn = async () => {
@ -224,22 +234,14 @@ const saveIdInfo = async () => {
idCardInserted.value = false;
}
}
// 500ms
// const pollingInterval = 500;
// let pollingTimer: ReturnType<typeof setInterval>;
onMounted(() => {
//
// pollingTimer = setInterval(getEvent, pollingInterval);
ws.connect();
ws.subscribe<AppEventMessage>('AppEvent', handleAppEvent);
});
onBeforeUnmount(() => {
//
// if (pollingTimer) {
// clearInterval(pollingTimer);
// }
ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent);
ws.disconnect();
});
@ -319,7 +321,7 @@ const checkIfResetCompleted = async () => {
showAlreadyModal.value = true;
} else {
console.log("初始化失败")
await getCheckData(); //
await getCheckData(); //
const failedItems = checkData.value.filter(item => !item.pass);
if (failedItems.length > 0) {
const errorMessages = generateErrorMessages(failedItems);

35
src/pages/Index/Regular/Consumables.vue

@ -49,8 +49,11 @@ import {
Tube,
} from '../../../types/Index'
import { createWebSocket } from '../../../websocket/socket'
import type { SensorStateMessage } from '../../../websocket/socket';
const socket = createWebSocket("/api/v1/app/ws/state")
import type { ConsumablesStateMessage, SensorStateMessage } from '../../../websocket/socket';
import { getServerInfo } from '../../../utils/getServerInfo'
const { wsUrl } = getServerInfo('/api/v1/app/ws/state')
const socket = createWebSocket(wsUrl)
console.log("🚀 ~ socket:", socket)
const consumableStore = useConsumablesStore()
const emergencyStore = useEmergencyStore()
@ -80,6 +83,8 @@ const moveLiquids = ref<LiquidState[]>([
tipNum: 0,
},
])
//
const isAlreadyLoad = ref(false)
//
const tempTipNum = ref<number[]>([
...moveLiquids.value.map((liquid) => liquid.tipNum),
@ -90,6 +95,10 @@ interface BufferLittle {
num: number
projShortName: string
color: string
lotId?: string
type?: string
projId?: number
projName?: string
}
const bufferLittles = ref<BufferLittle[]>([
{
@ -145,10 +154,12 @@ const GetLoadConsumables = async () => {
tempTipNum.value = [...moveLiquids.value.map((liquid) => liquid.tipNum)]
isLoad.value = true
isLoading.value = false
isAlreadyLoad.value = true
consumableStore.setConsumablesData(res.data.consumableState)
} catch (error) {
//
isLoading.value = false
isAlreadyLoad.value = false
}
}
@ -166,13 +177,24 @@ const handleSensorState = (data: SensorStateMessage['data']) => {
// 使
currentTemperature.value = data.incubateBoxTemperature;
wasteStatus.value = data.wasteBinFullFlag
//
if (currentTemperature.value > 40) {
console.warn('温度过高警告');
//
}
};
//
const handleConsumablesState = (data: ConsumablesStateMessage['data']) => {
if (isAlreadyLoad.value) {
console.log('🚀 ~ handleConsumablesState ~ data:', data)
moveLiquids.value = data.tips
plates.value = data.reactionPlateGroup as ReactionPlate[]
bufferLittles.value = data.littBottleGroup as BufferLittle[]
bufferBig.value = data.larBottleGroup as BottleGroup[]
} else {
return
}
}
// 使线
const updatePlatesAndBuffers = ({
value,
@ -187,11 +209,11 @@ const updatePlatesAndBuffers = ({
if (bufferBig.value && bufferBig.value[index])
bufferBig.value[index].isUse = value > 0
}
onMounted(() => {
eventBus.on('confirm', updatePlatesAndBuffers)
startWebSocket()
socket.subscribe<SensorStateMessage>('SensorState', handleSensorState);
socket.subscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
getEmergencyInfo()
})
onBeforeUnmount(() => {
@ -201,6 +223,7 @@ onBeforeUnmount(() => {
socket.disconnect() //
}
socket.unsubscribe<SensorStateMessage>('SensorState', handleSensorState);
socket.unsubscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
})
//
onActivated(() => {
@ -216,7 +239,7 @@ const handleIsLoad = async () => {
//
const startTime = Date.now()
const duration = 30000 // 30
const duration = 3000 // 30
const updateProgress = () => {
const elapsed = Date.now() - startTime
@ -246,6 +269,8 @@ const handleIsLoad = async () => {
const handleIsUnload = () => {
isLoad.value = !isLoad.value
isLoading.value = false
isAlreadyLoad.value = false
socket.unsubscribe<ConsumablesStateMessage>('ConsumablesStateService', handleConsumablesState);
// moveLiquids tempTipNum
moveLiquids.value = [
{ id: 1, tipNum: 0 },

8
src/pages/Index/Regular/Emergency.vue

@ -306,8 +306,8 @@ const hideKeyboard = () => {
margin: 0;
padding: 0;
position: relative;
height: 100%;
width: 100%;
height: 1200px;
width: 800px;
background-color: #f4f6f9;
box-sizing: border-box;
@ -329,7 +329,7 @@ const hideKeyboard = () => {
.page-header {
width: 100%;
height: 100px;
height: 80px;
display: flex;
align-items: center;
background-color: #ffffff;
@ -744,7 +744,7 @@ const hideKeyboard = () => {
.emergency-controller {
width: 100%;
height: 120px;
height: 100px;
display: flex;
margin-top: 20px;

61
src/pages/Index/Regular/TestTube.vue

@ -2,9 +2,10 @@
<div id="configuration-container" v-loading="loading">
<!-- 渲染试管架列表 -->
<div class="tube-rack-list">
<div v-for="(tubeRack, index) in tubeRacks" :key="tubeRack.uuid || index" class="tube-rack-container">
<div v-for="(tubeRack, index) in tubeRacks" :key="`${tubeRack.uuid || index}-${componentRefreshKey}`"
class="tube-rack-container">
<TestTubeRack ref="tubeRackComponentInstance" :tubeRack="tubeRack" :plates="plates" :key="refreshKey"
@updateActivate="handleActivateChange" @updateSelectedSamples="handleSelectedSamples"
@updateActivate="handleActivateChange" @updateSelectedSamples="handleUpdateSelectedSamples"
@deleteTubeRack="deleteHandle" @changeUser="handleChangeUser"
@clearProjectSelection="clearProjectSelection" />
</div>
@ -171,7 +172,7 @@ const handleSampleUpdate = async ({
//
tubeRacks.value = []
await nextTick()
tubeRacks.value = updatedTubeRacks //
tubeRacks.value = updatedTubeRacks //
selectedProject.value = null //
await nextTick()
@ -194,20 +195,28 @@ const projectSelectorInstance = ref()
const UUID = ref<string>('')
// ID
const selectedSampleIdsInParent = ref<number[]>([])
//
// key
const componentRefreshKey = ref(0)
// handleConfirmProjectSelector
const handleConfirmProjectSelector = () => {
console.log("确认事件")
selectedSampleIdsInParent.value = [] //
// tubeRacks
// tubeRacks.value.forEach((rack) => {
// console.log("");
// rack.selectedSampleIds = []
// emitClearSelectedSamples(rack.uuid)
// })
// ElMessage({
// message: '',
// type: 'success',
// })
//
selectedSampleIdsInParent.value = []
//
const currentTubeRack = tubeRacks.value.find(rack => rack.uuid === UUID.value)
if (currentTubeRack) {
currentTubeRack.selectedSampleIds = []
}
ElMessage({
message: '样本已确认,选中状态已清空',
type: 'success',
duration: 2000,
onClose: () => {
componentRefreshKey.value += 1
}
})
}
const tubeRackComponentInstance = ref()
//
@ -221,22 +230,18 @@ const tubeRackComponentInstance = ref()
// }
// }
//
//
const closeProjectSelector = () => {
selectedSampleIdsInParent.value = []
}
//
const handleSelectedSamples = ({
sampleIds,
uuid,
}: {
sampleIds: number[]
uuid: string
}) => {
const targetTubeRack = tubeRacks.value.find((rack) => rack.uuid === uuid)
if (targetTubeRack) {
targetTubeRack.selectedSampleIds = [...sampleIds]
selectedSampleIdsInParent.value = targetTubeRack.selectedSampleIds
const handleUpdateSelectedSamples = ({ sampleIds, uuid }: { sampleIds: number[]; uuid: string }) => {
//
const tubeRack = tubeRacks.value.find(tube => tube.uuid === uuid)
if (tubeRack) {
tubeRack.selectedSampleIds = sampleIds
//
selectedSampleIdsInParent.value = sampleIds
UUID.value = uuid
}
}

163
src/pages/Index/Settings/Users.vue

@ -1,12 +1,7 @@
<template>
<div class="user-table">
<el-table
:data="tableData"
header-cell-class-name="table-header"
row-class-name="table-row"
style="width: 100%"
@selection-change="handleSelectionChange"
>
<div class="user-table" :key="refreshKey">
<el-table :data="tableData" header-cell-class-name="table-header" row-class-name="table-row" style="width: 100%"
@selection-change="handleSelectionChange">
<el-table-column type="selection" />
<el-table-column label="用户" width="200" property="account" />
<el-table-column label="权限" property="usrRole" />
@ -17,94 +12,36 @@
<el-button type="danger" @click="deleteSelectedUsers">删除</el-button>
</div>
<!-- 删除用户 -->
<DelWarn
v-if="delShowModal"
:visible="delShowModal"
icon="/src/assets/Warn.svg"
title="删除用户"
:message="deleteUserMessage"
description="您正在删除用户,请谨慎操作"
confirm-text="确认删除"
cancel-text="取消删除"
@confirm="handleConfirmDelete"
@cancel="handleCancelDelete"
/>
<DelWarn v-if="delShowModal" :visible="delShowModal" icon="/src/assets/Warn.svg" title="删除用户"
:message="deleteUserMessage" description="您正在删除用户,请谨慎操作" confirm-text="确认删除" cancel-text="取消删除"
@confirm="handleConfirmDelete" @cancel="handleCancelDelete" />
<!-- 通知用户删除成功 -->
<DelMessage
v-if="delMessageShowModal"
:visible="delMessageShowModal"
icon="/src/assets/OK.svg"
message="已成功删除用户"
:username="selectedUsers[0].account"
@confirm="handleConfirmMsg"
/>
<DelMessage v-if="delMessageShowModal" v-model:visible="delMessageShowModal" icon="/src/assets/OK.svg"
message="已成功删除用户" :username="selectedUsers[0]?.account" @confirm="handleConfirmMsgDelete" />
<!-- 确认是否更改该用户的PIN码 -->
<DelWarn
v-if="updatePinModal"
:visible="updatePinModal"
icon="/src/assets/update-pin-icon.svg"
title="PIN码更新"
:message="updatePinMessage"
description="您正在更改PIN码,请谨慎操作"
confirm-text="确认更新"
cancel-text="取消更新"
@confirm="handleConfirmUpdatePin"
@cancel="handleCancelUpdatePin"
/>
<DelWarn v-if="updatePinModal" :visible="updatePinModal" icon="/src/assets/update-pin-icon.svg" title="PIN码更新"
:message="updatePinMessage" description="您正在更改PIN码,请谨慎操作" confirm-text="确认更新" cancel-text="取消更新"
@confirm="handleConfirmUpdatePin" @cancel="handleCancelUpdatePin" />
<!-- 让用户输入新的pin -->
<EnterPinModal
v-if="enterPinModal"
:visible="enterPinModal"
:loading="updatePinLoading"
@confirm="updatePinConfirm"
@cancel="closeEnterPinModal"
/>
<EnterPinModal v-if="enterPinModal" :visible="enterPinModal" :loading="updatePinLoading" @confirm="updatePinConfirm"
@cancel="closeEnterPinModal" />
<!-- 通知用户PIN码更新成功 -->
<DelMessage
v-if="updatePinMsgModal"
:visible="updatePinMsgModal"
icon="/src/assets/OK.svg"
message="PIN码更新成功"
:username="selectedUsers[0].account"
@confirm="handleConfirmMsg"
/>
<DelMessage v-if="updatePinMsgModal" :visible="updatePinMsgModal" icon="/src/assets/OK.svg" message="PIN码更新成功"
:username="selectedUsers[0].account" @confirm="handleConfirmMsg" />
<div v-if="currentStep === 'username'">
<AddUserModal
:visible="insertUserShowModal"
:already-exist="isExist"
placeholder="请输入用户名"
:tips="tips"
@confirm="handleConfirmInsert"
@cancel="handleCancelInsert"
@resetAlreadyExist="resetAlreadyExist"
/>
<AddUserModal :visible="insertUserShowModal" :already-exist="isExist" placeholder="请输入用户名" :tips="tips"
@confirm="handleConfirmInsert" @cancel="handleCancelInsert" @resetAlreadyExist="resetAlreadyExist" />
</div>
<div v-else-if="currentStep === 'pin'">
<EnterPinModal
:visible="isPinModalVisible"
:loading="registerLoading"
@confirm="handlePinConfirm"
@cancel="closeModal"
/>
<EnterPinModal :visible="isPinModalVisible" :loading="registerLoading" @confirm="handlePinConfirm"
@cancel="closeModal" />
</div>
<!-- 通知成功添加用户 -->
<DelMessage
v-if="confirmInsert"
:visible="confirmInsert"
icon="/src/assets/OK.svg"
message="已成功添加新用户"
:username="newUser.account"
@confirm="handleConfirmMsg"
/>
<DelMessage v-if="confirmInsert" :visible="confirmInsert" icon="/src/assets/OK.svg" message="已成功添加新用户"
:username="newUser.account" @confirm="handleConfirmMsg" />
<!-- 通知用户选中用户 -->
<DelMessage
v-if="isChecked"
:visible="isChecked"
icon="/src/assets/Warn.svg"
message="请先选中用户"
username=""
@confirm="handleConfirmMsg"
/>
<DelMessage v-if="isChecked" :visible="isChecked" icon="/src/assets/Warn.svg" message="请先选中用户" username=""
@confirm="handleConfirmMsg" />
</div>
</template>
@ -165,7 +102,7 @@ const isExist = ref(false)
const tips = ref('')
//
const selectedUsers = ref<User[]>([])
const refreshKey = ref(0)
//
const fetchUserList = async () => {
const response = await getUserList()
@ -180,8 +117,15 @@ const fetchUserList = async () => {
const handleSelectionChange = (val: User[]) => {
selectedUsers.value = val
console.log('选中的用户', val)
tempUser.value.id = val[0].id
tempUser.value.password = val[0].password
// tempUser
if (val && val.length > 0) {
tempUser.value.id = val[0].id
tempUser.value.password = val[0].password
} else {
// tempUser
tempUser.value.id = 0
tempUser.value.password = ''
}
}
//
const addUser = async () => {
@ -211,29 +155,39 @@ onMounted(() => {
})
const handleConfirmDelete = async () => {
const ids = selectedUsers.value.map((user) => user.id)
for (const id of ids) {
const response = await deleteUser({ id })
if (!response || !response.success) {
console.log('删除用户失败')
return
try {
const ids = selectedUsers.value.map((user) => user.id)
for (const id of ids) {
const response = await deleteUser({ id })
if (!response || !response.success) {
console.log('删除用户失败')
return
}
}
delShowModal.value = false
//
const deletedUsername = selectedUsers.value[0]?.account
//
selectedUsers.value = []
delMessageShowModal.value = true
} catch (error) {
console.error('删除用户时发生错误:', error)
}
fetchUserList()
delShowModal.value = false
console.log('用户已删除')
delMessageShowModal.value = true
}
const handleCancelDelete = () => {
delShowModal.value = false
console.log('删除操作已取消')
}
const handleConfirmMsg = () => {
delMessageShowModal.value = false
confirmInsert.value = false
isChecked.value = false
updatePinMsgModal.value = false
fetchUserList()
}
const handleConfirmMsgDelete = () => {
delMessageShowModal.value = false
fetchUserList()
}
//PIN
//
@ -279,7 +233,6 @@ const handleCancelUpdatePin = () => {
}
//
const handleConfirmInsert = (val: string) => {
console.log(val)
const user = tableData.value.find((item) => item.account == val)
if (val == '') {
isExist.value = true
@ -339,6 +292,7 @@ const deleteUserMessage = computed(
.user-table {
width: 100%;
background-color: #f9f9f9;
::v-deep {
.el-table {
border: none;
@ -364,17 +318,20 @@ const deleteUserMessage = computed(
color: #333;
text-align: center;
}
.el-table__cell {
height: 100px;
line-height: normal; //
padding: 0 20px; //
}
.cell {
height: 100px;
line-height: 100px;
padding: 0 10px; //
text-align: center;
}
th,
td {
border-right: none;
@ -393,6 +350,7 @@ const deleteUserMessage = computed(
margin-right: 0 !important; //
}
}
.table-footer {
position: absolute;
bottom: 100px;
@ -400,6 +358,7 @@ const deleteUserMessage = computed(
display: flex;
justify-content: center;
button {
font-size: 32px;
width: 320px;

133
src/pages/Index/TestTube/ChangeUser.vue

@ -1,12 +1,7 @@
<template>
<div id="changeUser-container">
<div class="navigator-box">
<img
class="nav-icon"
src="@/assets/Index/left.svg"
alt=""
@click.stop="goBack"
/>
<img class="nav-icon" src="@/assets/Index/left.svg" alt="" @click.stop="goBack" />
<div class="nav-text">患者信息</div>
<div class="tube-type-text">试管类型({{ tubeType }})</div>
</div>
@ -24,18 +19,9 @@
</div>
</div>
<div class="sample-display">
<div
class="sample-item"
v-for="item in box.samples"
:key="item.tubeIndex"
:class="{ selected: selectedSampleId === item.tubeIndex }"
@click="selectSample(item.tubeIndex)"
>
<div
class="sample-circle"
:data-index="item.tubeIndex + 1"
:style="generateSampleBackground(item.projId!)"
>
<div class="sample-item" v-for="item in box.samples" :key="item.tubeIndex"
:class="{ selected: selectedSampleId === item.tubeIndex }" @click="selectSample(item.tubeIndex)">
<div class="sample-circle" :data-index="item.tubeIndex + 1" :style="generateSampleBackground(item.projId!)">
<span style="color: black; font-size: 32px">{{
item.bloodType
}}</span>
@ -50,9 +36,7 @@
</div>
<div class="button-box">
<el-button class="cancel-button" @click="goBack">取消</el-button>
<el-button class="confirm-button" type="primary" @click="confirmChange"
>确定</el-button
>
<el-button class="confirm-button" type="primary" @click="confirmChange">确定</el-button>
</div>
<Keyboard />
</div>
@ -74,7 +58,7 @@ import {
processTubeSettings,
generateSampleBackground,
} from '../utils'
import { updateTubeInfo, pollTubeInfo } from '../../../services/Index/'
import { updateTubeInfo } from '../../../services/Index/'
const testTubeStore = useTestTubeStore()
const consumableStore = useConsumablesStore()
@ -86,7 +70,7 @@ const tubeType = ref<string>(testTubeStore.type || '自动') //导航栏的试
const plates = ref<ReactionPlate[]>(consumableStore.plates) //
const selectedSampleId = ref<number | null>(null) //
onMounted(() => {
startPolling()
// startPolling()
tubeInfo.value = testTubeStore.tubeInfo
tubeSettings.value = processTubeSettings(
tubeInfo.value.tubeSettings,
@ -99,41 +83,42 @@ onMounted(() => {
}
})
onUnmounted(() => {
stopPolling()
// stopPolling()
})
//
const intervalId = ref<NodeJS.Timeout | null>(null)
// const intervalId = ref<NodeJS.Timeout | null>(null)
//
//
const startPolling = () => {
intervalId.value = setInterval(async () => {
const response = await pollTubeInfo()
if (response && response.success) {
//
const latestTubeSettings = response.data.find(
(tube: handleTube) => tube.uuid === tubeInfo.value.uuid,
)?.tubeSettings
// const startPolling = () => {
// intervalId.value = setInterval(async () => {
// const response = await pollTubeInfo()
// if (response && response.success) {
// //
// const latestTubeSettings = response.data.find(
// (tube: handleTube) => tube.uuid === tubeInfo.value.uuid,
// )?.tubeSettings
if (latestTubeSettings) {
//
tubeSettings.value = processTubeSettings(
latestTubeSettings,
plates.value,
getBloodTypeLabel,
)
}
}
}, 3000) // 3
}
// if (latestTubeSettings) {
// //
// tubeSettings.value = processTubeSettings(
// latestTubeSettings,
// plates.value,
// getBloodTypeLabel,
// )
// }
// }
// }, 3000) // 3
// }
//
const stopPolling = () => {
if (intervalId.value) {
clearInterval(intervalId.value)
}
}
// const stopPolling = () => {
// if (intervalId.value) {
// clearInterval(intervalId.value)
// }
// }
//
const goBack = () => {
// stopPolling()
router.push('/index/regular/test-tube')
}
//
@ -177,25 +162,31 @@ const confirmChange = async () => {
<style lang="less" scoped>
#changeUser-container {
width: 100%;
height: 100%; /* 留出 200px 空间用于虚拟键盘 */
height: 100%;
/* 留出 200px 空间用于虚拟键盘 */
.navigator-box {
box-sizing: border-box;
width: 100%;
height: 80px; /* 调整导航条高度 */
height: 80px;
/* 调整导航条高度 */
padding: 10px;
background-color: #fff;
display: flex;
align-items: center;
font-size: 32px; /* 缩小字体 */
font-size: 32px;
/* 缩小字体 */
font-weight: 900;
.nav-icon {
width: 32px; /* 调整图标大小 */
width: 32px;
/* 调整图标大小 */
height: 32px;
}
.nav-text {
margin-left: 40px;
&::after {
content: '|';
margin: 0 40px;
@ -208,7 +199,8 @@ const confirmChange = async () => {
flex-direction: column;
gap: 20px;
padding: 10px;
height: 60%; /* 减去导航条高度 */
height: 60%;
/* 减去导航条高度 */
.sample-box {
display: flex;
@ -216,24 +208,28 @@ const confirmChange = async () => {
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
height: 45%; /* 上下两部分各占 45% */
height: 45%;
/* 上下两部分各占 45% */
box-sizing: border-box;
.sample-info {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 32px; /* 调整文字大小 */
font-size: 32px;
/* 调整文字大小 */
font-weight: bold;
color: #333;
padding-right: 20px;
white-space: nowrap;
.tube-info-title {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 130px;
}
.tube-info {
display: flex;
flex-direction: column;
@ -245,7 +241,8 @@ const confirmChange = async () => {
.sample-display {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px; /* 调整样本间距 */
gap: 20px;
/* 调整样本间距 */
width: 100%;
align-items: start;
@ -259,10 +256,13 @@ const confirmChange = async () => {
box-sizing: border-box;
height: 400px;
position: relative;
&.selected {
background-color: #b3e5fc; /* 比默认颜色深的选中背景 */
background-color: #b3e5fc;
/* 比默认颜色深的选中背景 */
box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
}
.sample-index {
position: absolute;
top: -10px;
@ -273,7 +273,8 @@ const confirmChange = async () => {
}
.sample-circle {
width: 100px; /* 缩小圆形区域 */
width: 100px;
/* 缩小圆形区域 */
height: 100px;
display: flex;
flex-direction: column;
@ -284,11 +285,14 @@ const confirmChange = async () => {
color: #fff;
font-size: 14px;
margin-top: 20px;
&::before {
content: attr(data-index);
position: absolute;
top: 10px; /* 试管上方显示 */
font-size: 18px; /* 调整序号字体大小 */
top: 10px;
/* 试管上方显示 */
font-size: 18px;
/* 调整序号字体大小 */
font-weight: bold;
color: #333;
}
@ -298,9 +302,11 @@ const confirmChange = async () => {
display: flex;
flex-direction: column;
gap: 20px;
.barcode-input,
.userid-input {
width: 80px; /* 缩小输入框 */
width: 80px;
/* 缩小输入框 */
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
@ -314,9 +320,11 @@ const confirmChange = async () => {
}
}
}
.button-box {
background-color: #fff;
margin: 40px 0;
.cancel-button,
.confirm-button {
width: 500px;
@ -325,6 +333,7 @@ const confirmChange = async () => {
font-size: 32px;
margin: 0 40px;
}
.cancel-button {
background-color: #f2f2f2;
}

55
src/pages/Index/components/Consumables/MoveLiquidArea.vue

@ -69,7 +69,7 @@
<div class="content">废料区</div>
</div>
<div class="waste-controller">
<button class="waste-button" :class="{ full: wasteStatus }" @click="fetchWasteStatus">
<button class="waste-button" :class="{ full: wasteStatus }">
<div class="button-icon">
<img :src="wasteStatus ? wasteFullIcon : wasteIcon" alt="Waste Status" />
</div>
@ -98,13 +98,13 @@
<script setup lang="ts">
import BallGrid from './BallGrid.vue'
import IdCardInfo from './IdCardInfo.vue'
import { ref, onMounted, watch, onBeforeUnmount, reactive } from 'vue'
import { ref, watch, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useEmergencyStore, useConsumablesStore } from '../../../../store'
import { Tube, LiquidState, BottleGroup } from '../../../../types/Index/index'
import {
wasteArea,
} from '../../../../services/index'
// import {
// wasteArea,
// } from '../../../../services/index'
import wasteFullIcon from '@/assets/Index/waste-full.svg'
import wasteIcon from '@/assets/Index/waste.svg'
@ -165,33 +165,32 @@ const pollInterVal = 3000
// console.log('Id', error)
// }
// }
//
const fetchWasteStatus = async () => {
try {
const res = await wasteArea()
wasteStatus.value = res.data.wasteBinFullFlag
} catch (error) {
console.log('获取废料区状态失败', error)
}
}
// //
// const fetchWasteStatus = async () => {
// try {
// const res = await wasteArea()
// wasteStatus.value = res.data.wasteBinFullFlag
// } catch (error) {
// console.log('', error)
// }
// }
//
const startPolling = () => {
// const idCardPoll = setInterval(fetchIdCardStatus, pollInterVal)
const wastePoll = setInterval(fetchWasteStatus, pollInterVal)
//
onBeforeUnmount(() => {
console.log("清除轮询")
// clearInterval(idCardPoll)
clearInterval(wastePoll)
})
}
// const startPolling = () => {
// const idCardPoll = setInterval(fetchIdCardStatus, pollInterVal)
// const wastePoll = setInterval(fetchWasteStatus, pollInterVal)
//
// onBeforeUnmount(() => {
// console.log("")
// clearInterval(idCardPoll)
// clearInterval(wastePoll)
// })
// }
const isActive = ref(false)
watch(
() => props.emergencyInfo,
(newVal) => {
console.log('急诊数据更新', newVal)
//
if (newVal && Object.keys(newVal).length > 0) {
isActive.value = true
@ -202,9 +201,9 @@ watch(
{ immediate: true }, //
)
//
onMounted(() => {
startPolling()
})
// onMounted(() => {
// startPolling()
// })
const emergencyInfo = reactive(emergencyStore.$state.emergencyInfo)
const router = useRouter()
//

48
src/pages/Index/components/Consumables/ProjectSelector.vue

@ -6,52 +6,34 @@
<span>项目选择</span>
</div>
<div class="project-list">
<button
@click="toggleAutoProject"
:class="['project-auto', { 'active-item': isSelected(0) }]"
>
<button @click="toggleAutoProject" :class="['project-auto', { 'active-item': isSelected(0) }]">
自动
</button>
<div
v-for="(item, index) in projects"
:key="index"
@click="toggleProject(item)"
:class="[
'project-item',
{ 'active-project-item': isSelected(item.projId) },
]"
:style="getStyle(item)"
>
<div v-for="(item, index) in projects" :key="index" @click="toggleProject(item)" :class="[
'project-item',
{ 'active-project-item': isSelected(item.projId) },
]" :style="getStyle(item)">
<span>{{ item.projName }}</span>
<span> {{ item.num }}/25</span>
</div>
</div>
<hr
style="
<hr style="
width: 98%;
margin: 0 auto;
background-color: gray;
margin: 10px 0;
"
/>
" />
<div class="project-controller">
<div class="blood-type">
<div class="project-title">
<span>血液类型</span>
</div>
<div class="type-list">
<button
@click="toggleAutoBloodType"
:class="['project-auto', { 'active-item': bloodType === '自动' }]"
>
<button @click="toggleAutoBloodType" :class="['project-auto', { 'active-item': bloodType === '自动' }]">
自动
</button>
<button
v-for="(item, index) in bloodTypes"
:key="index"
@click="selectBloodType(item)"
:class="getTypeClass(item)"
>
<button v-for="(item, index) in bloodTypes" :key="index" @click="selectBloodType(item)"
:class="getTypeClass(item)">
{{ item.projectName }}
</button>
</div>
@ -66,7 +48,7 @@
</template>
<script setup lang="ts">
import { ref} from 'vue'
import { ref } from 'vue'
import type { ReactionPlate } from '../../../../types/Index'
import { nanoid } from 'nanoid'
import { useConsumablesStore } from '../../../../store'
@ -90,7 +72,7 @@ defineExpose({
const projects = ref<ReactionPlate[]>(consumables.$state.plates || [])
const bloodType = ref('')
const selectedProjects = ref<number[]>([]) //
const selectedProjects = ref<number[]>([]) //
const bloodTypes = ref([
{
id: nanoid(),
@ -141,9 +123,10 @@ const toggleAutoProject = () => {
}
//
const handleConfirm = () => {
//
//
emitUpdate()
emit('confirm') //
//
emit('confirm')
}
const handleCancel = () => {
@ -418,6 +401,7 @@ const emitUpdate = () => {
}
}
}
.actions {
display: flex;
justify-content: center;

10
src/pages/Index/components/Running/PlateDisplay.vue

@ -2,16 +2,16 @@
<div class="project-list">
<div v-for="(project, index) in projects" :key="index" class="project-item">
<div class="project-info" :style="{
background: `linear-gradient(to left, ${project.color} ${getPercentage(
project.num,
)}%, #E0E0E0 ${getPercentage(project.num)}%)`,
background: `linear-gradient(to left, ${project.color || '#ccc'} ${getPercentage(
project.num || 0,
)}%, #E0E0E0 ${getPercentage(project.num || 0)}%)`,
}">
<div class="project-name">
{{ project.projName || '/' }}
{{ project.projName || '暂无' }}
</div>
</div>
<div class="project-count">
<span>{{ project.num }}/25</span>
<span>{{ project.num || 0 }}/25</span>
</div>
</div>
</div>

60
src/pages/Index/components/Setting/DelMessage.vue

@ -1,50 +1,49 @@
<template>
<teleport to="body">
<div v-if="visible" class="notification-overlay">
<div class="notification-modal">
<!-- 图标 -->
<div class="notification-icon">
<img v-if="icon" :src="iconUrl" alt="icon" />
</div>
<!-- 通知内容 -->
<div class="notification-content">
<p class="message">{{ message }}</p>
<p class="username">{{ username }}</p>
</div>
<!-- 确定按钮 -->
<div class="notification-footer">
<el-button type="primary" @click="confirm">确定</el-button>
<transition name="fade">
<div v-if="visible" class="notification-overlay">
<div class="notification-modal">
<div class="notification-icon">
<img v-if="icon" :src="iconUrl" alt="icon" />
</div>
<div class="notification-content">
<p class="message">{{ message }}</p>
<p v-if="username" class="username">{{ username }}</p>
</div>
<div class="notification-footer">
<el-button type="primary" @click="confirm">确定</el-button>
</div>
</div>
</div>
</div>
</transition>
</teleport>
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted } from 'vue'
import { defineProps, ref, onMounted, nextTick } from 'vue'
//
const props = defineProps<{
const props = withDefaults(defineProps<{
visible: boolean
icon: string //
message: string //
username: string //
}>()
icon: string
message: string
username?: string
}>(), {
username: ''
})
//
const emit = defineEmits<{
(e: 'confirm'): void
(e: 'update:visible', value: boolean): void
}>()
const icons = import.meta.glob('@/assets/**/*.svg')
// iconUrl
const iconUrl = ref<string>('')
//
onMounted(async () => {
const iconPath = `${props.icon}`
if (icons[iconPath]) {
// 使
const module = await icons[iconPath]() as { default: string }
iconUrl.value = module.default
} else {
@ -52,7 +51,7 @@ onMounted(async () => {
}
})
//
//
const confirm = () => {
emit('confirm')
}
@ -114,4 +113,15 @@ const confirm = () => {
border-radius: 50px;
}
}
//
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

14
src/pages/Index/components/TestTube/ProjectSetting.vue

@ -8,13 +8,8 @@
<!-- 标签选择 -->
<div class="tab-container">
<div
v-for="(option, index) in options"
:key="index"
class="tab"
:class="{ active: selectedOption === option }"
@click="selectOption(option)"
>
<div v-for="(option, index) in options" :key="index" class="tab"
:class="{ active: selectedOption === option }" @click="selectOption(option)">
{{ option }}
</div>
</div>
@ -30,7 +25,7 @@
</template>
<script lang="ts" setup>
import { ref, defineProps, watch } from 'vue'
import { ref, defineProps, watch } from 'vue'
import { useTestTubeStore } from '../../../../store'
const testTubeStore = useTestTubeStore()
const props = defineProps({
@ -45,7 +40,7 @@ watch(
}
},
)
const emit = defineEmits(['update:visible', 'confirm'])
const emit = defineEmits(['update:visible', 'confirm', 'clearSelection'])
const options = ref(['自动', 'BT', 'Epp.0.5', 'Epp.1.5', 'mini', 'Ctip'])
const selectedOption = ref(options.value[0])
@ -66,6 +61,7 @@ const cancel = () => {
const confirm = () => {
emit('confirm', selectedOption.value)
emit('clearSelection')
close()
}
</script>

53
src/pages/Index/components/TestTube/TestTubeRack.vue

@ -143,24 +143,21 @@ const selectedStyle = {
outline: '3px solid #4A90E2', // 使 outline
boxShadow: '0 0 10px rgba(74, 144, 226, 0.6)',
}
const clearSelection = () => {
console.log("清空选中状态")
props.tubeRack.selectedSampleIds = [] //
console.log('样本渲染组件已清空选中状态')
}
defineExpose({ clearSelection })
//
const toggleSampleSelection = (sampleId: number) => {
const index = props.tubeRack.selectedSampleIds!.indexOf(sampleId)
const currentSelected = [...(props.tubeRack.selectedSampleIds || [])]
const index = currentSelected.indexOf(sampleId)
if (index === -1) {
props.tubeRack.selectedSampleIds!.push(sampleId)
currentSelected.push(sampleId)
} else {
props.tubeRack.selectedSampleIds!.splice(index, 1)
currentSelected.splice(index, 1)
}
// updateSelectedSamples
//
emits('updateSelectedSamples', {
sampleIds: props.tubeRack.selectedSampleIds as unknown as number[],
sampleIds: currentSelected,
uuid: props.tubeRack.uuid,
})
}
@ -226,6 +223,12 @@ const handleConfirm = (selectedOption: string) => {
showSelector.value = false
// store
testTubeStore.setProjectSetting(props.tubeRack.uuid, selectedOption)
//
emits('updateSelectedSamples', {
sampleIds: [],
uuid: props.tubeRack.uuid,
})
}
const getPopoverStyle = () => ({
width: '100px',
@ -233,13 +236,27 @@ const getPopoverStyle = () => ({
justifyContent: 'space-between',
alignItems: 'center',
})
//
const clearSelectedSamples = () => {
//
emits('updateSelectedSamples', {
sampleIds: [],
uuid: props.tubeRack.uuid,
})
}
//
defineExpose({
clearSelectedSamples
})
</script>
<style lang="less" scoped>
#tube-container {
display: flex;
align-items: center;
width: 1200px;
width: 100%;
box-sizing: border-box;
flex-direction: column;
@ -260,7 +277,7 @@ const getPopoverStyle = () => ({
//
.status-panel {
width: 30%;
width: 10%;
display: flex;
flex-direction: column;
position: relative;
@ -311,8 +328,8 @@ const getPopoverStyle = () => ({
}
.sample-item {
width: 85px;
height: 85px;
width: 65px;
height: 65px;
border-radius: 50%;
border: 2px solid black;
display: flex;
@ -343,7 +360,7 @@ const getPopoverStyle = () => ({
display: flex;
align-items: center;
background-color: #ebebeb;
justify-content: space-around;
justify-content: space-evenly;
width: 100%;
padding: 20px 0;
@ -351,7 +368,7 @@ const getPopoverStyle = () => ({
.update-button,
.change-user-button,
.del-button {
width: 250px;
width: 200px;
height: 80px;
border-radius: 40px;
margin-right: 10px;
@ -372,7 +389,7 @@ const getPopoverStyle = () => ({
}
.text {
font-size: 25px;
font-size: 20px;
margin-left: 20px;
}

5
src/services/Index/Test-tube/test-tube.ts

@ -29,8 +29,7 @@ export const addTestTube = async () => {
export const deleteTube = async (data: string) => {
try {
const res = await apiClient.post(
'/api/v1/app/appTubeSettingMgr/removeTubeHolderSetting',
data,
`/api/v1/app/appTubeSettingMgr/removeTubeHolderSetting?uuid=${data}`,
)
return res.data
} catch (error) {
@ -79,7 +78,7 @@ export const updateTubeActivationStatus = async (
) => {
try {
const res = await apiClient.post(
'/api/v1/app/appTubeSettingMgr/updateTubeActivationStatus',
'/api/v1/app/appTubeSettingMgr/updateActiveState',
data,
)
return res.data

34
src/services/Index/emergency.ts

@ -1,14 +1,12 @@
import apiClient from "../../utils/axios";
import { AddEmergencyInfo } from "../../types/Index";
import apiClient from '../../utils/axios'
import { AddEmergencyInfo } from '../../types/Index'
//添加急诊信息
export const insertEmergency = async (body: AddEmergencyInfo) => {
try {
const res = await apiClient.post('/api/v1/app/emergencyTube/addNew', body)
console.log("添加急诊返回的信息为:", res);
return res.data;
}
catch (error) {
console.log(error);
return res.data
} catch (error) {
console.log(error)
}
}
@ -16,22 +14,20 @@ export const insertEmergency = async (body: AddEmergencyInfo) => {
export const getEmergency = async () => {
try {
const res = await apiClient.get('/api/regular/getEmergency')
console.log("获取急诊返回的信息为:", res);
return res.data;
}
catch (error) {
console.log(error);
return res.data
} catch (error) {
console.log(error)
}
}
//判断急诊位置是否存在试管
export const isTubeExist = async () => {
try {
const res = await apiClient.post('/api/v1/app/deviceState/getEmergencyTubePosState')
console.log("判断急诊位置是否存在试管返回的信息为:", res);
return res.data;
const res = await apiClient.post(
'/api/v1/app/deviceState/getEmergencyTubePosState',
)
return res.data
} catch (error) {
console.log(error)
}
catch (error) {
console.log(error);
}
}
}

1
src/services/Index/idCard.ts

@ -10,7 +10,6 @@ export const getIdCardList = async (params: idCardParams) => {
const res = await apiClient.post(
`/api/v1/app/a8kProjectCard/get?pageNum=${pageNum}&pageSize=${pageSize}`,
)
console.log('获取id卡信息列表', res)
return res.data
} catch (error) {
console.log('获取id卡信息出错', error)

9
src/services/Index/user-manage.ts

@ -52,8 +52,10 @@ export const getCurrentUserInfo = async (params: any) => {
//删除用户
export const deleteUser = async (params: any) => {
console.log('🚀 ~ deleteUser ~ params:', params)
const { id } = params
try {
const res = await apiClient.post('/api/v1/app/Usr/deleteUsr', params)
const res = await apiClient.post(`/api/v1/app/Usr/delUser?id=${id}`)
return res.data
} catch (error) {
console.log('删除用户时出错', error)
@ -62,8 +64,11 @@ export const deleteUser = async (params: any) => {
//注册
export const userRegister = async (params: any) => {
const { account, password, usrRole } = params
try {
const res = await apiClient.post('/api/v1/app/Usr/addUser', params)
const res = await apiClient.post(
`/api/v1/app/Usr/addUser?accound=${account}&password=${password}&role=${usrRole}`,
)
return res.data
} catch (error) {
console.log('注册时出错', error)

5
src/types/Index/Consumables.ts

@ -27,8 +27,7 @@ export interface ReactionPlate {
projShortName: string
lotId: string
color: string
enable: boolean // 当前可忽略
num: number
num?: number
}
// 小缓冲液 和 大缓冲液 接口
@ -79,7 +78,7 @@ export interface ConsumablesOneChannelScanResultPacket {
//移液盘
export interface LiquidState {
id: number
id?: number
tipNum: number
}

4
src/utils/getServerInfo.ts

@ -1,4 +1,4 @@
export function getServerInfo() {
export function getServerInfo(wsPath: string = '/api/v1/app/ws/state') {
// 获取当前页面的 URL 对象
const url = new URL(window.location.href)
@ -8,7 +8,7 @@ export function getServerInfo() {
// 构建 WebSocket URL
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsUrl = `${wsProtocol}//${host}:${port}/api/v1/app/ws/state`
const wsUrl = `${wsProtocol}//${host}:${port}${wsPath}`
// 构建 HTTP URL
const httpUrl = `${window.location.protocol}//${host}:${port}` // 例如: "http://192.168.1.100:8082" 或 "http://localhost:8082"

111
src/websocket/socket.ts

@ -1,7 +1,4 @@
// src/websocket/socket.ts
import { getServerInfo } from '../utils/getServerInfo'
const serverInfo = getServerInfo()
// 基础消息接口
interface BaseMessage {
messageType: 'Report' // 消息类型
@ -33,17 +30,19 @@ interface PromptInfo {
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: {
typeName: 'AppPromptEvents'
timestamp: number
prompt: PromptInfo[]
}
data: EventType
timestamp: number
}
// 设备工作状态消息
@ -157,6 +156,47 @@ interface IncubationPlateStateMessage extends BaseMessage {
}
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
@ -165,7 +205,8 @@ type WebSocketMessage =
| TubeHolderStateMessage
| SensorStateMessage
| IncubationPlateStateMessage
| AppEventMessage // 添加这一行
| AppEventMessage
| ConsumablesStateMessage
// 消息处理器类型
type MessageHandler<T extends WebSocketMessage> = (data: T['data']) => void
@ -179,7 +220,7 @@ class WebSocketClient {
// 使用类型安全的消息处理器映射
private messageHandlers: Map<
WebSocketMessage['type'],
WebSocketMessage['dataType'],
Set<MessageHandler<any>>
> = new Map()
@ -189,7 +230,7 @@ class WebSocketClient {
// 类型安全的订阅方法
subscribe<T extends WebSocketMessage>(
messageType: T['type'],
messageType: T['dataType'],
handler: MessageHandler<T>,
): void {
if (!this.messageHandlers.has(messageType)) {
@ -200,30 +241,37 @@ class WebSocketClient {
// 类型安全的取消订阅方法
unsubscribe<T extends WebSocketMessage>(
messageType: T['type'],
messageType: T['dataType'],
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)
}
})
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(serverInfo.wsUrl)
this.ws = new WebSocket(this.url)
this.bindEvents()
} catch (error) {
console.error('WebSocket 连接失败:', error)
@ -245,6 +293,8 @@ class WebSocketClient {
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)
@ -301,14 +351,19 @@ export type {
ProjectInfo,
Subtank,
AppEventMessage,
ConsumablesStateMessage,
ConsumableGroupBase,
LittleBottleGroup,
LargeBottleGroup,
TipInfo,
}
// 导出 WebSocket 客户端
export const createWebSocket = (url: string): WebSocketClient => {
if (!wsInstance) {
wsInstance = new WebSocketClient(url)
}
return wsInstance
// if (!wsInstance) {
// wsInstance = new WebSocketClient(url)
// }
return new WebSocketClient(url)
}
// 使用示例:

Loading…
Cancel
Save