From 76ec9c611b057142f537386b5503a072a718052e Mon Sep 17 00:00:00 2001 From: gzt Date: Tue, 17 Dec 2024 21:11:38 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E5=85=BC=E5=AE=B9Websocket?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E6=94=B9=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=83=A8=E5=88=86=E7=9A=84bug=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Index/Index.vue | 54 +++---- src/pages/Index/Regular/Consumables.vue | 35 ++++- src/pages/Index/Regular/Emergency.vue | 8 +- src/pages/Index/Regular/TestTube.vue | 61 ++++---- src/pages/Index/Settings/Users.vue | 163 ++++++++------------- src/pages/Index/TestTube/ChangeUser.vue | 133 +++++++++-------- .../components/Consumables/MoveLiquidArea.vue | 55 ++++--- .../components/Consumables/ProjectSelector.vue | 48 ++---- .../Index/components/Running/PlateDisplay.vue | 10 +- src/pages/Index/components/Setting/DelMessage.vue | 60 ++++---- .../Index/components/TestTube/ProjectSetting.vue | 14 +- .../Index/components/TestTube/TestTubeRack.vue | 53 ++++--- src/services/Index/Test-tube/test-tube.ts | 5 +- src/services/Index/emergency.ts | 34 ++--- src/services/Index/idCard.ts | 1 - src/services/Index/user-manage.ts | 9 +- src/types/Index/Consumables.ts | 5 +- src/utils/getServerInfo.ts | 4 +- src/websocket/socket.ts | 111 ++++++++++---- 19 files changed, 461 insertions(+), 402 deletions(-) diff --git a/src/pages/Index/Index.vue b/src/pages/Index/Index.vue index 8db496c..b5f2f7d 100644 --- a/src/pages/Index/Index.vue +++ b/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([]); const failItems = ref([]); -// const consumableStore = useConsumablesStore(); +const consumableStore = useConsumablesStore(); // 新增的变量 const user = ref(JSON.parse(sessionStorage.getItem('token') || '{}') as unknown as User) const isTesting = ref(false); // 用于控制按钮的显示 @@ -111,23 +112,16 @@ const showWarnModal = ref(false) const ErrorMessage = ref('') const showErrorModal = ref(false) const WarnMessage = ref('') -// 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; - onMounted(() => { - // 启动轮询 - // pollingTimer = setInterval(getEvent, pollingInterval); + ws.connect(); ws.subscribe('AppEvent', handleAppEvent); }); onBeforeUnmount(() => { - // 清除轮询 - // if (pollingTimer) { - // clearInterval(pollingTimer); - // } + ws.unsubscribe('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); diff --git a/src/pages/Index/Regular/Consumables.vue b/src/pages/Index/Regular/Consumables.vue index 34105a2..52639fb 100644 --- a/src/pages/Index/Regular/Consumables.vue +++ b/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([ tipNum: 0, }, ]) +//是否加载 +const isAlreadyLoad = ref(false) // 临时状态管理小球激活数量 const tempTipNum = ref([ ...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([ { @@ -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('SensorState', handleSensorState); + socket.subscribe('ConsumablesStateService', handleConsumablesState); getEmergencyInfo() }) onBeforeUnmount(() => { @@ -201,6 +223,7 @@ onBeforeUnmount(() => { socket.disconnect() // 断开连接 } socket.unsubscribe('SensorState', handleSensorState); + socket.unsubscribe('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('ConsumablesStateService', handleConsumablesState); // 重置 moveLiquids 和 tempTipNum moveLiquids.value = [ { id: 1, tipNum: 0 }, diff --git a/src/pages/Index/Regular/Emergency.vue b/src/pages/Index/Regular/Emergency.vue index 3374518..37d0357 100644 --- a/src/pages/Index/Regular/Emergency.vue +++ b/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; diff --git a/src/pages/Index/Regular/TestTube.vue b/src/pages/Index/Regular/TestTube.vue index 9350cb7..07a4dd4 100644 --- a/src/pages/Index/Regular/TestTube.vue +++ b/src/pages/Index/Regular/TestTube.vue @@ -2,9 +2,10 @@
-
+
@@ -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('') // 当前选中的样本ID列表 const selectedSampleIdsInParent = ref([]) -// 确认项目选择,清空当前选中样本 +// 添加一个刷新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 } } diff --git a/src/pages/Index/Settings/Users.vue b/src/pages/Index/Settings/Users.vue index 8fd2a11..81bb499 100644 --- a/src/pages/Index/Settings/Users.vue +++ b/src/pages/Index/Settings/Users.vue @@ -1,12 +1,7 @@ @@ -165,7 +102,7 @@ const isExist = ref(false) const tips = ref('') // 模拟用户数据 const selectedUsers = ref([]) - +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; diff --git a/src/pages/Index/TestTube/ChangeUser.vue b/src/pages/Index/TestTube/ChangeUser.vue index 04129b3..3c82d28 100644 --- a/src/pages/Index/TestTube/ChangeUser.vue +++ b/src/pages/Index/TestTube/ChangeUser.vue @@ -1,12 +1,7 @@ diff --git a/src/pages/Index/components/TestTube/TestTubeRack.vue b/src/pages/Index/components/TestTube/TestTubeRack.vue index cc147a3..7c600bc 100644 --- a/src/pages/Index/components/TestTube/TestTubeRack.vue +++ b/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 +})