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.
1505 lines
40 KiB
1505 lines
40 KiB
<template>
|
|
<div id="running-container">
|
|
<div class="circular-loader">
|
|
<!-- 中心元素 -->
|
|
<div class="center-square" :style="getCenterSquareStyle()">
|
|
<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 v-for="(item, index) in incubationPlates" :key="item.sampleId" class="rectangular-item"
|
|
:style="[getRotationStyle(item, index), getItemStyle(item)]"
|
|
@click="!item.isPlaceholder && toggleSelectItem(item,index)">
|
|
<template v-if="item.isPlaceholder">
|
|
<!-- 占位符 -->
|
|
<span class="placeholder-text">空位</span>
|
|
</template>
|
|
<template v-else>
|
|
<!-- 正常数据 -->
|
|
<span class="project-name">{{ item.projInfo.projShortName || '无项目' }}</span>
|
|
<span class="barcode">{{ item.sampleBarcode || '无条码' }}</span>
|
|
<div class="emergency-icon" v-if="item.pos === 'EMERGENCY'">
|
|
<img class="icon" src="@/assets/emergency.svg" />
|
|
<span class="text">急</span>
|
|
</div>
|
|
<span class="time">{{ getRemainingTime(item) }}</span>
|
|
</template>
|
|
<div v-show="currentIndex == index" :style="`margin-top:${item.pos=='EMERGENCY'?'20px':'75px'}`" class="quan">{{ index +1 }}</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="consumables-container">
|
|
<!-- 第一行 -->
|
|
<div class="row-first">
|
|
<!-- 急诊按钮 -->
|
|
<div class="emergency-button" @click="showEmergencyAlert = !showEmergencyAlert">
|
|
<span>急诊</span>
|
|
</div>
|
|
<!-- 试管架区域 -->
|
|
<div class="test-tube-rack-area">
|
|
<div class="tube-project-tab">
|
|
<tube-item
|
|
:tube="sampleTube"
|
|
:index="0"
|
|
:projects="sampleProjects"
|
|
:bloodTypes="sampleBloodTypes"
|
|
/>
|
|
</div>
|
|
<div class="tube-items">
|
|
<!-- <SampleDisplay :samples="tubeHolderState.tubes" :selectedSamples="selectedSamples"
|
|
@updateSelectedSamples="updateSelectedSamples" /> -->
|
|
<div class="tube-container">
|
|
<template v-for="(tube, index) in tubes" :key="index">
|
|
<tube-item
|
|
:tube="tube"
|
|
:index="index"
|
|
:projects="projects"
|
|
:bloodTypes="bloodTypes"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 第二行 -->
|
|
<div class="row-second">
|
|
<!-- 反应板区域 -->
|
|
<div class="plates-area">
|
|
<PlateDisplay :projects="consumablesStore.plates" />
|
|
</div>
|
|
<!-- 小缓冲液区域 -->
|
|
<div class="little-buffer-liquid">
|
|
<LittleBufferDisplay :bufferData="consumablesStore.bufferLittles" />
|
|
</div>
|
|
<!-- tips 大缓冲液区域 -->
|
|
<div class="tips-and-big-buffer">
|
|
<div class="tips-item">
|
|
<div class="tip-fill" :style="getFillStyle(consumablesStore.moveLiquids[0])"></div>
|
|
<div class="tip-text">
|
|
{{ consumablesStore.moveLiquids[0].tipNum }}/120
|
|
</div>
|
|
</div>
|
|
<BallGrid :total="6" :customColors="true" width="160px" height="110px" :data="consumablesStore.bufferBig"
|
|
: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, onDeactivated } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useTestTubeStore, useConsumablesStore } from '../../../store'
|
|
import { getBloodTypeLabel, processTubeSettings } from '../Utils'
|
|
import {
|
|
SampleDisplay,
|
|
PlateDisplay,
|
|
LittleBufferDisplay,
|
|
EmergencyResultDialog,
|
|
} from '../Components'
|
|
import tubeItem from '../components/TestTube/Tube.vue'
|
|
import BallGrid from '../Components/Consumables/BallGrid.vue'
|
|
import { wasteArea, getTubeRackState } from '../../../services/index'
|
|
import type { Subtank, TubeRackInfo } from '../../../types/Index'
|
|
import { getRunningList } from '../../../services/Index/running/running'
|
|
import { createWebSocket } from '../../../websocket/socket'
|
|
import type { TubeHolderStateMessage, IncubationPlateStateMessage, EmergencyPosStateMessage, ProjectInfo } from '../../../websocket/socket'
|
|
import { getServerInfo } from '../../../utils/getServerInfo'
|
|
import { useEmergencyStore } from '../../../store/modules/emergency'
|
|
const { wsUrl } = getServerInfo('/api/v1/app/ws/state')
|
|
const ws = createWebSocket(wsUrl)
|
|
const testTubeStore = useTestTubeStore()
|
|
const emergencyStore = useEmergencyStore()
|
|
const consumablesStore = useConsumablesStore()
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
// 定义试管架状态
|
|
const tubeRack = ref<TubeRackInfo>({
|
|
tubes: [], // 默认空试管数据
|
|
state: 'IDLE',
|
|
hasTubeToBeProcessed: false,
|
|
})
|
|
// 假设这是10条真实数据
|
|
// 模拟10条真实数据
|
|
const tubes = [
|
|
{ userid: 'user1', projId: [1], bloodType: 'A' },
|
|
{ userid: 'user2', projId: [2], bloodType: 'B' },
|
|
{ userid: 'user3', projId: [1, 2], bloodType: 'AB' },
|
|
{ userid: 'user4', projId: [], bloodType: 'O' },
|
|
{ userid: 'user5', projId: [3], bloodType: 'A' },
|
|
{ userid: 'user6', projId: [4], bloodType: 'B' },
|
|
{ userid: 'user7', projId: [3, 4], bloodType: 'AB' },
|
|
{ userid: 'user8', projId: [], bloodType: 'O' },
|
|
{ userid: 'user9', projId: [5], bloodType: 'A' },
|
|
{ userid: 'user10', projId: [6], bloodType: 'B' }
|
|
]
|
|
|
|
// 项目数据
|
|
const projects = [
|
|
{ projId: 1, projName: 'Project Alpha', color: 'red' },
|
|
{ projId: 2, projName: 'Project Beta', color: 'blue' },
|
|
{ projId: 3, projName: 'Project Gamma', color: 'green' },
|
|
{ projId: 4, projName: 'Project Delta', color: 'yellow' },
|
|
{ projId: 5, projName: 'Project Epsilon', color: 'purple' },
|
|
{ projId: 6, projName: 'Project Zeta', color: 'orange' }
|
|
]
|
|
|
|
// 血型数据
|
|
const bloodTypes = [
|
|
{ key: 'A', name: 'Type A' },
|
|
{ key: 'B', name: 'Type B' },
|
|
{ key: 'AB', name: 'Type AB' },
|
|
{ key: 'O', name: 'Type O' }
|
|
]
|
|
|
|
// 示例数据
|
|
const sampleTube = {
|
|
userid: 'user123',
|
|
projId: [1, 2],
|
|
bloodType: 'A'
|
|
}
|
|
|
|
const sampleProjects = [
|
|
{ projId: 1, projName: 'Project One', color: 'red' },
|
|
{ projId: 2, projName: 'Project Two', color: 'blue' }
|
|
]
|
|
|
|
const sampleBloodTypes = [
|
|
{ key: 'A', name: 'Type A' },
|
|
{ key: 'B', name: 'Type B' }
|
|
]
|
|
|
|
// 修改试管架状态的类型定义
|
|
interface TubeHolderState {
|
|
tubeHolderType: string
|
|
tubes: Array<{
|
|
sampleId: string | null
|
|
pos: number
|
|
isHighTube: boolean
|
|
isEmergency: boolean
|
|
bloodType: string
|
|
sampleBarcode: string
|
|
userid: string
|
|
projInfo: any[]
|
|
projIds: number[]
|
|
state: string
|
|
errors: string[]
|
|
}>
|
|
state: string
|
|
}
|
|
|
|
// 试管架状态
|
|
const tubeHolderStateData = ref<TubeHolderState>({
|
|
tubeHolderType: '',
|
|
tubes: [],
|
|
state: 'IDLE'
|
|
})
|
|
//处理试管架状态
|
|
const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
|
|
// console.log('试管架状态:', data)
|
|
tubeHolderStateData.value = data
|
|
}
|
|
//处理孵育盘状态
|
|
const handleIncubationPlateStateMessage = (data: IncubationPlateStateMessage['data']) => {
|
|
console.log('孵育盘状态:', data)
|
|
// incubationPlates.value = data.subtanks
|
|
incubationPlates.value =[
|
|
{
|
|
pos: "A1",
|
|
state: "REACTED",
|
|
bloodType: "WHOLE_BLOOD",
|
|
sampleBarcode: "BC001",
|
|
userid: "U001",
|
|
projInfo: {
|
|
projId: 1,
|
|
projName: "血液疾病检测项目",
|
|
projShortName: "项目1",
|
|
color: "red"
|
|
},
|
|
sampleId: "S001",
|
|
projId: 1,
|
|
startIncubatedTime: 1612345678,
|
|
incubatedTimeSec: 1800,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "A2",
|
|
state: "REACTED",
|
|
bloodType: "PLASMA",
|
|
sampleBarcode: "BC002",
|
|
userid: "U002",
|
|
projInfo: {
|
|
projId: 1,
|
|
projName: "血液疾病检测项目",
|
|
projShortName: "项目1",
|
|
color: "red"
|
|
},
|
|
sampleId: "S002",
|
|
projId: 1,
|
|
startIncubatedTime: 1612345778,
|
|
incubatedTimeSec: 2100,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "A3",
|
|
state: "PARTIAL_REACTION",
|
|
bloodType: "SERUM",
|
|
sampleBarcode: "BC003",
|
|
userid: "U003",
|
|
projInfo: {
|
|
projId: 1,
|
|
projName: "血液疾病检测项目",
|
|
projShortName: "项目1",
|
|
color: "red"
|
|
},
|
|
sampleId: "S003",
|
|
projId: 1,
|
|
startIncubatedTime: 1612345878,
|
|
incubatedTimeSec: 1900,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "B1",
|
|
state: "REACTED",
|
|
bloodType: "WHOLE_BLOOD",
|
|
sampleBarcode: "BC004",
|
|
userid: "U004",
|
|
projInfo: {
|
|
projId: 2,
|
|
projName: "血液成分分析项目",
|
|
projShortName: "项目2",
|
|
color: "blue"
|
|
},
|
|
sampleId: "S004",
|
|
projId: 2,
|
|
startIncubatedTime: 1612345978,
|
|
incubatedTimeSec: 2200,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "B2",
|
|
state: "NO_REACTION",
|
|
bloodType: "PLASMA",
|
|
sampleBarcode: "BC005",
|
|
userid: "U005",
|
|
projInfo: {
|
|
projId: 2,
|
|
projName: "血液成分分析项目",
|
|
projShortName: "项目2",
|
|
color: "blue"
|
|
},
|
|
sampleId: "S005",
|
|
projId: 2,
|
|
startIncubatedTime: 1612346078,
|
|
incubatedTimeSec: 2000,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "B3",
|
|
state: "REACTED",
|
|
bloodType: "SERUM",
|
|
sampleBarcode: "BC006",
|
|
userid: "U006",
|
|
projInfo: {
|
|
projId: 2,
|
|
projName: "血液成分分析项目",
|
|
projShortName: "项目2",
|
|
color: "blue"
|
|
},
|
|
sampleId: "S006",
|
|
projId: 2,
|
|
startIncubatedTime: 1612346178,
|
|
incubatedTimeSec: 2300,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "C1",
|
|
state: "PARTIAL_REACTION",
|
|
bloodType: "WHOLE_BLOOD",
|
|
sampleBarcode: "BC007",
|
|
userid: "U007",
|
|
projInfo: {
|
|
projId: 3,
|
|
projName: "特殊血液指标检测项目",
|
|
projShortName: "项目3",
|
|
color: "green"
|
|
},
|
|
sampleId: "S007",
|
|
projId: 3,
|
|
startIncubatedTime: 1612346278,
|
|
incubatedTimeSec: 1950,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "C2",
|
|
state: "REACTED",
|
|
bloodType: "PLASMA",
|
|
sampleBarcode: "BC008",
|
|
userid: "U008",
|
|
projInfo: {
|
|
projId: 3,
|
|
projName: "特殊血液指标检测项目",
|
|
projShortName: "项目3",
|
|
color: "green"
|
|
},
|
|
sampleId: "S008",
|
|
projId: 3,
|
|
startIncubatedTime: 1612346378,
|
|
incubatedTimeSec: 2150,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "C3",
|
|
state: "NO_REACTION",
|
|
bloodType: "SERUM",
|
|
sampleBarcode: "BC009",
|
|
userid: "U009",
|
|
projInfo: {
|
|
projId: 3,
|
|
projName: "特殊血液指标检测项目",
|
|
projShortName: "项目3",
|
|
color: "green"
|
|
},
|
|
sampleId: "S009",
|
|
projId: 3,
|
|
startIncubatedTime: 1612346478,
|
|
incubatedTimeSec: 2050,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "D1",
|
|
state: "REACTED",
|
|
bloodType: "WHOLE_BLOOD",
|
|
sampleBarcode: "BC010",
|
|
userid: "U010",
|
|
projInfo: {
|
|
projId: 1,
|
|
projName: "血液疾病检测项目",
|
|
projShortName: "项目1",
|
|
color: "red"
|
|
},
|
|
sampleId: "S010",
|
|
projId: 1,
|
|
startIncubatedTime: 1612346578,
|
|
incubatedTimeSec: 2250,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "D2",
|
|
state: "PARTIAL_REACTION",
|
|
bloodType: "PLASMA",
|
|
sampleBarcode: "BC011",
|
|
userid: "U011",
|
|
projInfo: {
|
|
projId: 1,
|
|
projName: "血液疾病检测项目",
|
|
projShortName: "项目1",
|
|
color: "red"
|
|
},
|
|
sampleId: "S011",
|
|
projId: 1,
|
|
startIncubatedTime: 1612346678,
|
|
incubatedTimeSec: 1850,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "D3",
|
|
state: "NO_REACTION",
|
|
bloodType: "SERUM",
|
|
sampleBarcode: "BC012",
|
|
userid: "U012",
|
|
projInfo: {
|
|
projId: 1,
|
|
projName: "血液疾病检测项目",
|
|
projShortName: "项目1",
|
|
color: "red"
|
|
},
|
|
sampleId: "S012",
|
|
projId: 1,
|
|
startIncubatedTime: 1612346778,
|
|
incubatedTimeSec: 2000,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "E1",
|
|
state: "REACTED",
|
|
bloodType: "WHOLE_BLOOD",
|
|
sampleBarcode: "BC013",
|
|
userid: "U013",
|
|
projInfo: {
|
|
projId: 2,
|
|
projName: "血液成分分析项目",
|
|
projShortName: "项目2",
|
|
color: "blue"
|
|
},
|
|
sampleId: "S013",
|
|
projId: 2,
|
|
startIncubatedTime: 1612346878,
|
|
incubatedTimeSec: 2350,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "E2",
|
|
state: "PARTIAL_REACTION",
|
|
bloodType: "PLASMA",
|
|
sampleBarcode: "BC014",
|
|
userid: "U014",
|
|
projInfo: {
|
|
projId: 2,
|
|
projName: "血液成分分析项目",
|
|
projShortName: "项目2",
|
|
color: "blue"
|
|
},
|
|
sampleId: "S014",
|
|
projId: 2,
|
|
startIncubatedTime: 1612346978,
|
|
incubatedTimeSec: 1900,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "E3",
|
|
state: "NO_REACTION",
|
|
bloodType: "SERUM",
|
|
sampleBarcode: "BC015",
|
|
userid: "U015",
|
|
projInfo: {
|
|
projId: 2,
|
|
projName: "血液成分分析项目",
|
|
projShortName: "项目2",
|
|
color: "blue"
|
|
},
|
|
sampleId: "S015",
|
|
projId: 2,
|
|
startIncubatedTime: 1612347078,
|
|
incubatedTimeSec: 2100,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "F1",
|
|
state: "REACTED",
|
|
bloodType: "WHOLE_BLOOD",
|
|
sampleBarcode: "BC016",
|
|
userid: "U016",
|
|
projInfo: {
|
|
projId: 3,
|
|
projName: "特殊血液指标检测项目",
|
|
projShortName: "项目3",
|
|
color: "green"
|
|
},
|
|
sampleId: "S016",
|
|
projId: 3,
|
|
startIncubatedTime: 1612347178,
|
|
incubatedTimeSec: 2200,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "F2",
|
|
state: "PARTIAL_REACTION",
|
|
bloodType: "PLASMA",
|
|
sampleBarcode: "BC017",
|
|
userid: "U017",
|
|
projInfo: {
|
|
projId: 3,
|
|
projName: "特殊血液指标检测项目",
|
|
projShortName: "项目3",
|
|
color: "green"
|
|
},
|
|
sampleId: "S017",
|
|
projId: 3,
|
|
startIncubatedTime: 1612347278,
|
|
incubatedTimeSec: 1950,
|
|
errors: []
|
|
},
|
|
{
|
|
pos: "F3",
|
|
state: "NO_REACTION",
|
|
bloodType: "SERUM",
|
|
sampleBarcode: "BC018",
|
|
userid: "U018",
|
|
projInfo: {
|
|
projId: 3,
|
|
projName: "特殊血液指标检测项目",
|
|
projShortName: "项目3",
|
|
color: "green"
|
|
},
|
|
sampleId: "S018",
|
|
projId: 3,
|
|
startIncubatedTime: 1612347378,
|
|
incubatedTimeSec: 2050,
|
|
errors: []
|
|
}
|
|
]
|
|
}
|
|
|
|
// 定义方法以更新试管架状态
|
|
const fetchTubeRackState = async () => {
|
|
try {
|
|
const response = await getTubeRackState()
|
|
if (response && response.success) {
|
|
tubeRack.value = response.data
|
|
} else {
|
|
console.error('获取试管架状态失败', response)
|
|
}
|
|
} catch (err) {
|
|
console.error('请求试管架状态接口出错:', err)
|
|
}
|
|
}
|
|
|
|
// 页面加载时获取试管架状态
|
|
onMounted(() => {
|
|
fetchTubeRackState()
|
|
setInterval(fetchTubeRackState, 10000) // 每隔指定时间更新状态
|
|
})
|
|
|
|
onMounted(() => {
|
|
ws.connect()
|
|
ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
|
|
ws.subscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
|
|
})
|
|
onActivated(() => {
|
|
ws.connect()
|
|
ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
|
|
ws.subscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
|
|
})
|
|
|
|
onDeactivated(() => {
|
|
ws.disconnect()
|
|
ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
|
|
ws.unsubscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
ws.disconnect()
|
|
ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
|
|
ws.unsubscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
|
|
})
|
|
|
|
onMounted(() => {
|
|
// 在页面加载时检查是否有急诊数据传递
|
|
// 初次获取路由参数
|
|
fetchIncubationData()
|
|
// 初次加载,处理可能已经存在的数据
|
|
updateProcessedTubeSettings()
|
|
|
|
// 监听 testTubeStore 数据的变化
|
|
watch(
|
|
() => testTubeStore.tubeInfo?.tubeSettings, // 监听目标
|
|
(newTubeSettings) => {
|
|
if (newTubeSettings) {
|
|
// 当 tubeSettings 发生变化时重新处理
|
|
updateProcessedTubeSettings()
|
|
}
|
|
},
|
|
{ deep: true, immediate: true }, // 深度监听 + 初始调用
|
|
)
|
|
})
|
|
|
|
// 监听急诊数据变化
|
|
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) => {
|
|
const subtank: Subtank = {
|
|
pos: `EMERGENCY-${index + 1}`,
|
|
state: 'OCCUPIED',
|
|
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 updateProcessedTubeSettings = () => {
|
|
const tubeSettings = testTubeStore.tubeInfo?.tubeSettings || [] // 获取当前试管数据
|
|
const plateData = plates.value || [] // 获取当前板数据
|
|
|
|
processedTubeSettings.value = processTubeSettings(
|
|
tubeSettings,
|
|
plateData,
|
|
getBloodTypeLabel,
|
|
)
|
|
}
|
|
|
|
//急诊状态
|
|
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
|
|
}
|
|
|
|
// 获取圆心样式:选中时显示蓝色边框
|
|
const getCenterSquareStyle = () => ({
|
|
borderColor: selectedItem.value ? 'blue' : '#ffffff',
|
|
borderStyle: selectedItem.value ? 'solid' : 'none',
|
|
})
|
|
|
|
//被选中的样本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.isPlaceholder ? '#f0f0f0' :
|
|
selectedItemId.value === item.sampleId ? '#1890ff' :
|
|
getRemainingTime(item) === '已完成' ? '#ff4d4f' : 'transparent',
|
|
borderWidth: '3px',
|
|
borderStyle: 'solid',
|
|
}
|
|
}
|
|
|
|
// 获取当前元素样式:若为选中状态则添加蓝色边框,若为完成状态则添加红色边框
|
|
const getItemStyle = (item: Subtank) => {
|
|
if (item.isPlaceholder) {
|
|
return {
|
|
borderColor: '#ffffff', // 占位符边框为白色
|
|
borderStyle: 'solid',
|
|
backgroundColor: '#f8f8f8', // 占位符背景色
|
|
}
|
|
}
|
|
|
|
const remainingTime = getRemainingTime(item)
|
|
return {
|
|
backgroundColor: item.pos === 'EMERGENCY' ? '#ffeded' : item.projInfo.color, // 急诊位特殊背景
|
|
borderColor:
|
|
selectedItemId.value === item.sampleId
|
|
? '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 plates = ref(consumablesStore.plates)
|
|
|
|
//样本数据
|
|
const processedTubeSettings = ref()
|
|
|
|
// 更新选中的样本列表
|
|
const updateSelectedSamples = (sampleIds: number[]) => {
|
|
selectedSamples.value = sampleIds
|
|
}
|
|
|
|
// 计算填充样式
|
|
const getFillStyle = (item: any) => {
|
|
const percentage = (item.tipNum / 120) * 100
|
|
return {
|
|
background: `linear-gradient(to top, #bbd3fb ${percentage}%,#c9c9c9 ${percentage}%)`,
|
|
}
|
|
}
|
|
|
|
//控制废料区的状态
|
|
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
|
|
watch(isFull, (newValue) => {
|
|
if (newValue) {
|
|
console.log('废料箱已满,更新样式并展示弹窗')
|
|
} else {
|
|
console.log('废料箱未满,恢复样式')
|
|
}
|
|
})
|
|
|
|
const pollWasteAreaStatus = async () => {
|
|
try {
|
|
const response = await wasteArea()
|
|
if (response.success) {
|
|
const isWasteBinFull = response.data.wasteBinFullFlag
|
|
|
|
// 更新废料箱状态
|
|
if (isFull.value !== isWasteBinFull) {
|
|
isFull.value = isWasteBinFull
|
|
|
|
// 如果废料箱满,且未展示弹窗,则展示弹窗
|
|
if (isFull.value && !showWasteAlert.value) {
|
|
if (wasteAlertTimeout) clearTimeout(wasteAlertTimeout) // 清除可能存在的防抖计时器
|
|
wasteAlertTimeout = setTimeout(() => {
|
|
showWasteAlert.value = true
|
|
}, 300) // 防抖时间,避免连续触发
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('废料区接口轮询失败:', error)
|
|
}
|
|
}
|
|
|
|
//轮询孵育盘列表
|
|
|
|
// 轮询间隔
|
|
const POLL_INTERVAL = 1000 // 每 秒获取一次最新据
|
|
let pollIntervalId: ReturnType<typeof setInterval>
|
|
|
|
// 获取当前孵育盘列表数据,并初始化倒计时
|
|
|
|
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: '',
|
|
sampleBarcode: '',
|
|
userid: '',
|
|
projInfo: {
|
|
projId: 0,
|
|
projName: '',
|
|
projShortName: '',
|
|
color: '',
|
|
},
|
|
startIncubatedTime: 0,
|
|
incubatedTimeSec: 0,
|
|
sampleId: `PLACEHOLDER-${filledData.length + 1}`,
|
|
projId: 0,
|
|
errors: [],
|
|
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: '',
|
|
sampleBarcode: '',
|
|
userid: '',
|
|
projInfo: {
|
|
projId: 0,
|
|
projName: '',
|
|
projShortName: '',
|
|
color: '',
|
|
},
|
|
startIncubatedTime: 0,
|
|
incubatedTimeSec: 0,
|
|
sampleId: `PLACEHOLDER-${filledData.length + 1}`,
|
|
projId: 0,
|
|
errors: [],
|
|
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) => {
|
|
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 startPolling = () => {
|
|
fetchIncubationData() // 初始化获取数据
|
|
pollIntervalId = setInterval(fetchIncubationData, POLL_INTERVAL) // 设置轮询
|
|
}
|
|
|
|
// 结束轮询
|
|
const stopPolling = () => {
|
|
if (pollIntervalId) clearInterval(pollIntervalId)
|
|
}
|
|
|
|
//设置轮询
|
|
let pollInterval: ReturnType<typeof setInterval>
|
|
onMounted(() => {
|
|
pollInterval = setInterval(pollWasteAreaStatus, 1000) //每一秒轮询一次
|
|
startPolling()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
clearInterval(pollInterval)
|
|
stopPolling()
|
|
})
|
|
|
|
const emergencyResult = ref() // 存储急诊结果
|
|
const isDialogVisible = ref(false) // 控制弹窗的显示
|
|
let emergencyCompleted = ref(false) // 标志位,确保急诊结果只显示一次
|
|
|
|
// 监听急诊位的反应时间并触���弹窗
|
|
const watchEmergencyCompletion = () => {
|
|
watch(
|
|
() => incubationPlates.value,
|
|
(newPlates) => {
|
|
const emergencyPlate = newPlates.find(
|
|
(plate) => plate.pos === 'EMERGENCY',
|
|
)
|
|
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
|
|
})
|
|
|
|
|
|
// 修改试管架状态的类型定义
|
|
interface TubeHolderState {
|
|
tubeHolderType: string
|
|
tubes: Array<{
|
|
sampleId: string | null
|
|
pos: number
|
|
isHighTube: boolean
|
|
isEmergency: boolean
|
|
bloodType: string
|
|
sampleBarcode: string
|
|
userid: string
|
|
projInfo: any[]
|
|
projIds: number[]
|
|
state: string
|
|
errors: string[]
|
|
}>
|
|
state: string
|
|
}
|
|
|
|
// 试管架状态
|
|
const tubeHolderState = ref<TubeHolderState>({
|
|
tubeHolderType: '',
|
|
tubes: [],
|
|
state: 'IDLE'
|
|
})
|
|
|
|
// 处理 WebSocket 消息
|
|
const handleTubeHolderState = (data: TubeHolderStateMessage['data']) => {
|
|
tubeHolderState.value = data
|
|
}
|
|
|
|
onMounted(() => {
|
|
// 订阅 WebSocket 消息
|
|
ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderState)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
// 取消订阅
|
|
ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderState)
|
|
})
|
|
</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: 800px;
|
|
height: 800px;
|
|
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: 74.5%; // 将所有矩形的起始位置设置在圆心
|
|
width: 75px;
|
|
height: 170px;
|
|
transform-origin: center -110px; // 调整旋转中心点
|
|
border-radius: 12px;
|
|
background-color: #721616;
|
|
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);
|
|
}
|
|
}
|
|
.quan {
|
|
width: 18px;
|
|
height: 18px;
|
|
line-height: 18px;
|
|
text-align: center;
|
|
border-radius: 50%;
|
|
border: 2px solid red;
|
|
font-size: 13px;
|
|
}
|
|
.tube-container {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: nowrap;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
height: auto;
|
|
padding: 20px;
|
|
width: 100%;
|
|
}
|
|
</style>
|