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.

542 lines
13 KiB

8 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 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
7 months ago
7 months ago
7 months ago
8 months ago
8 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
8 months ago
8 months ago
  1. <template>
  2. <div id="running-container">
  3. <div class="circular-loader">
  4. <!-- 中心元素 -->
  5. <TankInfo
  6. :selected-item="
  7. currentIndex >= 0 ? runningStore.subTanks![currentIndex] : undefined
  8. "
  9. ></TankInfo>
  10. <!-- 温度计 -->
  11. <div class="plate-temp">
  12. <span class="plate-temp-reg"> 孵育盘温度 </span>
  13. <span class="plate-temp-value">
  14. <span style="font-size: 22px">
  15. {{ deviceStore.sensorState?.incubateBoxTemperature || 0 }}
  16. </span>
  17. </span>
  18. </div>
  19. <!-- 光学模组 -->
  20. <div class="scan-main">
  21. <span style="font-weight: 600; font-size: 18px; display: block">
  22. 光学模组
  23. </span>
  24. <div
  25. v-if="
  26. runningStore.optScanModuleState &&
  27. runningStore.optScanModuleState!.state !== 'EMPTY'
  28. "
  29. class="scan-ji"
  30. >
  31. <!--是急诊位时-->
  32. <div
  33. style="position: absolute; left: 0px"
  34. v-if="runningStore.optScanModuleState!.isEmergency"
  35. >
  36. <img
  37. src="@/assets/ji.png"
  38. alt=""
  39. width="15"
  40. height="15"
  41. style="border-radius: 50%"
  42. />
  43. </div>
  44. <span
  45. class="scan-men"
  46. :style="`background-color:${consumablesStore.projIdColorMap[runningStore.optScanModuleState.projId]}`"
  47. >
  48. <img
  49. src="@/assets/men.png"
  50. alt=""
  51. width="20"
  52. height="20"
  53. style="border-radius: 50%"
  54. />
  55. <span v-if="runningStore.optScanModuleState?.state === 'SCANNING'"
  56. >扫描中</span
  57. >
  58. <span v-else>{{ runningStore.optScanModuleState.userid }}</span>
  59. <span style="font-size: 14px">{{
  60. settingTubeStore.bloodTypeKeyMap[
  61. runningStore.optScanModuleState.bloodType
  62. ].name
  63. }}</span>
  64. <span style="font-size: 16px; font-weight: 600">{{
  65. runningStore.optScanModuleState.projInfo.projShortName
  66. }}</span>
  67. </span>
  68. </div>
  69. <div v-else class="scan-empty">空闲</div>
  70. </div>
  71. <SubTankItem
  72. v-for="(item, index) in runningStore.subTanks"
  73. :item="item"
  74. :index="index"
  75. :key="item.pos"
  76. :is-selected="index === currentIndex"
  77. @toggle-select-item="toggleSelectItem"
  78. ></SubTankItem>
  79. </div>
  80. <div class="consumables-container">
  81. <div class="row-first">
  82. <!-- 急诊按钮 -->
  83. <div class="emergency-area">
  84. <div
  85. class="emergency-button"
  86. :style="`background:${canSetEmergency ? '#ff6b6b' : '#c7c7c7'}`"
  87. @click="canSetEmergency ? confirmEmergency() : null"
  88. >
  89. <span>急诊</span>
  90. </div>
  91. <div class="emergency-tube" :class="{ config: !canSetEmergency }">
  92. <tube-item
  93. :tube="canSetEmergency ? undefined : emergencyStore.emergencyInfo"
  94. :showNum="false"
  95. />
  96. </div>
  97. </div>
  98. <div class="split"></div>
  99. <div class="tube-items">
  100. <tube-item
  101. v-for="(tube, index) in runningStore.tubeHolderState?.tubes || []"
  102. :key="index"
  103. :tube="tube"
  104. :tubeNo="index + 1"
  105. />
  106. </div>
  107. </div>
  108. <!-- 第二行 -->
  109. <div class="row-second">
  110. <!-- 反应板区域 -->
  111. <div class="plates-area">
  112. <PlateDisplay
  113. :projects="consumablesStore.consumableData.reactionPlateGroup"
  114. />
  115. </div>
  116. <!-- 小缓冲液区域 -->
  117. <div class="little-buffer-liquid">
  118. <LittleBufferDisplay
  119. :bufferData="consumablesStore.consumableData.littBottleGroup"
  120. />
  121. </div>
  122. <!-- tips + 大缓冲液区域 -->
  123. <div class="tips-and-big-buffer">
  124. <div class="tips-item">
  125. <div
  126. class="tip-fill"
  127. :style="getFillStyle(consumablesStore.tipCount)"
  128. ></div>
  129. <div class="tip-text">{{ consumablesStore.tipCount }}/360</div>
  130. <span class="label">移液头</span>
  131. </div>
  132. <BigBufferDisplay
  133. :data="consumablesStore.consumableData.larBottleGroup"
  134. :width="160"
  135. height="110px"
  136. ></BigBufferDisplay>
  137. </div>
  138. <!-- 废料区域 -->
  139. <div
  140. class="waste-area"
  141. :class="{ isFull: deviceStore.sensorState.wasteBinFullFlag }"
  142. >
  143. <div class="waste-text">废料箱</div>
  144. <div class="full-state">
  145. {{ deviceStore.sensorState.wasteBinFullFlag ? '已满' : '未满' }}
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. </div>
  151. <!-- 弹窗提示 -->
  152. <teleport to="body">
  153. <div v-if="showWasteAlert" class="alert-overlay">
  154. <div class="alert-container">
  155. <div class="alert-title">废料箱已满</div>
  156. <div class="alert-message">请尽快清理废料箱</div>
  157. <el-button type="danger" @click="closeAlert" class="alert-button"
  158. >确认</el-button
  159. >
  160. </div>
  161. </div>
  162. </teleport>
  163. <!-- 急诊弹窗提示 -->
  164. </template>
  165. <script setup lang="ts">
  166. import { computed, ref, watch } from 'vue'
  167. import { useRouter } from 'vue-router'
  168. import {
  169. useConsumablesStore,
  170. useDeviceStore,
  171. useSettingTestTubeStore,
  172. useEmergencyStore,
  173. } from '@/store'
  174. import TankInfo from '../components/Running/TankInfo.vue'
  175. import { PlateDisplay, LittleBufferDisplay } from '../components'
  176. import tubeItem from '../components/TestTube/Tube.vue'
  177. import { useRunningStore } from '@/store/modules/running'
  178. import SubTankItem from '../components/Running/SubTank.vue'
  179. import BigBufferDisplay from '../components/Running/BigBufferDisplay.vue'
  180. const consumablesStore = useConsumablesStore()
  181. const runningStore = useRunningStore()
  182. const deviceStore = useDeviceStore()
  183. const settingTubeStore = useSettingTestTubeStore()
  184. const emergencyStore = useEmergencyStore() //从急诊页面添加的急诊数据
  185. const router = useRouter()
  186. const canSetEmergency = computed(() => {
  187. return (
  188. emergencyStore.emergencyInfo &&
  189. (emergencyStore.emergencyInfo.state === 'EMPTY' ||
  190. emergencyStore.emergencyInfo.state === 'PROCESS_COMPLETE')
  191. )
  192. })
  193. //确认添加急诊
  194. const confirmEmergency = () => {
  195. router.push('/index/emergency')
  196. }
  197. // 当前选中的tank索引
  198. let currentIndex = ref(-1)
  199. const toggleSelectItem = (index: number) => {
  200. if (index === currentIndex.value) {
  201. currentIndex.value = -1
  202. } else {
  203. currentIndex.value = index
  204. }
  205. }
  206. // 计算填充样式
  207. const getFillStyle = (cnt: number) => {
  208. const percentage = (cnt / 360) * 100
  209. return {
  210. background: `linear-gradient(to top, #bbd3fb ${percentage}%,#c9c9c9 ${percentage}%)`,
  211. }
  212. }
  213. const showWasteAlert = ref(false)
  214. const closeAlert = () => {
  215. showWasteAlert.value = false
  216. }
  217. watch(
  218. () => deviceStore.sensorState.wasteBinFullFlag,
  219. (newVal) => {
  220. if (newVal) {
  221. showWasteAlert.value = true
  222. }
  223. },
  224. )
  225. </script>
  226. <style lang="less" scoped>
  227. #running-container {
  228. display: flex;
  229. flex-direction: column;
  230. justify-content: space-around;
  231. align-items: center;
  232. background-color: #f9fafb;
  233. height: 92vh;
  234. /* 更柔和的背景色 */
  235. // 孵育盘
  236. .circular-loader {
  237. position: relative;
  238. width: 900px;
  239. height: 900px;
  240. border-radius: 50%;
  241. background-color: #f0f2f5;
  242. display: flex;
  243. align-items: center;
  244. justify-content: center;
  245. margin: 0 auto;
  246. border: 8px solid #ffffff;
  247. box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
  248. }
  249. // 耗材区
  250. .consumables-container {
  251. width: 100%;
  252. box-sizing: border-box;
  253. padding: 15px 20px;
  254. display: flex;
  255. flex-direction: column;
  256. background-color: #ffffff;
  257. box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05);
  258. border-radius: 20px 20px 0 0;
  259. // 第一行
  260. .row-first {
  261. display: flex;
  262. align-items: center;
  263. margin-bottom: 10px;
  264. .emergency-area {
  265. display: flex;
  266. gap: 8px;
  267. align-items: center;
  268. align-self: stretch;
  269. .emergency-button {
  270. width: 80px;
  271. height: 80px;
  272. background: #ff6b6b;
  273. border-radius: 20px;
  274. display: flex;
  275. align-items: center;
  276. justify-content: center;
  277. transition: all 0.3s ease;
  278. box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
  279. span {
  280. font-size: 24px;
  281. color: #ffffff;
  282. font-weight: bold;
  283. }
  284. }
  285. .emergency-tube {
  286. align-self: flex-end;
  287. }
  288. }
  289. .split {
  290. width: 4px;
  291. height: 100px;
  292. background-color: #ccc;
  293. margin: 0 10px;
  294. }
  295. .tube-items {
  296. display: flex;
  297. flex-wrap: nowrap;
  298. justify-content: space-between;
  299. align-items: center;
  300. height: auto;
  301. column-gap: 5px;
  302. // padding: 0 10px;
  303. }
  304. }
  305. //第二行
  306. .row-second {
  307. display: grid;
  308. gap: 15px;
  309. grid-template-columns: 3fr 4fr 2fr 1fr;
  310. padding: 10px 0;
  311. .tips-and-big-buffer {
  312. display: flex;
  313. flex-direction: column;
  314. .tips-item {
  315. width: 200px;
  316. height: 137px;
  317. border-radius: 12px;
  318. display: flex;
  319. align-items: center;
  320. justify-content: center;
  321. position: relative;
  322. background: #c0c0c0;
  323. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  324. overflow: hidden;
  325. margin-bottom: 10px;
  326. .label {
  327. position: absolute;
  328. left: 12px;
  329. top: 8px;
  330. font-size: 18px;
  331. color: #fff;
  332. }
  333. .tip-fill {
  334. position: absolute;
  335. width: 100%;
  336. height: 100%;
  337. border-radius: 16px;
  338. transition: all 0.3s ease;
  339. background: rgba(92, 184, 92, 0.2);
  340. }
  341. .tip-text {
  342. font-size: 36px;
  343. color: #fff;
  344. font-weight: bold;
  345. z-index: 1;
  346. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  347. }
  348. }
  349. }
  350. .waste-area {
  351. border-radius: 12px;
  352. display: flex;
  353. flex-direction: column;
  354. justify-content: center;
  355. align-items: center;
  356. transition: all 0.3s ease;
  357. background-color: #5cb85c;
  358. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  359. &.isFull {
  360. background-color: #d9534f;
  361. }
  362. .waste-text {
  363. font-size: 26px;
  364. font-weight: 600;
  365. color: #ffffff;
  366. writing-mode: vertical-rl;
  367. text-orientation: upright;
  368. letter-spacing: 6px;
  369. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  370. }
  371. .full-state {
  372. margin-top: 8px;
  373. color: #fff;
  374. font-size: 20px;
  375. font-weight: bold;
  376. }
  377. }
  378. }
  379. }
  380. }
  381. .alert-overlay {
  382. position: fixed;
  383. top: 0;
  384. left: 0;
  385. width: 100%;
  386. height: 100%;
  387. background-color: rgba(0, 0, 0, 0.7);
  388. backdrop-filter: blur(5px);
  389. display: flex;
  390. justify-content: center;
  391. align-items: center;
  392. z-index: 1000;
  393. .alert-container {
  394. background-color: #ffffff;
  395. padding: 40px;
  396. border-radius: 20px;
  397. text-align: center;
  398. max-width: 600px;
  399. animation: slideIn 0.3s ease;
  400. position: relative;
  401. .alert-icon {
  402. .icon {
  403. width: 80px;
  404. height: 80px;
  405. margin: 0 auto;
  406. }
  407. span {
  408. font-size: 32px;
  409. position: absolute;
  410. top: 30%;
  411. left: 50%;
  412. transform: translate(-50%, -50%);
  413. color: #fff;
  414. }
  415. }
  416. .alert-title {
  417. font-size: 36px;
  418. font-weight: bold;
  419. color: #d9534f;
  420. margin-bottom: 20px;
  421. }
  422. .alert-message {
  423. font-size: 28px;
  424. color: #495057;
  425. margin-bottom: 30px;
  426. }
  427. .action-buttons {
  428. display: flex;
  429. justify-content: center;
  430. gap: 20px;
  431. .el-button {
  432. min-width: 160px;
  433. height: 50px;
  434. font-size: 24px;
  435. border-radius: 25px;
  436. transition: all 0.3s ease;
  437. &:hover {
  438. transform: translateY(-2px);
  439. }
  440. }
  441. }
  442. }
  443. }
  444. @keyframes slideIn {
  445. from {
  446. opacity: 0;
  447. transform: translateY(-20px);
  448. }
  449. to {
  450. opacity: 1;
  451. transform: translateY(0);
  452. }
  453. }
  454. .plate-temp {
  455. position: fixed;
  456. top: 140px;
  457. right: 30px;
  458. padding: 10px;
  459. background-color: rgb(209, 229, 255);
  460. border-radius: 5px;
  461. .plate-temp-reg {
  462. color: rgb(95, 177, 253);
  463. font-size: 18px;
  464. display: block;
  465. }
  466. .plate-temp-value {
  467. display: block;
  468. background-color: rgb(95, 177, 253);
  469. color: white;
  470. }
  471. }
  472. .scan-main {
  473. position: fixed;
  474. top: 54rem;
  475. right: 10px;
  476. padding: 8px 5px;
  477. background-color: rgb(223, 237, 248);
  478. border-radius: 5px;
  479. height: 180px;
  480. .scan-men {
  481. color: #fff;
  482. display: inline-block;
  483. background-color: rgb(120, 206, 86);
  484. border-radius: 5px;
  485. height: 140px;
  486. padding: 5px;
  487. display: flex;
  488. flex-direction: column;
  489. justify-content: space-around;
  490. align-items: center;
  491. }
  492. .scan-empty {
  493. background-color: #ccc;
  494. height: 140px;
  495. border-radius: 5px;
  496. display: flex;
  497. justify-content: center;
  498. align-items: center;
  499. }
  500. .scan-ji {
  501. display: flex;
  502. height: 100px;
  503. width: 100px;
  504. justify-content: center;
  505. }
  506. }
  507. </style>