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.
1102 lines
29 KiB
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>
|