|
|
<template> <div id="add-emergency-container"> <!-- 添加急诊 --> <div class="page-header"> <img class="page-header-icon" src="@/assets/Index/left.svg" @click="goBack"></img> <div class="page-header-title">添加急诊</div> </div>
<!-- 急诊信息 --> <!-- <div class="emergency-info" :class="{ 'disable-section': !isEmergencyEnabled }"> <div class="tab-content"> <div class="tab-form" :class="{ isOpacity: !isEmergencyEnabled }"> <div class="line-code"> <label>样本条形码</label> <input type="text" placeholder="请输入样本条形码信息" @focus="showKeyboard('sampleBarcode')" :value="emergencyPosition.sampleBarcode"/> </div> <div class="id-info"> <label>用户ID</label> <input type="text" placeholder="请输入用户ID信息" @focus="showKeyboard('userid')" :value="emergencyPosition.userid" :disabled="!isEmergencyEnabled" readonly /> </div> </div> </div> </div> --> <div class="emergency-main"> <el-row> <el-col :span="5"> <label>样本条形码</label> </el-col> <el-col :span="12" style="padding-left:20px"> <input type="text" placeholder="请输入样本条形码信息" @focus="showKeyboard('sampleBarcode')" style="width:400px" :value="emergencyPosition.sampleBarcode"/> </el-col> </el-row> <el-row> <el-col :span="5"> <label>用户ID</label> </el-col> <el-col :span="12" style="padding-left:20px"> <input type="text" placeholder="请输入用户ID信息" @focus="showKeyboard('userid')" :value="emergencyPosition.userid" style="width:400px" :disabled="!isEmergencyEnabled" readonly /> </el-col> </el-row> </div> <hr style="height: 1px; border: none;" /> <!-- 急诊项目 --> <div class="emergency-project" :class="{ isOpacity: !isEmergencyEnabled }"> <!-- 项目选择内容,根据需要添加 --> <div class="project-title"> <span>项目选择</span> </div> <div class="project-list"> <div v-for="(item, index) in projects" :key="index" @click="selectProject(item)" :class="[ isEmergencyEnabled ? 'project-item' : 'disabled-project-item', { 'active-project-item': emergencyPosition.projIds.includes(item.projId) && isEmergencyEnabled }, ]"> <span :disabled="!isEmergencyEnabled">{{ item.projName }}</span> <span :disabled="!isEmergencyEnabled"> {{ item.num }}/25</span> </div> </div> <hr style="width: 98%; margin: 0 auto; background-color: gray; margin: 10px 0;" /> <div class="project-controller"> <!-- 血液类型 --> <div class="blood-type"> <div class="project-title"> <span>血液类型</span> </div> <div class="type-list"> <button v-for="(item, index) in bloodTypes" :key="index" @click="selectBloodType(item)" :disabled="!isEmergencyEnabled" :class="getTypeClass(item)"> {{ item.projectName }} </button> </div> </div> </div> </div> <hr style="height: 1px; border: none;" /> <!-- 急诊控制 --> <div class="emergency-controller" :class="{ isOpacity: !isEmergencyEnabled }" style="padding:50px 0"> <el-button class="cancel-button" @click="cancelHandle">取消</el-button> <el-button v-if="showSaveBtn" class="ok-button" :disabled="!isEmergencyEnabled" @click="confirmHandle">确定</el-button> </div>
<!-- 键盘 --> <transition name="slide-up"> <div class="keyboard" v-if="keyboardVisible"> <SimpleKeyboard :input="currentInputValue" @onChange="handleKeyboardInput" @onKeyPress="handleKeyPress" /> </div> </transition>
<!-- 试管选择弹窗 --> <div v-if="showTubeSelector" class="tube-selector-overlay"> <div class="tube-selector-container"> <div class="tube-selector-header"> <span class="title">选择试管位置</span> </div> <div class="tube-selector-content"> <div class="tube-grid"> <button v-for="i in 10" :key="i - 1" :class="['tube-button', { 'occupied': isTubeOccupied(i - 1) }]" :disabled="isTubeOccupied(i - 1)" @click="selectTube(i - 1)"> {{ i }} </button> </div> </div> </div> </div> </div> </template>
<script setup lang="ts"> import { ref, onMounted, onUnmounted, onActivated, onDeactivated, watch } from 'vue'; import { useRouter } from 'vue-router'; import { nanoid } from 'nanoid'; import { insertEmergency } from '@/services/Index/index'; import { useEmergencyStore, useConsumablesStore, useDeviceStore } from '@/store'; import type { ReactionPlate, AddEmergencyInfo } from '@/types/Index'; import type { EmergencyPosStateMessage, TubeHolderStateMessage } from '@/websocket/socket'; import { ElMessage } from 'element-plus'; import { createWebSocket } from '@/websocket/socket'; import { getServerInfo } from '@/utils/getServerInfo'; const ws = createWebSocket(getServerInfo().wsUrl); const consumableStore = useConsumablesStore(); const emergencyStore = useEmergencyStore(); const deviceStore = useDeviceStore(); // 急诊位开启/关闭状态
const isEmergencyEnabled = ref(true);//设置表单是否可输入
// 模拟项目列表数据
const projects = ref<ReactionPlate[]>([]); onMounted(() => { // 延迟加载 projects,确保数据从持久化存储中加载完成
if (consumableStore.plates.length > 0) { projects.value = consumableStore.plates as ReactionPlate[]; } //过滤projId为null的数据
projects.value = projects.value.filter(item => item.projId) })
const bloodTypes = ref([ { id: nanoid(), projectName: '血清/血浆', projectType: "SERUM_OR_PLASMA" }, { id: nanoid(), projectName: '全血', projectType: "WHOLE_BLOOD" }, ]);
// 获取类型class
const getTypeClass = (project: { projectType: string }) => { return [ isEmergencyEnabled.value ? 'blood-button' : 'disabled-blood', { 'active-item': bloodType.value === project.projectType && isEmergencyEnabled.value }, ]; };
// 公共状态管理
const projectName = ref(''); const bloodType = ref('');
// 急诊位数据
const emergencyPosition = ref<AddEmergencyInfo>({ sampleBarcode: '', // 样本条形码
userid: '', // 用户ID
projIds: [], // 项目选择
bloodType: '', // 血液类型
});
//获取试管架状态
const tubeRackState = ref<TubeHolderStateMessage['data']>({ tubeHolderType: '', tubes: [], state: 'IDLE' });
//处理试管架状态
const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => { tubeRackState.value = data }
//急诊位可操作状态
const emergencyStateList = ["EMPTY", "TO_BE_PROCESSED", "PROCESS_COMPLETE", "ERROR"] let showSaveBtn = ref<Boolean>(false) onMounted(() => { ws.connect() ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage) }) onActivated(() => { ws.connect() ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage) const { emergencyInfo } = emergencyStore.$state console.log('emergencyInfo---', emergencyInfo) if(emergencyInfo && emergencyInfo.state){ const {state, sampleBarcode} = emergencyInfo currentInputValue.value = sampleBarcode if(emergencyStateList.includes(state)){ showSaveBtn.value = true } } }) onDeactivated(() => { ws.disconnect() ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage); }) onUnmounted(() => { ws.disconnect() ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage); }) // 返回事件处理
const router = useRouter(); const goBack = () => { router.go(-1); };
// 取消和确认事件
const cancelHandle = () => { router.push('/index/regular/consumables'); }; const getProjectInfo = (projIds: number[]) => { const projectInfo = projects.value.filter(item => projIds.includes(item.projId)); return projectInfo; } // 确认请求
const confirmHandle = async () => { //只有设备暂停状态才能添加急诊
// if (deviceStore.status !== 'PAUSE') {
// ElMessage.error('设备未暂停,无法添加急诊');
// return
// }
const emergencyInfo = emergencyPosition.value; if(!emergencyInfo.sampleBarcode){ ElMessage.error('请输入样本条形码'); return } if(!emergencyInfo.userid){ ElMessage.error('请输入用户ID'); return } if (emergencyInfo.projIds.length === 0) { ElMessage.error('请选择项目'); return } if(!emergencyInfo.bloodType){ ElMessage.error('请选择血液类型'); return; } const res = await insertEmergency(emergencyPosition.value); if (res.success) { // 构造急诊位数据
const emergencyData: EmergencyPosStateMessage['data']['tube'] = { pos: 1, state: "OCCUPIED", bloodType: emergencyInfo.bloodType as "SERUM_OR_PLASMA" | "WHOLE_BLOOD", sampleBarcode: emergencyInfo.sampleBarcode || "", userid: emergencyInfo.userid || "", projInfo: getProjectInfo(emergencyInfo.projIds), sampleId: `EMERGENCY-${Date.now()}`, projIds: emergencyInfo.projIds, errors: [], isHighTube: false, isEmergency: true }; emergencyStore.setInfo(emergencyData);
// 跳转到运行中页面并传递数据
router.push({ path: "/index/regular/running", }); } else { console.log("🚀 ~ confirmHandle ~ res:", res) }
};
// 项目选择事件
const selectProject = (item: any) => { if (!isEmergencyEnabled.value) return; if (item === '自动') { // 自动选择清空项目
projectName.value = '自动'; emergencyPosition.value.projIds = []; } else { projectName.value = "" const projectIndex = emergencyPosition.value.projIds.findIndex((proj) => proj === item.projId); if (projectIndex > -1) { // 如果已经选中,则移除该项目
emergencyPosition.value.projIds.splice(projectIndex, 1); } else { // 如果未选中,则添加该项目
emergencyPosition.value.projIds.push(item.projId); } } };
// 血液类型选择事件
const selectBloodType = (item: any) => { if (!isEmergencyEnabled.value) return; if (item === "自动") { // 自动选择清空血液类型
console.log("血液类型为自动"); bloodType.value = '自动'; emergencyPosition.value.bloodType = ''; return } emergencyPosition.value.bloodType = item.projectType; bloodType.value = item.projectType; };
// 切换急诊位开启/关闭状态
const toggleEmergency = () => { isEmergencyEnabled.value = !isEmergencyEnabled.value; if (!isEmergencyEnabled.value) { // 清空数据
emergencyPosition.value = { sampleBarcode: '', userid: '', projIds: [], bloodType: '', }; projectName.value = ''; bloodType.value = ''; } }; watch(isEmergencyEnabled, (newVal) => { if (newVal) { showTubeSelector.value = true } else { selectedTubePos.value = null } })
// 处理回显数据
onMounted(() => { const { emergencyInfo } = emergencyStore.$state if (Object.keys(emergencyInfo).length > 0) { console.log("🚀 ~ onMounted ~ emergencyInfo:", emergencyInfo) isEmergencyEnabled.value = true emergencyPosition.value.bloodType = emergencyInfo.bloodType; emergencyInfo.projInfo.forEach(item => { emergencyPosition.value.projIds.push(item.projId); }) emergencyPosition.value.sampleBarcode = emergencyInfo.sampleBarcode; emergencyPosition.value.userid = emergencyInfo.userid; bloodType.value = emergencyInfo.bloodType; projectName.value = ''; }
}); // 键盘相关状态
const keyboardVisible = ref(false) const currentInputValue = ref('') const currentInputField = ref<'sampleBarcode' | 'userid' | ''>('')
// 显示键盘
const showKeyboard = (field: 'sampleBarcode' | 'userid') => { // 清空当前输入值,避免累加
// currentInputValue.value = ''
if(field == 'sampleBarcode'){ currentInputValue.value = emergencyPosition.value.sampleBarcode } if(field == 'userid'){ currentInputValue.value = emergencyPosition.value.userid } currentInputField.value = field keyboardVisible.value = true }
// 处理键盘输入
const handleKeyboardInput = (value: string) => { if (!currentInputField.value) return console.log('value----', value) // 更新当前输入值
currentInputValue.value = value // 更新对应字段的值
if (currentInputField.value === 'sampleBarcode') { emergencyPosition.value.sampleBarcode = value } else { emergencyPosition.value.userid = value } }
// 处理键盘按键
const handleKeyPress = (button: string) => { if (button === '{enter}') { hideKeyboard() } else if (button === '{bksp}') { // 处理退格键
const value = currentInputValue.value if (value.length > 0) { const newValue = value.slice(0, -1) handleKeyboardInput(newValue) } } }
// 隐藏键盘
const hideKeyboard = () => { keyboardVisible.value = false currentInputField.value = '' currentInputValue.value = '' }
// 在组件卸载时清理状态
onUnmounted(() => { hideKeyboard() })
const showTubeSelector = ref(false) const selectedTubePos = ref<number | null>(1)
// 检查试管是否被占用
const isTubeOccupied = (pos: number) => { return tubeRackState.value.tubes.some(tube => tube.pos === pos && tube.state === 'OCCUPIED' ) }
// 选择试管
const selectTube = (pos: number) => { selectedTubePos.value = pos showTubeSelector.value = false isEmergencyEnabled.value = true }
</script>
<style lang="less" scoped>
input { margin-bottom: 20px; padding: 8px 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 32px; transition: box-shadow 0.2s ease; border-radius: 10px; &::placeholder { font-size: 32px; font-weight: 100; color: #d8d8d8; }
}
label { margin-bottom: 8px; font-size: 32px; font-weight: 700; text-align: left; }
#add-emergency-container { margin: 0; padding: 0; position: relative; height: 100%; width: 100%; background-color: #f4f6f9; box-sizing: border-box;
/* 设置背景色 */ .active-item { background-color: #528dfe; color: white !important; }
.isOpacity { opacity: 0.4;
}
hr { background-color: #e0e0e0; /* 细化分割线的颜色 */ }
.page-header { width: 100%; height: 100px; display: flex; align-items: center; background-color: #ffffff; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); border-radius: 8px;
.page-header-icon { width: 16.14px; height: 26.76px; margin: 0 20px; cursor: pointer; transition: transform 0.2s ease;
&:hover { transform: scale(1.1); /* 放大效果 */ } }
.page-header-title { font-size: 36px; font-weight: 900; line-height: 1.2; color: #333; } }
.emergency-tabs { width: 1200px; height: 118px; display: flex; background-color: #ffffff; display: flex; transition: all 0.3s ease; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); border-radius: 8px; overflow: hidden; margin-top: 20px;
.tab-button { flex: 1; padding: 10px; background-color: #f6f6f6; border: none; cursor: pointer; font-size: 40px; font-weight: 700; line-height: 24px; transition: background-color 0.3s ease, color 0.3s ease;
&:hover { background-color: #eef4ff; /* 悬停时背景色 */ } }
.tab-button.active { background-color: #528dfe; color: white; } }
.emergency-info { width: 100%; height: auto; margin-top: 20px; border-radius: 8px; padding: 20px 0;
.tab-content { display: flex; font-size: 32px; font-weight: 700; padding: 0 40px;
.tab-form { flex: 0.7; padding: 20px;
.line-code, .id-info { display: flex; flex-direction: column; margin-bottom: 20px;
label { margin-bottom: 8px; font-size: 32px; font-weight: 700; text-align: left; }
input { margin-bottom: 20px; padding: 8px 5px; border: 1px solid #ccc; border-radius: 4px; width: 80%; font-size: 32px; transition: box-shadow 0.2s ease; border-radius: 10px; &::placeholder { font-size: 32px; font-weight: 100; color: #d8d8d8; }
//自定义获取焦点后的样式
&:focus { outline: none; // 去掉默认的聚焦边框
border-color: #528dfe; box-shadow: 0 0 5px rgba(181, 189, 181, 0.5); /* 添加阴影效果 */
} } } }
.active-button { flex: 0.3; display: flex; align-items: center; justify-content: center;
.open-button { width: 80%; height: 70%; background-color: #4caf50; /* 开启状态颜色:绿色 */ color: white; font-size: 40px; font-weight: 600; border: none; border-radius: 6px; cursor: pointer; transition: background-color 0.3s ease, transform 0.2s ease; }
.close-button { width: 80%; height: 70%; background-color: #f44336; /* 关闭状态颜色:红色 */ color: white; font-size: 40px; font-weight: 600; border: none; border-radius: 6px; cursor: pointer; transition: background-color 0.3s ease, transform 0.2s ease; }
button { width: 100%; height: 70%; background-color: #528dfe; color: white; border: none; border-radius: 7px; cursor: pointer; margin-top: 50px; font-size: 40px; transition: background-color 0.3s ease, transform 0.2s ease; }
}
}
}
.emergency-project { width: 100%; padding: 20px 40px; border-radius: 10px; margin-top: 20px; box-sizing: border-box; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
.disabled-item { color: gray; border: 1px solid gray; height: 70px; width: 150px; background-color: white; border-radius: 8px; margin-right: 20px; font-size: 32px; }
.project-auto { height: 70px; width: 150px; border: 1px solid #4a90e2; color: #4a90e2; border-radius: 8px; margin-right: 20px; cursor: pointer; font-size: 32px; transition: all 0.3s ease }
//禁用状态下的样式
.project-title { width: 100%; text-align: left; margin-bottom: 10px; gap: 10px;
span { font-size: 32px; font-weight: 700; } }
.project-list { display: flex; align-items: center; padding: 10px 0; border-radius: 8px; justify-content: flex-start;
.active-project-item { background-color: #528dfe !important; color: white !important; }
.disabled-item { color: gray; border: 1px solid gray; height: 70px; width: 150px; background-color: white; border-radius: 8px; margin-right: 20px; font-size: 32px; }
.project-item { width: 150px; display: flex; flex-direction: column; align-items: center; padding: 0 10px; color: #4a90e2; background-color: #f2f2f2; font-size: 24px; text-align: center; position: relative; border-radius: 8px;
&::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 1px; height: 60%; background-color: #ccc; } }
.disabled-project-item { width: 150px; display: flex; flex-direction: column; align-items: center; padding: 0 10px; color: gray; font-size: 24px; text-align: center; position: relative; background-color: #f2f2f2; border-radius: 8px; pointer-events: none;
&::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 1px; height: 60%; background-color: #ccc; } }
}
.project-controller { display: flex; width: 100%; margin: 20px 0; justify-content: space-between; gap: 20px;
.blood-type { width: 60%; display: flex; flex-direction: column;
.type-list { display: flex; gap: 15px; justify-content: space-between; padding: 10px 0 10px;
.blood-button, .disabled-blood { flex: 1; min-width: 150px; padding: 15px; font-size: 32px; border-radius: 8px; text-align: center; transition: all 0.3s ease }
.blood-button { border: 1px solid #5c94fe; border-radius: 8px; padding: 0; color: #4a90e2; border-radius: 8px; margin-right: 20px; cursor: pointer; font-size: 32px; }
.disabled-blood {
border: 1px solid gray; border-radius: 8px; padding: 0; color: gray; border-radius: 8px; margin-right: 20px; cursor: none; font-size: 32px; }
} }
.sample-attenuation { flex: 1; display: flex; flex-direction: column; position: relative;
.active-item { background-color: #528dfe; color: white !important; }
.project-title { margin-left: 20px; }
&::before { content: ""; position: absolute; left: 0; top: 70%; transform: translateY(-50%); width: 2px; height: 40%; background-color: #ccc; }
.sample-button { width: 80%; margin: 0 20px; height: 74px; border-radius: 8px; border: 1px solid #5c94fe; color: #4a90e2; border-radius: 8px; margin-right: 20px; cursor: pointer; font-size: 32px; }
.disabled-sample-button { width: 80%; margin: 0 20px; height: 74px; border-radius: 8px; border: 1px solid gray; color: gray; border-radius: 8px; margin-right: 20px; cursor: pointer; font-size: 32px; } } } }
.emergency-controller { width: 100%; height: 120px; display: flex; margin-top: 20px;
display: flex; justify-content: center; align-items: center;
.cancel-button, .ok-button { width: 365px; height: 100px; border-radius: 30px; font-size: 40px; font-weight: 400; margin: 0 10px; border: none; cursor: pointer; transition: background-color 0.3s ease, transform 0.2s ease; }
.ok-button { background-color: #528dfe; color: white; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.cancel-button { background-color: #f2f2f2; color: black; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } }
.keyboard { position: fixed; bottom: 0; left: 0; width: 100%; height: 300px; background-color: #f5f7fa; border-top-left-radius: 16px; border-top-right-radius: 16px; box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1); z-index: 1000; }
// 键盘动画
.slide-up-enter-active, .slide-up-leave-active { transition: transform 0.3s ease; }
.slide-up-enter-from, .slide-up-leave-to { transform: translateY(100%); }
.tube-selector-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); display: flex; justify-content: center; align-items: center; z-index: 10000; }
.tube-selector-container { background: white; border-radius: 8px; padding: 20px; width: 600px;
.tube-selector-header { text-align: center; margin-bottom: 20px;
.title { font-size: 32px; font-weight: bold; } }
.tube-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; padding: 20px;
.tube-button { padding: 20px; font-size: 24px; border: 2px solid #409eff; border-radius: 8px; background: white; cursor: pointer; transition: all 0.3s;
&:hover:not(:disabled) { background: #409eff; color: white; }
&.occupied { border-color: #909399; background: #f4f4f5; cursor: not-allowed; } } } } } .emergency-main{ margin-top:40px; margin-left:80px } </style>
|