forked from gzt/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
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>
|