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.
 
 
 
 

542 lines
13 KiB

<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>