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.

540 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
  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:${runningStore.optScanModuleState.projInfo.color}`"
  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">
  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. .emergency-button {
  269. width: 80px;
  270. height: 80px;
  271. background: #ff6b6b;
  272. border-radius: 20px;
  273. display: flex;
  274. align-items: center;
  275. justify-content: center;
  276. transition: all 0.3s ease;
  277. box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
  278. span {
  279. font-size: 24px;
  280. color: #ffffff;
  281. font-weight: bold;
  282. }
  283. }
  284. .emergency-tube {
  285. }
  286. }
  287. .split {
  288. width: 4px;
  289. height: 100px;
  290. background-color: #CCC;
  291. margin: 0 10px;
  292. }
  293. .tube-items {
  294. display: flex;
  295. flex-wrap: nowrap;
  296. justify-content: space-between;
  297. align-items: center;
  298. height: auto;
  299. column-gap: 5px;
  300. // padding: 0 10px;
  301. }
  302. }
  303. //第二行
  304. .row-second {
  305. display: grid;
  306. gap: 15px;
  307. grid-template-columns: 3fr 4fr 2fr 1fr;
  308. padding: 10px 0;
  309. .tips-and-big-buffer {
  310. display: flex;
  311. flex-direction: column;
  312. .tips-item {
  313. width: 200px;
  314. height: 137px;
  315. border-radius: 12px;
  316. display: flex;
  317. align-items: center;
  318. justify-content: center;
  319. position: relative;
  320. background: #c0c0c0;
  321. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  322. overflow: hidden;
  323. margin-bottom: 10px;
  324. .label {
  325. position: absolute;
  326. left: 12px;
  327. top: 8px;
  328. font-size: 18px;
  329. color: #fff;
  330. }
  331. .tip-fill {
  332. position: absolute;
  333. width: 100%;
  334. height: 100%;
  335. border-radius: 16px;
  336. transition: all 0.3s ease;
  337. background: rgba(92, 184, 92, 0.2);
  338. }
  339. .tip-text {
  340. font-size: 36px;
  341. color: #fff;
  342. font-weight: bold;
  343. z-index: 1;
  344. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  345. }
  346. }
  347. }
  348. .waste-area {
  349. border-radius: 12px;
  350. display: flex;
  351. flex-direction: column;
  352. justify-content: center;
  353. align-items: center;
  354. transition: all 0.3s ease;
  355. background-color: #5cb85c;
  356. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  357. &.isFull {
  358. background-color: #d9534f;
  359. }
  360. .waste-text {
  361. font-size: 26px;
  362. font-weight: 600;
  363. color: #ffffff;
  364. writing-mode: vertical-rl;
  365. text-orientation: upright;
  366. letter-spacing: 6px;
  367. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  368. }
  369. .full-state {
  370. margin-top: 8px;
  371. color: #fff;
  372. font-size: 20px;
  373. font-weight: bold;
  374. }
  375. }
  376. }
  377. }
  378. }
  379. .alert-overlay {
  380. position: fixed;
  381. top: 0;
  382. left: 0;
  383. width: 100%;
  384. height: 100%;
  385. background-color: rgba(0, 0, 0, 0.7);
  386. backdrop-filter: blur(5px);
  387. display: flex;
  388. justify-content: center;
  389. align-items: center;
  390. z-index: 1000;
  391. .alert-container {
  392. background-color: #ffffff;
  393. padding: 40px;
  394. border-radius: 20px;
  395. text-align: center;
  396. max-width: 600px;
  397. animation: slideIn 0.3s ease;
  398. position: relative;
  399. .alert-icon {
  400. .icon {
  401. width: 80px;
  402. height: 80px;
  403. margin: 0 auto;
  404. }
  405. span {
  406. font-size: 32px;
  407. position: absolute;
  408. top: 30%;
  409. left: 50%;
  410. transform: translate(-50%, -50%);
  411. color: #fff;
  412. }
  413. }
  414. .alert-title {
  415. font-size: 36px;
  416. font-weight: bold;
  417. color: #d9534f;
  418. margin-bottom: 20px;
  419. }
  420. .alert-message {
  421. font-size: 28px;
  422. color: #495057;
  423. margin-bottom: 30px;
  424. }
  425. .action-buttons {
  426. display: flex;
  427. justify-content: center;
  428. gap: 20px;
  429. .el-button {
  430. min-width: 160px;
  431. height: 50px;
  432. font-size: 24px;
  433. border-radius: 25px;
  434. transition: all 0.3s ease;
  435. &:hover {
  436. transform: translateY(-2px);
  437. }
  438. }
  439. }
  440. }
  441. }
  442. @keyframes slideIn {
  443. from {
  444. opacity: 0;
  445. transform: translateY(-20px);
  446. }
  447. to {
  448. opacity: 1;
  449. transform: translateY(0);
  450. }
  451. }
  452. .plate-temp {
  453. position: fixed;
  454. top: 140px;
  455. right: 30px;
  456. padding: 10px;
  457. background-color: rgb(209, 229, 255);
  458. border-radius: 5px;
  459. .plate-temp-reg {
  460. color: rgb(95, 177, 253);
  461. font-size: 18px;
  462. display: block;
  463. }
  464. .plate-temp-value {
  465. display: block;
  466. background-color: rgb(95, 177, 253);
  467. color: white;
  468. }
  469. }
  470. .scan-main {
  471. position: fixed;
  472. top: 54rem;
  473. right: 10px;
  474. padding: 8px 5px;
  475. background-color: rgb(223, 237, 248);
  476. border-radius: 5px;
  477. height: 180px;
  478. .scan-men {
  479. color: #fff;
  480. display: inline-block;
  481. background-color: rgb(120, 206, 86);
  482. border-radius: 5px;
  483. height: 140px;
  484. padding: 5px;
  485. display: flex;
  486. flex-direction: column;
  487. justify-content: space-around;
  488. align-items: center;
  489. }
  490. .scan-empty {
  491. background-color: #ccc;
  492. height: 140px;
  493. border-radius: 5px;
  494. display: flex;
  495. justify-content: center;
  496. align-items: center;
  497. }
  498. .scan-ji {
  499. display: flex;
  500. height: 100px;
  501. width: 100px;
  502. justify-content: center;
  503. }
  504. }
  505. </style>