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.
 
 
 
 

1102 lines
29 KiB

<template>
<div id="running-container">
<div class="circular-loader">
<!-- 中心元素 -->
<div class="center-square" :style="selectedPlateStyle">
<template v-if="selectedItem">
<span>{{ selectedItem.sampleBarcode || '无条码' }}</span>
<span>{{ selectedItem.userid || '无患者ID' }}</span>
<span>{{ selectedItem.projInfo.projName || '无项目' }}</span>
<!-- <span>{{ selectedItem.projInfo.lotId ?? '无批次号' }}</span> -->
<span>{{
getBloodTypeLabel(selectedItem.bloodType) || '无类型'
}}</span>
<span>{{ getRemainingTime(selectedItem) }}</span>
</template>
<template v-else> </template>
</div>
<!-- 温度计 -->
<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">
<img
src="@/assets/ji.png"
alt=""
width="15"
height="15"
style="border-radius: 50%"
/>
</div>
<span class="scan-men">
<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] }}</span>
<span style="font-size: 16px; font-weight: 600">{{ runningStore.optScanModuleState.projInfo.projShortName }}</span>
</span>
</div>
<div v-else class="scan-empty">
空闲
</div>
</div>
<div
v-for="(item, index) in runningStore.subTanks"
:key="item.sampleId"
class="rectangular-item"
:style="[getRotationStyle(item, index), getItemStyle(item, index)]"
@click="item.state !== 'EMPTY' && toggleSelectItem(item, index)"
>
<!--孵育盘中的急诊位 显示 '急' icon-->
<div v-if="item.isEmergency">
<div style="position: absolute;left:-16px">
<img
src="@/assets/ji.png"
alt=""
width="15"
height="15"
style="border-radius: 50%"
/>
</div>
</div>
<template v-if="item.state === 'EMPTY'">
<span class="placeholder-text">空位</span>
</template>
<template v-else>
<span v-if="item.state === 'RESERVED'" class="">
<img
src="@/assets/being booked.png"
alt=""
width="20"
height="20"
style="border-radius: 50%"
/>
</span>
<span v-if="item.state === 'INCUBATING'"
><img
src="@/assets/In incubation.png"
alt=""
width="20"
height="20"
style="border-radius: 50%"
/></span>
<span v-if="item.state === 'INCUBATION_COMPLETE'"
><img
src="@/assets/isok.png"
alt=""
width="20"
height="20"
style="border-radius: 50%"
/></span>
<span class="project-name">{{
item.projInfo.projShortName || '无项目'
}}</span>
<span class="barcode">{{ item.sampleBarcode || '无条码' }}</span>
<span class="time">{{ getRemainingTime(item) }}</span>
</template>
<div
:style="`margin-top:140px;background-color:${currentIndex == index ? 'red' : 'rgb(149, 149, 149)'}`"
class="quan"
>
{{ index + 1 }}
</div>
</div>
</div>
<div class="consumables-container">
<div class="row-first">
<!-- 急诊按钮 -->
<div
class="emergency-button"
:class="{ disabled: hasEmergencyPosition }"
@click="!hasEmergencyPosition && (showEmergencyAlert = !showEmergencyAlert)"
>
<span>急诊</span>
</div>
<!-- 试管架区域 -->
<div class="test-tube-rack-area">
<div class="tube-project-tab">
<tube-item
:tube="sampleTube"
:showNum = "false"
/>
</div>
<div class="tube-items">
<!-- <SampleDisplay :samples="tubeHolderState.tubes" :selectedSamples="selectedSamples"
@updateSelectedSamples="updateSelectedSamples" /> -->
<div class="tube-container">
<template v-for="(tube, index) in runningStore.tubeHolderState?.tubes || []" :key="index">
<tube-item
:tube="tube"
:tubeIndex="index + 1"
/>
</template>
</div>
</div>
</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.consumableData.tips[0])"
></div>
<div class="tip-text">
{{ consumablesStore.consumableData.tips[0].tipNum }}/120
</div>
</div>
<BallGrid
:total="6"
:customColors="true"
width="160px"
height="110px"
:data="consumablesStore.consumableData.larBottleGroup"
:columns="3"
class="buffer-grid"
/>
</div>
<!-- 废料区域 -->
<div class="waste-area" :style="getWasteStyle()">
<div class="waste-text">废料箱</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>
<!-- 急诊弹窗提示 -->
<teleport to="body">
<div v-if="showEmergencyAlert" class="alert-overlay">
<div class="alert-container">
<div class="alert-icon">
<img class="icon" src="@/assets/emergency.svg" />
<span>急</span>
</div>
<div class="alert-message">确认要添加急诊吗?</div>
<div class="action-buttons">
<el-button type="info" @click="cancelEmergency" class="confirm-button"
>取消</el-button
>
<el-button
type="primary"
@click="confirmEmergency"
class="cancel-button"
>确认</el-button
>
</div>
</div>
</div>
</teleport>
<EmergencyResultDialog
:result="emergencyResult!"
:visible="isDialogVisible"
@update:visible="confirmEmergencyWarn"
/>
</template>
<script setup lang="ts">
import {
ref,
onMounted,
onUnmounted,
watch,
onActivated,
computed,
} from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useConsumablesStore, useDeviceStore, useSettingTestTubeStore } from '@/store'
import { getBloodTypeLabel } from '../utils'
import {
PlateDisplay,
LittleBufferDisplay,
EmergencyResultDialog,
} from '../components'
import tubeItem from '../components/TestTube/Tube.vue'
import BallGrid from '../components/Consumables/BallGrid.vue'
import { getRunningList } from '@/services/Index/running/running'
import type {
EmergencyPosStateMessage,
ProjectInfo,
SubTank,
} from '@/websocket/socket'
import { useEmergencyStore } from '@/store/modules/emergency'
import { useRunningStore } from '@/store/modules/running'
const emergencyStore = useEmergencyStore()
const consumablesStore = useConsumablesStore()
const runningStore = useRunningStore()
const deviceStore = useDeviceStore()
const settingTubeStore = useSettingTestTubeStore()
const router = useRouter()
const route = useRoute()
// 示例数据
const sampleTube = {
userid: 'user123',
projId: [1, 2],
bloodType: 'A',
}
const hasEmergencyPosition = ref(false)
onMounted(() => {
// 在页面加载时检查是否有急诊数据传递
// 初次获取路由参数
fetchIncubationData()
})
// 监听急诊数据变化
watch(
() => emergencyStore.emergencyInfo,
(newData) => {
if (newData) {
const emergencyInfo = newData as EmergencyPosStateMessage['data']['tube']
if (emergencyInfo.projInfo && Array.isArray(emergencyInfo.projInfo)) {
emergencyInfo.projInfo.forEach(
(project: ProjectInfo, index: number) => {
//@ts-ignore
const subtank: SubTank = {
pos: `EMERGENCY-${index + 1}`,
state: 'INCUBATING',
bloodType: emergencyInfo.bloodType,
sampleBarcode: emergencyInfo.sampleBarcode,
userid: emergencyInfo.userid,
projInfo: project,
sampleId: `${emergencyInfo.sampleId}-${index}`,
projId: project.projId,
startIncubatedTime: Date.now(),
incubatedTimeSec: 300,
errors: [],
isEmergency: true,
}
// 添加到孵育盘数据中
// incubationPlates.value.push(SubTank)
},
)
}
fetchEmergencyData()
}
},
)
// 页面激活时触发
onActivated(() => {
fetchEmergencyData()
})
//急诊状态
const showEmergencyAlert = ref(false)
//确认添加急诊
const confirmEmergency = () => {
showEmergencyAlert.value = false
router.push('/index/emergency')
}
//确认结果
const confirmEmergencyWarn = (val: any) => {
console.log('急诊结果:', val)
isDialogVisible.value = false
}
//获取路由参数
const fetchEmergencyData = () => {
const emergencyQuery = route.query.emergencyData as string
if (emergencyQuery) {
emergencyData.value = JSON.parse(emergencyQuery)
console.log('急诊数据:', emergencyData.value)
}
}
//取消
const cancelEmergency = () => {
showEmergencyAlert.value = false
}
// 获取圆心样式:选中时显示蓝色边框
let selectedPlateStyle = computed(()=>{
let borderColor = selectedItem.value ? 'blue' : '#ffffff'
let borderStyle = selectedItem.value ? 'solid' : 'none'
return {
borderColor,
borderStyle,
}
})
//被选中的样本id列表
// const selectedSamples = ref<number[]>([])
// 外圈矩形元素的数量
// 孵育盘列表数据
const incubationPlates = ref<SubTank[]>([])
const selectedItem = ref<SubTank | null>(null) // 当前选中的样本
const selectedItemId = ref<string | null>(null) //
const TOTAL_SLOTS = 20 // 总矩形数
const emergencyData = ref<SubTank | null>(null)
// 修改获取旋转样式的方法
const getRotationStyle = (item: SubTank, index: number) => {
const totalItems = TOTAL_SLOTS
const angleStep = 360 / totalItems
const angle = index * angleStep
return {
transform: `
translate(-50%, -50%) /* 将矩形中心点移到圆心 */
rotate(${angle}deg) /* 旋转到对应角度 */
translateY(-400px) /* 向上偏移到圆环位置 */
`,
backgroundColor: item.projInfo?.color || '#ffffff',
borderColor: item.state === 'EMPTY'
? '#f0f0f0'
: selectedItemId.value === item.sampleId
? '#1890ff'
: getRemainingTime(item) === '已完成'
? '#ff4d4f'
: 'transparent',
borderWidth: '3px',
borderStyle: 'solid',
}
}
// 获取当前元素样式:若为选中状态则添加蓝色边框,若为完成状态则添加红色边框
const getItemStyle = (item: SubTank, index) => {
if (item.state === 'EMPTY') {
return {
borderColor: '#ffffff', // 占位符边框为白色
borderStyle: 'solid',
backgroundColor: '#f8f8f8', // 占位符背景色
}
}
const remainingTime = getRemainingTime(item)
return {
backgroundColor: item.projInfo.color,//item.isEmergency ? '#ffeded' : item.projInfo.color, // 急诊位特殊背景
borderColor: currentIndex.value === index ? 'blue' : remainingTime === '已完成' ? 'red' : 'transparent',
borderStyle: selectedItemId.value === item.sampleId || remainingTime === '已完成' ? 'solid' : 'none',
}
}
// 切换选中状态
let currentIndex = ref(0)
const toggleSelectItem = (item: SubTank, index: number) => {
// 如果点击的是已选中的样本,则取消选中
// if (selectedItemId.value === item.sampleId) {
// selectedItem.value = null
// selectedItemId.value = null
// } else {
// 否则选中新的样本
selectedItem.value = item
selectedItemId.value = item.sampleId
currentIndex.value = index
// }
}
// 计算填充样式
const getFillStyle = (item: any) => {
const percentage = (item.tipNum / 120) * 100
return {
background: `linear-gradient(to top, #bbd3fb ${percentage}%,#c9c9c9 ${percentage}%)`,
}
}
//控制废料区的状态
//@ts-ignore
const isFull = ref(consumablesStore.wasteStatus)
const showWasteAlert = ref(false)
// 获取废料区样式
const getWasteStyle = () => ({
backgroundColor: isFull.value ? '#d9534f' : '#5cb85c', // 红色表示满,绿色表示未满
transition: 'background-color 0.3s ease', // 增加平滑过渡效果
})
const closeAlert = () => {
showWasteAlert.value = false
if (wasteAlertTimeout) clearTimeout(wasteAlertTimeout) // 确保清除防抖计时器
}
let wasteAlertTimeout: ReturnType<typeof setTimeout> | null = null
// 获取当前孵育盘列表数据,并初始化倒计时
const fetchIncubationData = async () => {
try {
const response = await getRunningList()
let data: SubTank[] = []
if (response && response.success) {
// console.log('获取孵育盘列表成功:', response.data)
// 处理服务器返回的数据
data = response.data.subtanks.map((plate: SubTank) => ({
...plate,
isSelected: plate.sampleId === selectedItemId.value,
}))
}
// 添加急诊数据到末位(如果存在急诊数据)
const filledData = emergencyData.value
? [...data, emergencyData.value]
: data
// 填充占位符,确保总数为 20 个
while (filledData.length < TOTAL_SLOTS) {
filledData.push({
pos: `PLACEHOLDER-${filledData.length + 1}`,
state: 'EMPTY',
bloodType: "WHOLE_BLOOD",
sampleBarcode: '',
userid: '',
projInfo: {
projId: 0,
projName: '',
projShortName: '',
color: '',
},
startIncubatedTime: 0,
incubatedTimeSec: 0,
remainTimeSec: 0,
sampleId: `PLACEHOLDER-${filledData.length + 1}`,
projId: 0,
errors: [],
isEmergency: false
// isPlaceholder: true, // 标记为占位符
})
}
// 更新 `incubationPlates` 数据
// incubationPlates.value = filledData
// 更新开始时间戳
updateStartTimes()
} catch (error) {
console.error('获取孵育盘列表失败:', error)
// 即使失败,也需要填充占位符
const filledData = emergencyData.value ? [emergencyData.value] : []
while (filledData.length < TOTAL_SLOTS) {
filledData.push({
pos: `PLACEHOLDER-${filledData.length + 1}`,
state: 'EMPTY',
bloodType: 'WHOLE_BLOOD',
sampleBarcode: '',
userid: '',
projInfo: {
projId: 0,
projName: '',
projShortName: '',
color: '',
},
startIncubatedTime: 0,
incubatedTimeSec: 0,
remainTimeSec: 0,
sampleId: `PLACEHOLDER-${filledData.length + 1}`,
projId: 0,
errors: [],
isEmergency: false,
// isPlaceholder: true,
})
}
// incubationPlates.value = filledData
}
}
// 更新开始时间戳(表示计时起点)
const startTimes = ref<Record<string, number>>({})
const updateStartTimes = () => {
const currentTime = Date.now() // 获取当前时间戳
incubationPlates.value?.forEach((plate) => {
if (!startTimes.value[plate.pos]) {
startTimes.value[plate.pos] = currentTime // 记录初始时戳
}
})
}
// 计算剩余时间
const getRemainingTime = (plate: SubTank) => {
if(!plate.projId){
return;
}
const startTime = startTimes.value[plate.pos] || Date.now()
const elapsed = (Date.now() - startTime) / 1000 // 已经过的秒数
const remaining = Math.max(0, plate.incubatedTimeSec - elapsed) // 剩余时间,最小为0
if (remaining === 0) return '已完成'
const minutes = Math.floor(remaining / 60)
const seconds = Math.floor(remaining % 60)
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
}
const emergencyResult = ref() // 存储急诊结果
const isDialogVisible = ref(false) // 控制弹窗的显示
let emergencyCompleted = ref(false) // 标志位,确保急诊结果只显示一次
// 监听急诊位的反应时间并触���弹窗
const watchEmergencyCompletion = () => {
watch(
() => incubationPlates.value,
(newPlates) => {
const emergencyPlate = newPlates.find(
(plate) => plate.isEmergency,
)
if (emergencyPlate && getRemainingTime(emergencyPlate) === '已完成') {
if (!emergencyCompleted.value) {
// 如果急诊结果还没显示过,显示弹窗并更新结果
emergencyResult.value = {
date: new Date().toLocaleString(),
sampleBarcode: emergencyPlate.sampleBarcode,
projInfo: emergencyPlate.projInfo,
bloodType: emergencyPlate.bloodType,
operator: 'admin', // 示例操作人
expiryDate: '2025-12-31', // 示例数据
serialNumber: 'SN12345678', // 示例数据
reference: '参考值', // 示例数据
value: '5.24 mg/L', // 示例数据
}
isDialogVisible.value = true
emergencyCompleted.value = true // 设置为已显示
}
}
},
{ deep: true },
)
}
onMounted(() => {
watchEmergencyCompletion()
})
// 停止监听的逻辑
onUnmounted(() => {
isDialogVisible.value = false
emergencyResult.value = null
})
</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);
/* 轻微阴影增加立体感 */
.center-square {
width: 380px; // 调整中心矩形的宽度
height: 380px; // 调整中心矩形的高度
background-color: #ffffff;
border-radius: 50%;
position: absolute;
border: 8px solid #ffffff;
box-shadow: inset 0px 4px 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
justify-content: space-around;
gap: 20px;
z-index: 2;
span {
text-align: center;
}
span:nth-child(3),
span:nth-child(6) {
font-size: 32px;
font-weight: bold;
}
span:nth-child(1),
span:nth-child(2),
span:nth-child(4),
span:nth-child(5) {
font-size: 24px;
font-weight: 300;
}
}
.rectangular-item {
position: absolute;
left: 50%; // 将所有矩形的起始位置设置在圆心
top: 38rem; // 将所有矩形的起始位置设置在圆心
width: 75px;
height: 170px;
transform-origin: center -70px; // 调整旋转中心点
border-radius: 12px;
background-color: #dbdbdb;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
align-items: center;
padding: 15px 8px;
box-sizing: border-box;
cursor: pointer;
.icon {
width: 60px;
height: 60px;
margin: 0 auto;
}
.text {
position: absolute;
top: 60%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: #fff;
font-weight: bold;
}
&.placeholder {
background-color: #f8f8f8;
box-shadow: none;
opacity: 0.6;
}
.barcode {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
word-break: break-all; // 自动换行
text-align: center;
}
.blood-type {
font-size: 18px;
color: #666;
margin-bottom: auto;
}
.time {
font-size: 20px;
font-weight: bold;
color: #333;
position: absolute;
bottom: 15px;
&.completed {
color: #ff4d4f;
}
}
}
}
// 耗材区
.consumables-container {
width: 100%;
box-sizing: border-box;
padding: 0 30px;
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;
justify-content: flex-start;
// 急诊按钮
.emergency-button {
background: linear-gradient(135deg, #ff6b6b, #ff4757);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4);
}
span {
font-size: 32px;
color: #ffffff;
font-weight: bold;
}
}
// 试管架区域
.test-tube-rack-area {
display: flex;
align-items: center;
gap: 20px;
.tube-project-tab {
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #1976d2;
position: relative;
box-shadow: 0 2px 8px rgba(25, 118, 210, 0.1);
&::after {
content: '';
width: 3px;
height: 120px;
background: linear-gradient(to bottom, #e0341d, #fa4f0b);
position: absolute;
right: -30px;
}
}
}
}
//第二行
.row-second {
display: grid;
grid-template-columns: 3fr 4fr 2fr 1fr;
padding: 10px 0;
.tips-and-big-buffer {
display: flex;
flex-direction: column;
gap: 20px;
.tips-item {
width: 200px;
height: 137px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #f8f9fa;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
.tip-fill {
position: absolute;
width: 100%;
height: 100%;
border-radius: 16px;
transition: all 0.3s ease;
background: linear-gradient(
to top,
rgba(92, 184, 92, 0.1),
transparent
);
}
.tip-text {
font-size: 36px;
color: #2c3e50;
font-weight: bold;
z-index: 1;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
}
}
.buffer-grid {
margin-top: -10px;
}
.waste-area {
border-radius: 25px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
background: #f8f9fa;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
.waste-text {
font-size: 28px;
font-weight: 700;
color: #ffffff;
writing-mode: vertical-rl;
text-orientation: upright;
letter-spacing: 4px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
&:hover {
transform: translateY(-2px);
}
}
}
// 添加响应式设计
@media screen and (max-width: 800px) {
padding: 15px 20px; // 修改padding值以适应小屏幕
.row-first {
margin-bottom: 20px;
gap: 20px;
.emergency-button {
width: 200px;
height: 100px;
span {
font-size: 28px;
}
}
}
.row-second {
gap: 15px;
.tips-item {
width: 180px;
height: 120px;
.tip-text {
font-size: 32px;
}
}
.waste-area {
width: 90px;
height: 285px;
.waste-text {
font-size: 24px;
}
}
}
}
}
}
.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);
}
}
// 添加动画
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.red {
background-color: rgb(223, 237, 248);
}
.quan {
width: 40px;
height: 18px;
line-height: 18px;
text-align: center;
border-radius: 5%;
background-color: rgb(149, 149, 149);
border: 1px solid rgb(114, 111, 111);
color: rgb(255, 255, 255);
font-size: 13px;
position: absolute;
}
.tube-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
height: auto;
padding: 20px;
width: 100%;
}
.plate-temp{
position: fixed;
top: 120px;
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: 880px;
right: 10px;
padding: 8px 5px;
background-color: rgb(223, 237, 248);
border-radius: 5px;
height: 180px;
.scan-men{
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>