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.

1024 lines
25 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
7 months ago
8 months ago
7 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
  1. <template>
  2. <div id="add-emergency-container">
  3. <!-- 添加急诊 -->
  4. <div class="page-header">
  5. <img class="page-header-icon" src="@/assets/Index/left.svg" @click="goBack"></img>
  6. <div class="page-header-title">添加急诊</div>
  7. </div>
  8. <!-- 急诊信息 -->
  9. <!-- <div class="emergency-info" :class="{ 'disable-section': !isEmergencyEnabled }">
  10. <div class="tab-content">
  11. <div class="tab-form" :class="{ isOpacity: !isEmergencyEnabled }">
  12. <div class="line-code">
  13. <label>样本条形码</label>
  14. <input type="text" placeholder="请输入样本条形码信息" @focus="showKeyboard('sampleBarcode')"
  15. :value="emergencyPosition.sampleBarcode"/>
  16. </div>
  17. <div class="id-info">
  18. <label>用户ID</label>
  19. <input type="text" placeholder="请输入用户ID信息" @focus="showKeyboard('userid')" :value="emergencyPosition.userid"
  20. :disabled="!isEmergencyEnabled" readonly />
  21. </div>
  22. </div>
  23. </div>
  24. </div> -->
  25. <div class="emergency-main">
  26. <el-row>
  27. <el-col :span="5">
  28. <label>样本条形码</label>
  29. </el-col>
  30. <el-col :span="12" style="padding-left:20px">
  31. <input type="text" placeholder="请输入样本条形码信息" @focus="showKeyboard('sampleBarcode')" style="width:400px"
  32. :value="emergencyPosition.sampleBarcode"/>
  33. </el-col>
  34. </el-row>
  35. <el-row>
  36. <el-col :span="5">
  37. <label>用户ID</label>
  38. </el-col>
  39. <el-col :span="12" style="padding-left:20px">
  40. <input type="text" placeholder="请输入用户ID信息" @focus="showKeyboard('userid')" :value="emergencyPosition.userid" style="width:400px"
  41. :disabled="!isEmergencyEnabled" readonly />
  42. </el-col>
  43. </el-row>
  44. </div>
  45. <hr style="height: 1px; border: none;" />
  46. <!-- 急诊项目 -->
  47. <div class="emergency-project" :class="{ isOpacity: !isEmergencyEnabled }">
  48. <!-- 项目选择内容根据需要添加 -->
  49. <div class="project-title">
  50. <span>项目选择</span>
  51. </div>
  52. <div class="project-list">
  53. <div v-for="(item, index) in projects" :key="index" @click="selectProject(item)" :class="[
  54. isEmergencyEnabled ? 'project-item' : 'disabled-project-item',
  55. { 'active-project-item': emergencyPosition.projIds.includes(item.projId) && isEmergencyEnabled },
  56. ]">
  57. <span :disabled="!isEmergencyEnabled">{{ item.projName }}</span>
  58. <span :disabled="!isEmergencyEnabled"> {{ item.num }}/25</span>
  59. </div>
  60. </div>
  61. <hr style="width: 98%; margin: 0 auto; background-color: gray; margin: 10px 0;" />
  62. <div class="project-controller">
  63. <!-- 血液类型 -->
  64. <div class="blood-type">
  65. <div class="project-title">
  66. <span>血液类型</span>
  67. </div>
  68. <div class="type-list">
  69. <button v-for="(item, index) in bloodTypes" :key="index" @click="selectBloodType(item)"
  70. :disabled="!isEmergencyEnabled" :class="getTypeClass(item)">
  71. {{ item.projectName }}
  72. </button>
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. <hr style="height: 1px; border: none;" />
  78. <!-- 急诊控制 -->
  79. <div class="emergency-controller" :class="{ isOpacity: !isEmergencyEnabled }" style="padding:50px 0">
  80. <el-button class="cancel-button" @click="cancelHandle">取消</el-button>
  81. <el-button v-if="showSaveBtn" class="ok-button" :disabled="!isEmergencyEnabled" @click="confirmHandle">确定</el-button>
  82. </div>
  83. <!-- 键盘 -->
  84. <transition name="slide-up">
  85. <div class="keyboard" v-if="keyboardVisible">
  86. <SimpleKeyboard :input="currentInputValue" @onChange="handleKeyboardInput" @onKeyPress="handleKeyPress" />
  87. </div>
  88. </transition>
  89. <!-- 试管选择弹窗 -->
  90. <div v-if="showTubeSelector" class="tube-selector-overlay">
  91. <div class="tube-selector-container">
  92. <div class="tube-selector-header">
  93. <span class="title">选择试管位置</span>
  94. </div>
  95. <div class="tube-selector-content">
  96. <div class="tube-grid">
  97. <button v-for="i in 10" :key="i - 1" :class="['tube-button', { 'occupied': isTubeOccupied(i - 1) }]"
  98. :disabled="isTubeOccupied(i - 1)" @click="selectTube(i - 1)">
  99. {{ i }}
  100. </button>
  101. </div>
  102. </div>
  103. </div>
  104. </div>
  105. </div>
  106. </template>
  107. <script setup lang="ts">
  108. import { ref, onMounted, onUnmounted, onActivated, onDeactivated, watch } from 'vue';
  109. import { useRouter } from 'vue-router';
  110. import { nanoid } from 'nanoid';
  111. import { insertEmergency } from '@/services/Index/index';
  112. import { useEmergencyStore, useConsumablesStore, useDeviceStore } from '@/store';
  113. import type { ReactionPlate, AddEmergencyInfo } from '@/types/Index';
  114. import type { EmergencyPosStateMessage, TubeHolderStateMessage } from '@/websocket/socket';
  115. import { ElMessage } from 'element-plus';
  116. import { createWebSocket } from '@/websocket/socket';
  117. import { getServerInfo } from '@/utils/getServerInfo';
  118. const ws = createWebSocket(getServerInfo().wsUrl);
  119. const consumableStore = useConsumablesStore();
  120. const emergencyStore = useEmergencyStore();
  121. const deviceStore = useDeviceStore();
  122. // 急诊位开启/关闭状态
  123. const isEmergencyEnabled = ref(true);//设置表单是否可输入
  124. // 模拟项目列表数据
  125. const projects = ref<ReactionPlate[]>([]);
  126. onMounted(() => {
  127. // 延迟加载 projects,确保数据从持久化存储中加载完成
  128. if (consumableStore.plates.length > 0) {
  129. projects.value = consumableStore.plates as ReactionPlate[];
  130. }
  131. //过滤projId为null的数据
  132. projects.value = projects.value.filter(item => item.projId)
  133. })
  134. const bloodTypes = ref([
  135. {
  136. id: nanoid(),
  137. projectName: '血清/血浆',
  138. projectType: "SERUM_OR_PLASMA"
  139. },
  140. {
  141. id: nanoid(),
  142. projectName: '全血',
  143. projectType: "WHOLE_BLOOD"
  144. },
  145. ]);
  146. // 获取类型class
  147. const getTypeClass = (project: { projectType: string }) => {
  148. return [
  149. isEmergencyEnabled.value ? 'blood-button' : 'disabled-blood',
  150. { 'active-item': bloodType.value === project.projectType && isEmergencyEnabled.value },
  151. ];
  152. };
  153. // 公共状态管理
  154. const projectName = ref('');
  155. const bloodType = ref('');
  156. // 急诊位数据
  157. const emergencyPosition = ref<AddEmergencyInfo>({
  158. sampleBarcode: '', // 样本条形码
  159. userid: '', // 用户ID
  160. projIds: [], // 项目选择
  161. bloodType: '', // 血液类型
  162. });
  163. //获取试管架状态
  164. const tubeRackState = ref<TubeHolderStateMessage['data']>({
  165. tubeHolderType: '',
  166. tubes: [],
  167. state: 'IDLE'
  168. });
  169. //处理试管架状态
  170. const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
  171. tubeRackState.value = data
  172. }
  173. //急诊位可操作状态
  174. const emergencyStateList = ["EMPTY", "TO_BE_PROCESSED", "PROCESS_COMPLETE", "ERROR"]
  175. let showSaveBtn = ref<Boolean>(false)
  176. onMounted(() => {
  177. ws.connect()
  178. ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
  179. })
  180. onActivated(() => {
  181. ws.connect()
  182. ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
  183. const { emergencyInfo } = emergencyStore.$state
  184. console.log('emergencyInfo---', emergencyInfo)
  185. if(emergencyInfo && emergencyInfo.state){
  186. const {state, sampleBarcode} = emergencyInfo
  187. currentInputValue.value = sampleBarcode
  188. if(emergencyStateList.includes(state)){
  189. showSaveBtn.value = true
  190. }
  191. }
  192. })
  193. onDeactivated(() => {
  194. ws.disconnect()
  195. ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage);
  196. })
  197. onUnmounted(() => {
  198. ws.disconnect()
  199. ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage);
  200. })
  201. // 返回事件处理
  202. const router = useRouter();
  203. const goBack = () => {
  204. router.go(-1);
  205. };
  206. // 取消和确认事件
  207. const cancelHandle = () => {
  208. router.push('/index/regular/consumables');
  209. };
  210. const getProjectInfo = (projIds: number[]) => {
  211. const projectInfo = projects.value.filter(item => projIds.includes(item.projId));
  212. return projectInfo;
  213. }
  214. // 确认请求
  215. const confirmHandle = async () => {
  216. //只有设备暂停状态才能添加急诊
  217. // if (deviceStore.status !== 'PAUSE') {
  218. // ElMessage.error('设备未暂停,无法添加急诊');
  219. // return
  220. // }
  221. const emergencyInfo = emergencyPosition.value;
  222. if(!emergencyInfo.sampleBarcode){
  223. ElMessage.error('请输入样本条形码');
  224. return
  225. }
  226. if(!emergencyInfo.userid){
  227. ElMessage.error('请输入用户ID');
  228. return
  229. }
  230. if (emergencyInfo.projIds.length === 0) {
  231. ElMessage.error('请选择项目');
  232. return
  233. }
  234. if(!emergencyInfo.bloodType){
  235. ElMessage.error('请选择血液类型');
  236. return;
  237. }
  238. const res = await insertEmergency(emergencyPosition.value);
  239. if (res.success) {
  240. // 构造急诊位数据
  241. const emergencyData: EmergencyPosStateMessage['data']['tube'] = {
  242. pos: 1,
  243. state: "OCCUPIED",
  244. bloodType: emergencyInfo.bloodType as "SERUM_OR_PLASMA" | "WHOLE_BLOOD",
  245. sampleBarcode: emergencyInfo.sampleBarcode || "",
  246. userid: emergencyInfo.userid || "",
  247. projInfo: getProjectInfo(emergencyInfo.projIds),
  248. sampleId: `EMERGENCY-${Date.now()}`,
  249. projIds: emergencyInfo.projIds,
  250. errors: [],
  251. isHighTube: false,
  252. isEmergency: true
  253. };
  254. emergencyStore.setInfo(emergencyData);
  255. // 跳转到运行中页面并传递数据
  256. router.push({
  257. path: "/index/regular/running",
  258. });
  259. } else {
  260. console.log("🚀 ~ confirmHandle ~ res:", res)
  261. }
  262. };
  263. // 项目选择事件
  264. const selectProject = (item: any) => {
  265. if (!isEmergencyEnabled.value) return;
  266. if (item === '自动') {
  267. // 自动选择清空项目
  268. projectName.value = '自动';
  269. emergencyPosition.value.projIds = [];
  270. } else {
  271. projectName.value = ""
  272. const projectIndex = emergencyPosition.value.projIds.findIndex((proj) => proj === item.projId);
  273. if (projectIndex > -1) {
  274. // 如果已经选中,则移除该项目
  275. emergencyPosition.value.projIds.splice(projectIndex, 1);
  276. } else {
  277. // 如果未选中,则添加该项目
  278. emergencyPosition.value.projIds.push(item.projId);
  279. }
  280. }
  281. };
  282. // 血液类型选择事件
  283. const selectBloodType = (item: any) => {
  284. if (!isEmergencyEnabled.value) return;
  285. if (item === "自动") {
  286. // 自动选择清空血液类型
  287. console.log("血液类型为自动");
  288. bloodType.value = '自动';
  289. emergencyPosition.value.bloodType = '';
  290. return
  291. }
  292. emergencyPosition.value.bloodType = item.projectType;
  293. bloodType.value = item.projectType;
  294. };
  295. // 切换急诊位开启/关闭状态
  296. const toggleEmergency = () => {
  297. isEmergencyEnabled.value = !isEmergencyEnabled.value;
  298. if (!isEmergencyEnabled.value) {
  299. // 清空数据
  300. emergencyPosition.value = {
  301. sampleBarcode: '',
  302. userid: '',
  303. projIds: [],
  304. bloodType: '',
  305. };
  306. projectName.value = '';
  307. bloodType.value = '';
  308. }
  309. };
  310. watch(isEmergencyEnabled, (newVal) => {
  311. if (newVal) {
  312. showTubeSelector.value = true
  313. } else {
  314. selectedTubePos.value = null
  315. }
  316. })
  317. // 处理回显数据
  318. onMounted(() => {
  319. const { emergencyInfo } = emergencyStore.$state
  320. if (Object.keys(emergencyInfo).length > 0) {
  321. console.log("🚀 ~ onMounted ~ emergencyInfo:", emergencyInfo)
  322. isEmergencyEnabled.value = true
  323. emergencyPosition.value.bloodType = emergencyInfo.bloodType;
  324. emergencyInfo.projInfo.forEach(item => {
  325. emergencyPosition.value.projIds.push(item.projId);
  326. })
  327. emergencyPosition.value.sampleBarcode = emergencyInfo.sampleBarcode;
  328. emergencyPosition.value.userid = emergencyInfo.userid;
  329. bloodType.value = emergencyInfo.bloodType;
  330. projectName.value = '';
  331. }
  332. });
  333. // 键盘相关状态
  334. const keyboardVisible = ref(false)
  335. const currentInputValue = ref('')
  336. const currentInputField = ref<'sampleBarcode' | 'userid' | ''>('')
  337. // 显示键盘
  338. const showKeyboard = (field: 'sampleBarcode' | 'userid') => {
  339. // 清空当前输入值,避免累加
  340. // currentInputValue.value = ''
  341. if(field == 'sampleBarcode'){
  342. currentInputValue.value = emergencyPosition.value.sampleBarcode
  343. }
  344. if(field == 'userid'){
  345. currentInputValue.value = emergencyPosition.value.userid
  346. }
  347. currentInputField.value = field
  348. keyboardVisible.value = true
  349. }
  350. // 处理键盘输入
  351. const handleKeyboardInput = (value: string) => {
  352. if (!currentInputField.value) return
  353. console.log('value----', value)
  354. // 更新当前输入值
  355. currentInputValue.value = value
  356. // 更新对应字段的值
  357. if (currentInputField.value === 'sampleBarcode') {
  358. emergencyPosition.value.sampleBarcode = value
  359. } else {
  360. emergencyPosition.value.userid = value
  361. }
  362. }
  363. // 处理键盘按键
  364. const handleKeyPress = (button: string) => {
  365. if (button === '{enter}') {
  366. hideKeyboard()
  367. } else if (button === '{bksp}') {
  368. // 处理退格键
  369. const value = currentInputValue.value
  370. if (value.length > 0) {
  371. const newValue = value.slice(0, -1)
  372. handleKeyboardInput(newValue)
  373. }
  374. }
  375. }
  376. // 隐藏键盘
  377. const hideKeyboard = () => {
  378. keyboardVisible.value = false
  379. currentInputField.value = ''
  380. currentInputValue.value = ''
  381. }
  382. // 在组件卸载时清理状态
  383. onUnmounted(() => {
  384. hideKeyboard()
  385. })
  386. const showTubeSelector = ref(false)
  387. const selectedTubePos = ref<number | null>(1)
  388. // 检查试管是否被占用
  389. const isTubeOccupied = (pos: number) => {
  390. return tubeRackState.value.tubes.some(tube =>
  391. tube.pos === pos && tube.state === 'OCCUPIED'
  392. )
  393. }
  394. // 选择试管
  395. const selectTube = (pos: number) => {
  396. selectedTubePos.value = pos
  397. showTubeSelector.value = false
  398. isEmergencyEnabled.value = true
  399. }
  400. </script>
  401. <style lang="less" scoped>
  402. input {
  403. margin-bottom: 20px;
  404. padding: 8px 5px;
  405. border: 1px solid #ccc;
  406. border-radius: 4px;
  407. font-size: 32px;
  408. transition: box-shadow 0.2s ease;
  409. border-radius: 10px;
  410. &::placeholder {
  411. font-size: 32px;
  412. font-weight: 100;
  413. color: #d8d8d8;
  414. }
  415. }
  416. label {
  417. margin-bottom: 8px;
  418. font-size: 32px;
  419. font-weight: 700;
  420. text-align: left;
  421. }
  422. #add-emergency-container {
  423. margin: 0;
  424. padding: 0;
  425. position: relative;
  426. height: 100%;
  427. width: 100%;
  428. background-color: #f4f6f9;
  429. box-sizing: border-box;
  430. /* 设置背景色 */
  431. .active-item {
  432. background-color: #528dfe;
  433. color: white !important;
  434. }
  435. .isOpacity {
  436. opacity: 0.4;
  437. }
  438. hr {
  439. background-color: #e0e0e0;
  440. /* 细化分割线的颜色 */
  441. }
  442. .page-header {
  443. width: 100%;
  444. height: 100px;
  445. display: flex;
  446. align-items: center;
  447. background-color: #ffffff;
  448. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  449. border-radius: 8px;
  450. .page-header-icon {
  451. width: 16.14px;
  452. height: 26.76px;
  453. margin: 0 20px;
  454. cursor: pointer;
  455. transition: transform 0.2s ease;
  456. &:hover {
  457. transform: scale(1.1);
  458. /* 放大效果 */
  459. }
  460. }
  461. .page-header-title {
  462. font-size: 36px;
  463. font-weight: 900;
  464. line-height: 1.2;
  465. color: #333;
  466. }
  467. }
  468. .emergency-tabs {
  469. width: 1200px;
  470. height: 118px;
  471. display: flex;
  472. background-color: #ffffff;
  473. display: flex;
  474. transition: all 0.3s ease;
  475. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
  476. border-radius: 8px;
  477. overflow: hidden;
  478. margin-top: 20px;
  479. .tab-button {
  480. flex: 1;
  481. padding: 10px;
  482. background-color: #f6f6f6;
  483. border: none;
  484. cursor: pointer;
  485. font-size: 40px;
  486. font-weight: 700;
  487. line-height: 24px;
  488. transition: background-color 0.3s ease, color 0.3s ease;
  489. &:hover {
  490. background-color: #eef4ff;
  491. /* 悬停时背景色 */
  492. }
  493. }
  494. .tab-button.active {
  495. background-color: #528dfe;
  496. color: white;
  497. }
  498. }
  499. .emergency-info {
  500. width: 100%;
  501. height: auto;
  502. margin-top: 20px;
  503. border-radius: 8px;
  504. padding: 20px 0;
  505. .tab-content {
  506. display: flex;
  507. font-size: 32px;
  508. font-weight: 700;
  509. padding: 0 40px;
  510. .tab-form {
  511. flex: 0.7;
  512. padding: 20px;
  513. .line-code,
  514. .id-info {
  515. display: flex;
  516. flex-direction: column;
  517. margin-bottom: 20px;
  518. label {
  519. margin-bottom: 8px;
  520. font-size: 32px;
  521. font-weight: 700;
  522. text-align: left;
  523. }
  524. input {
  525. margin-bottom: 20px;
  526. padding: 8px 5px;
  527. border: 1px solid #ccc;
  528. border-radius: 4px;
  529. width: 80%;
  530. font-size: 32px;
  531. transition: box-shadow 0.2s ease;
  532. border-radius: 10px;
  533. &::placeholder {
  534. font-size: 32px;
  535. font-weight: 100;
  536. color: #d8d8d8;
  537. }
  538. //自定义获取焦点后的样式
  539. &:focus {
  540. outline: none; // 去掉默认的聚焦边框
  541. border-color: #528dfe;
  542. box-shadow: 0 0 5px rgba(181, 189, 181, 0.5);
  543. /* 添加阴影效果 */
  544. }
  545. }
  546. }
  547. }
  548. .active-button {
  549. flex: 0.3;
  550. display: flex;
  551. align-items: center;
  552. justify-content: center;
  553. .open-button {
  554. width: 80%;
  555. height: 70%;
  556. background-color: #4caf50;
  557. /* 开启状态颜色:绿色 */
  558. color: white;
  559. font-size: 40px;
  560. font-weight: 600;
  561. border: none;
  562. border-radius: 6px;
  563. cursor: pointer;
  564. transition: background-color 0.3s ease, transform 0.2s ease;
  565. }
  566. .close-button {
  567. width: 80%;
  568. height: 70%;
  569. background-color: #f44336;
  570. /* 关闭状态颜色:红色 */
  571. color: white;
  572. font-size: 40px;
  573. font-weight: 600;
  574. border: none;
  575. border-radius: 6px;
  576. cursor: pointer;
  577. transition: background-color 0.3s ease, transform 0.2s ease;
  578. }
  579. button {
  580. width: 100%;
  581. height: 70%;
  582. background-color: #528dfe;
  583. color: white;
  584. border: none;
  585. border-radius: 7px;
  586. cursor: pointer;
  587. margin-top: 50px;
  588. font-size: 40px;
  589. transition: background-color 0.3s ease, transform 0.2s ease;
  590. }
  591. }
  592. }
  593. }
  594. .emergency-project {
  595. width: 100%;
  596. padding: 20px 40px;
  597. border-radius: 10px;
  598. margin-top: 20px;
  599. box-sizing: border-box;
  600. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  601. .disabled-item {
  602. color: gray;
  603. border: 1px solid gray;
  604. height: 70px;
  605. width: 150px;
  606. background-color: white;
  607. border-radius: 8px;
  608. margin-right: 20px;
  609. font-size: 32px;
  610. }
  611. .project-auto {
  612. height: 70px;
  613. width: 150px;
  614. border: 1px solid #4a90e2;
  615. color: #4a90e2;
  616. border-radius: 8px;
  617. margin-right: 20px;
  618. cursor: pointer;
  619. font-size: 32px;
  620. transition: all 0.3s ease
  621. }
  622. //禁用状态下的样式
  623. .project-title {
  624. width: 100%;
  625. text-align: left;
  626. margin-bottom: 10px;
  627. gap: 10px;
  628. span {
  629. font-size: 32px;
  630. font-weight: 700;
  631. }
  632. }
  633. .project-list {
  634. display: flex;
  635. align-items: center;
  636. padding: 10px 0;
  637. border-radius: 8px;
  638. justify-content: flex-start;
  639. .active-project-item {
  640. background-color: #528dfe !important;
  641. color: white !important;
  642. }
  643. .disabled-item {
  644. color: gray;
  645. border: 1px solid gray;
  646. height: 70px;
  647. width: 150px;
  648. background-color: white;
  649. border-radius: 8px;
  650. margin-right: 20px;
  651. font-size: 32px;
  652. }
  653. .project-item {
  654. width: 150px;
  655. display: flex;
  656. flex-direction: column;
  657. align-items: center;
  658. padding: 0 10px;
  659. color: #4a90e2;
  660. background-color: #f2f2f2;
  661. font-size: 24px;
  662. text-align: center;
  663. position: relative;
  664. border-radius: 8px;
  665. &::before {
  666. content: "";
  667. position: absolute;
  668. left: 0;
  669. top: 50%;
  670. transform: translateY(-50%);
  671. width: 1px;
  672. height: 60%;
  673. background-color: #ccc;
  674. }
  675. }
  676. .disabled-project-item {
  677. width: 150px;
  678. display: flex;
  679. flex-direction: column;
  680. align-items: center;
  681. padding: 0 10px;
  682. color: gray;
  683. font-size: 24px;
  684. text-align: center;
  685. position: relative;
  686. background-color: #f2f2f2;
  687. border-radius: 8px;
  688. pointer-events: none;
  689. &::before {
  690. content: "";
  691. position: absolute;
  692. left: 0;
  693. top: 50%;
  694. transform: translateY(-50%);
  695. width: 1px;
  696. height: 60%;
  697. background-color: #ccc;
  698. }
  699. }
  700. }
  701. .project-controller {
  702. display: flex;
  703. width: 100%;
  704. margin: 20px 0;
  705. justify-content: space-between;
  706. gap: 20px;
  707. .blood-type {
  708. width: 60%;
  709. display: flex;
  710. flex-direction: column;
  711. .type-list {
  712. display: flex;
  713. gap: 15px;
  714. justify-content: space-between;
  715. padding: 10px 0 10px;
  716. .blood-button,
  717. .disabled-blood {
  718. flex: 1;
  719. min-width: 150px;
  720. padding: 15px;
  721. font-size: 32px;
  722. border-radius: 8px;
  723. text-align: center;
  724. transition: all 0.3s ease
  725. }
  726. .blood-button {
  727. border: 1px solid #5c94fe;
  728. border-radius: 8px;
  729. padding: 0;
  730. color: #4a90e2;
  731. border-radius: 8px;
  732. margin-right: 20px;
  733. cursor: pointer;
  734. font-size: 32px;
  735. }
  736. .disabled-blood {
  737. border: 1px solid gray;
  738. border-radius: 8px;
  739. padding: 0;
  740. color: gray;
  741. border-radius: 8px;
  742. margin-right: 20px;
  743. cursor: none;
  744. font-size: 32px;
  745. }
  746. }
  747. }
  748. .sample-attenuation {
  749. flex: 1;
  750. display: flex;
  751. flex-direction: column;
  752. position: relative;
  753. .active-item {
  754. background-color: #528dfe;
  755. color: white !important;
  756. }
  757. .project-title {
  758. margin-left: 20px;
  759. }
  760. &::before {
  761. content: "";
  762. position: absolute;
  763. left: 0;
  764. top: 70%;
  765. transform: translateY(-50%);
  766. width: 2px;
  767. height: 40%;
  768. background-color: #ccc;
  769. }
  770. .sample-button {
  771. width: 80%;
  772. margin: 0 20px;
  773. height: 74px;
  774. border-radius: 8px;
  775. border: 1px solid #5c94fe;
  776. color: #4a90e2;
  777. border-radius: 8px;
  778. margin-right: 20px;
  779. cursor: pointer;
  780. font-size: 32px;
  781. }
  782. .disabled-sample-button {
  783. width: 80%;
  784. margin: 0 20px;
  785. height: 74px;
  786. border-radius: 8px;
  787. border: 1px solid gray;
  788. color: gray;
  789. border-radius: 8px;
  790. margin-right: 20px;
  791. cursor: pointer;
  792. font-size: 32px;
  793. }
  794. }
  795. }
  796. }
  797. .emergency-controller {
  798. width: 100%;
  799. height: 120px;
  800. display: flex;
  801. margin-top: 20px;
  802. display: flex;
  803. justify-content: center;
  804. align-items: center;
  805. .cancel-button,
  806. .ok-button {
  807. width: 365px;
  808. height: 100px;
  809. border-radius: 30px;
  810. font-size: 40px;
  811. font-weight: 400;
  812. margin: 0 10px;
  813. border: none;
  814. cursor: pointer;
  815. transition: background-color 0.3s ease, transform 0.2s ease;
  816. }
  817. .ok-button {
  818. background-color: #528dfe;
  819. color: white;
  820. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  821. }
  822. .cancel-button {
  823. background-color: #f2f2f2;
  824. color: black;
  825. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  826. }
  827. }
  828. .keyboard {
  829. position: fixed;
  830. bottom: 0;
  831. left: 0;
  832. width: 100%;
  833. height: 300px;
  834. background-color: #f5f7fa;
  835. border-top-left-radius: 16px;
  836. border-top-right-radius: 16px;
  837. box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
  838. z-index: 1000;
  839. }
  840. // 键盘动画
  841. .slide-up-enter-active,
  842. .slide-up-leave-active {
  843. transition: transform 0.3s ease;
  844. }
  845. .slide-up-enter-from,
  846. .slide-up-leave-to {
  847. transform: translateY(100%);
  848. }
  849. .tube-selector-overlay {
  850. position: fixed;
  851. top: 0;
  852. left: 0;
  853. width: 100vw;
  854. height: 100vh;
  855. background: rgba(0, 0, 0, 0.7);
  856. backdrop-filter: blur(4px);
  857. display: flex;
  858. justify-content: center;
  859. align-items: center;
  860. z-index: 10000;
  861. }
  862. .tube-selector-container {
  863. background: white;
  864. border-radius: 8px;
  865. padding: 20px;
  866. width: 600px;
  867. .tube-selector-header {
  868. text-align: center;
  869. margin-bottom: 20px;
  870. .title {
  871. font-size: 32px;
  872. font-weight: bold;
  873. }
  874. }
  875. .tube-grid {
  876. display: grid;
  877. grid-template-columns: repeat(5, 1fr);
  878. gap: 15px;
  879. padding: 20px;
  880. .tube-button {
  881. padding: 20px;
  882. font-size: 24px;
  883. border: 2px solid #409eff;
  884. border-radius: 8px;
  885. background: white;
  886. cursor: pointer;
  887. transition: all 0.3s;
  888. &:hover:not(:disabled) {
  889. background: #409eff;
  890. color: white;
  891. }
  892. &.occupied {
  893. border-color: #909399;
  894. background: #f4f4f5;
  895. cursor: not-allowed;
  896. }
  897. }
  898. }
  899. }
  900. }
  901. .emergency-main{
  902. margin-top:40px;
  903. margin-left:80px
  904. }
  905. </style>