|
|
<template> <div id="running-container"> <div class="circular-loader"> <!-- 中心元素 --> <TankInfo :selected-item=" currentIndex >= 0 ? runningStore.subTanks![currentIndex] : undefined " ></TankInfo> <!-- 温度计 --> <div class="plate-temp"> <span class="plate-temp-reg"> 孵育盘温度 </span> <span class="plate-temp-value"> <span style="font-size: 22px"> {{ deviceStore.sensorState?.incubateBoxTemperature || 0 }} </span> </span> </div> <!-- 光学模组 --> <div class="scan-main"> <span style="font-weight: 600; font-size: 18px; display: block"> 光学模组 </span> <div v-if=" runningStore.optScanModuleState && runningStore.optScanModuleState!.state !== 'EMPTY' " class="scan-ji" > <!--是急诊位时--> <div style="position: absolute; left: 0px" v-if="runningStore.optScanModuleState!.isEmergency" > <img src="@/assets/ji.png" alt="" width="15" height="15" style="border-radius: 50%" /> </div> <span class="scan-men" :style="`background-color:${consumablesStore.projIdColorMap[runningStore.optScanModuleState.projId]}`" > <img src="@/assets/men.png" alt="" width="20" height="20" style="border-radius: 50%" /> <span v-if="runningStore.optScanModuleState?.state === 'SCANNING'" >扫描中</span > <span v-else>{{ runningStore.optScanModuleState.userid }}</span> <span style="font-size: 14px">{{ settingTubeStore.bloodTypeKeyMap[ runningStore.optScanModuleState.bloodType ].name }}</span> <span style="font-size: 16px; font-weight: 600">{{ runningStore.optScanModuleState.projInfo.projShortName }}</span> </span> </div> <div v-else class="scan-empty">空闲</div> </div> <SubTankItem v-for="(item, index) in runningStore.subTanks" :item="item" :index="index" :key="item.pos" :is-selected="index === currentIndex" @toggle-select-item="toggleSelectItem" ></SubTankItem> </div> <div class="consumables-container"> <div class="row-first"> <!-- 急诊按钮 --> <div class="emergency-area"> <div class="emergency-button" :style="`background:${canSetEmergency ? '#ff6b6b' : '#c7c7c7'}`" @click="canSetEmergency ? confirmEmergency() : null" > <span>急诊</span> </div> <div class="emergency-tube" :class="{ config: !canSetEmergency }"> <tube-item :tube="canSetEmergency ? undefined : emergencyStore.emergencyInfo" :showNum="false" /> </div> </div> <div class="split"></div> <div class="tube-items"> <tube-item v-for="(tube, index) in runningStore.tubeHolderState?.tubes || []" :key="index" :tube="tube" :tubeNo="index + 1" /> </div> </div> <!-- 第二行 --> <div class="row-second"> <!-- 反应板区域 --> <div class="plates-area"> <PlateDisplay :projects="consumablesStore.consumableData.reactionPlateGroup" /> </div> <!-- 小缓冲液区域 --> <div class="little-buffer-liquid"> <LittleBufferDisplay :bufferData="consumablesStore.consumableData.littBottleGroup" /> </div> <!-- tips + 大缓冲液区域 --> <div class="tips-and-big-buffer"> <div class="tips-item"> <div class="tip-fill" :style="getFillStyle(consumablesStore.tipCount)" ></div> <div class="tip-text">{{ consumablesStore.tipCount }}/360</div> <span class="label">移液头</span> </div> <BigBufferDisplay :data="consumablesStore.consumableData.larBottleGroup" :width="160" height="110px" ></BigBufferDisplay> </div> <!-- 废料区域 --> <div class="waste-area" :class="{ isFull: deviceStore.sensorState.wasteBinFullFlag }" > <div class="waste-text">废料箱</div> <div class="full-state"> {{ deviceStore.sensorState.wasteBinFullFlag ? '已满' : '未满' }} </div> </div> </div> </div> </div> <!-- 弹窗提示 --> <teleport to="body"> <div v-if="showWasteAlert" class="alert-overlay"> <div class="alert-container"> <div class="alert-title">废料箱已满</div> <div class="alert-message">请尽快清理废料箱</div> <el-button type="danger" @click="closeAlert" class="alert-button" >确认</el-button > </div> </div> </teleport> <!-- 急诊弹窗提示 --> </template>
<script setup lang="ts"> import { computed, ref, watch } from 'vue' import { useRouter } from 'vue-router' import { useConsumablesStore, useDeviceStore, useSettingTestTubeStore, useEmergencyStore, } from '@/store' import TankInfo from '../components/Running/TankInfo.vue' import { PlateDisplay, LittleBufferDisplay } from '../components' import tubeItem from '../components/TestTube/Tube.vue' import { useRunningStore } from '@/store/modules/running' import SubTankItem from '../components/Running/SubTank.vue' import BigBufferDisplay from '../components/Running/BigBufferDisplay.vue'
const consumablesStore = useConsumablesStore() const runningStore = useRunningStore() const deviceStore = useDeviceStore() const settingTubeStore = useSettingTestTubeStore() const emergencyStore = useEmergencyStore() //从急诊页面添加的急诊数据
const router = useRouter()
const canSetEmergency = computed(() => { return ( emergencyStore.emergencyInfo && (emergencyStore.emergencyInfo.state === 'EMPTY' || emergencyStore.emergencyInfo.state === 'PROCESS_COMPLETE') ) }) //确认添加急诊
const confirmEmergency = () => { router.push('/index/emergency') }
// 当前选中的tank索引
let currentIndex = ref(-1) const toggleSelectItem = (index: number) => { if (index === currentIndex.value) { currentIndex.value = -1 } else { currentIndex.value = index } }
// 计算填充样式
const getFillStyle = (cnt: number) => { const percentage = (cnt / 360) * 100 return { background: `linear-gradient(to top, #bbd3fb ${percentage}%,#c9c9c9 ${percentage}%)`, } }
const showWasteAlert = ref(false) const closeAlert = () => { showWasteAlert.value = false } watch( () => deviceStore.sensorState.wasteBinFullFlag, (newVal) => { if (newVal) { showWasteAlert.value = true } }, ) </script> <style lang="less" scoped> #running-container { display: flex; flex-direction: column; justify-content: space-around; align-items: center; background-color: #f9fafb; height: 92vh;
/* 更柔和的背景色 */ // 孵育盘
.circular-loader { position: relative; width: 900px; height: 900px; border-radius: 50%; background-color: #f0f2f5; display: flex; align-items: center; justify-content: center; margin: 0 auto; border: 8px solid #ffffff; box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); }
// 耗材区
.consumables-container { width: 100%; box-sizing: border-box; padding: 15px 20px; display: flex; flex-direction: column; background-color: #ffffff; box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05); border-radius: 20px 20px 0 0;
// 第一行
.row-first { display: flex; align-items: center; margin-bottom: 10px;
.emergency-area { display: flex; gap: 8px; align-items: center; align-self: stretch;
.emergency-button { width: 80px; height: 80px; background: #ff6b6b; border-radius: 20px; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
span { font-size: 24px; color: #ffffff; font-weight: bold; } } .emergency-tube { align-self: flex-end; } } .split { width: 4px; height: 100px; background-color: #ccc; margin: 0 10px; } .tube-items { display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: center; height: auto; column-gap: 5px; // padding: 0 10px;
} }
//第二行
.row-second { display: grid; gap: 15px; grid-template-columns: 3fr 4fr 2fr 1fr; padding: 10px 0;
.tips-and-big-buffer { display: flex; flex-direction: column;
.tips-item { width: 200px; height: 137px; border-radius: 12px; display: flex; align-items: center; justify-content: center; position: relative; background: #c0c0c0; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); overflow: hidden;
margin-bottom: 10px;
.label { position: absolute; left: 12px; top: 8px; font-size: 18px; color: #fff; } .tip-fill { position: absolute; width: 100%; height: 100%; border-radius: 16px; transition: all 0.3s ease; background: rgba(92, 184, 92, 0.2); }
.tip-text { font-size: 36px; color: #fff; font-weight: bold; z-index: 1; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } } }
.waste-area { border-radius: 12px; display: flex; flex-direction: column; justify-content: center; align-items: center; transition: all 0.3s ease; background-color: #5cb85c; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); &.isFull { background-color: #d9534f; } .waste-text { font-size: 26px; font-weight: 600; color: #ffffff; writing-mode: vertical-rl; text-orientation: upright; letter-spacing: 6px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .full-state { margin-top: 8px; color: #fff; font-size: 20px; font-weight: bold; } } } } }
.alert-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); display: flex; justify-content: center; align-items: center; z-index: 1000;
.alert-container { background-color: #ffffff; padding: 40px; border-radius: 20px; text-align: center; max-width: 600px; animation: slideIn 0.3s ease; position: relative;
.alert-icon { .icon { width: 80px; height: 80px; margin: 0 auto; }
span { font-size: 32px; position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%); color: #fff; } }
.alert-title { font-size: 36px; font-weight: bold; color: #d9534f; margin-bottom: 20px; }
.alert-message { font-size: 28px; color: #495057; margin-bottom: 30px; }
.action-buttons { display: flex; justify-content: center; gap: 20px;
.el-button { min-width: 160px; height: 50px; font-size: 24px; border-radius: 25px; transition: all 0.3s ease;
&:hover { transform: translateY(-2px); } } } } }
@keyframes slideIn { from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); } }
.plate-temp { position: fixed; top: 140px; right: 30px; padding: 10px; background-color: rgb(209, 229, 255); border-radius: 5px;
.plate-temp-reg { color: rgb(95, 177, 253); font-size: 18px; display: block; }
.plate-temp-value { display: block; background-color: rgb(95, 177, 253); color: white; } } .scan-main { position: fixed; top: 54rem; right: 10px; padding: 8px 5px; background-color: rgb(223, 237, 248); border-radius: 5px; height: 180px; .scan-men { color: #fff; display: inline-block; background-color: rgb(120, 206, 86); border-radius: 5px; height: 140px; padding: 5px; display: flex; flex-direction: column; justify-content: space-around; align-items: center; }
.scan-empty { background-color: #ccc; height: 140px; border-radius: 5px; display: flex; justify-content: center; align-items: center; }
.scan-ji { display: flex; height: 100px; width: 100px; justify-content: center; } } </style>
|