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

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
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
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
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. <div class="center-square" :style="getCenterSquareStyle()">
  6. <template v-if="selectedItem">
  7. <span>{{ selectedItem.sampleBarcode || '无条码' }}</span>
  8. <span>{{ selectedItem.userid || '无患者ID' }}</span>
  9. <span>{{ selectedItem.projInfo.projName || '无项目' }}</span>
  10. <span>{{ selectedItem.projInfo.lotId ?? '无批次号' }}</span>
  11. <span>{{
  12. getBloodTypeLabel(selectedItem.bloodType) || '无类型'
  13. }}</span>
  14. <span>{{ getRemainingTime(selectedItem) }}</span>
  15. </template>
  16. <template v-else> </template>
  17. </div>
  18. <!-- 外圈的矩形元素 -->
  19. <div v-for="(item, index) in incubationPlates" :key="item.sampleId" class="rectangular-item"
  20. :style="[getRotationStyle(item, index), getItemStyle(item)]"
  21. @click="!item.isPlaceholder && toggleSelectItem(item,index)">
  22. <template v-if="item.isPlaceholder">
  23. <!-- 占位符 -->
  24. <span class="placeholder-text">空位</span>
  25. </template>
  26. <template v-else>
  27. <!-- 正常数据 -->
  28. <span class="project-name">{{ item.projInfo.projShortName || '无项目' }}</span>
  29. <span class="barcode">{{ item.sampleBarcode || '无条码' }}</span>
  30. <div class="emergency-icon" v-if="item.pos === 'EMERGENCY'">
  31. <img class="icon" src="@/assets/emergency.svg" />
  32. <span class="text"></span>
  33. </div>
  34. <span class="time">{{ getRemainingTime(item) }}</span>
  35. </template>
  36. <div v-show="currentIndex == index" :style="`margin-top:${item.pos=='EMERGENCY'?'20px':'75px'}`" class="quan">{{ index +1 }}</div>
  37. </div>
  38. </div>
  39. <div class="consumables-container">
  40. <!-- 第一行 -->
  41. <div class="row-first">
  42. <!-- 急诊按钮 -->
  43. <div class="emergency-button" @click="showEmergencyAlert = !showEmergencyAlert">
  44. <span>急诊</span>
  45. </div>
  46. <!-- 试管架区域 -->
  47. <div class="test-tube-rack-area">
  48. <div class="tube-project-tab">
  49. <tube-item
  50. :tube="sampleTube"
  51. :index="0"
  52. :projects="sampleProjects"
  53. :bloodTypes="sampleBloodTypes"
  54. />
  55. </div>
  56. <div class="tube-items">
  57. <!-- <SampleDisplay :samples="tubeHolderState.tubes" :selectedSamples="selectedSamples"
  58. @updateSelectedSamples="updateSelectedSamples" /> -->
  59. <div class="tube-container">
  60. <template v-for="(tube, index) in tubes" :key="index">
  61. <tube-item
  62. :tube="tube"
  63. :index="index"
  64. :projects="projects"
  65. :bloodTypes="bloodTypes"
  66. />
  67. </template>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. <!-- 第二行 -->
  73. <div class="row-second">
  74. <!-- 反应板区域 -->
  75. <div class="plates-area">
  76. <PlateDisplay :projects="consumablesStore.plates" />
  77. </div>
  78. <!-- 小缓冲液区域 -->
  79. <div class="little-buffer-liquid">
  80. <LittleBufferDisplay :bufferData="consumablesStore.bufferLittles" />
  81. </div>
  82. <!-- tips 大缓冲液区域 -->
  83. <div class="tips-and-big-buffer">
  84. <div class="tips-item">
  85. <div class="tip-fill" :style="getFillStyle(consumablesStore.moveLiquids[0])"></div>
  86. <div class="tip-text">
  87. {{ consumablesStore.moveLiquids[0].tipNum }}/120
  88. </div>
  89. </div>
  90. <BallGrid :total="6" :customColors="true" width="160px" height="110px" :data="consumablesStore.bufferBig"
  91. :columns="3" class="buffer-grid" />
  92. </div>
  93. <!-- 废料区域 -->
  94. <div class="waste-area" :style="getWasteStyle()">
  95. <div class="waste-text">废料箱</div>
  96. </div>
  97. </div>
  98. </div>
  99. </div>
  100. <!-- 弹窗提示 -->
  101. <teleport to="body">
  102. <div v-if="showWasteAlert" class="alert-overlay">
  103. <div class="alert-container">
  104. <div class="alert-title">废料箱已满</div>
  105. <div class="alert-message">请尽快清理废料箱</div>
  106. <el-button type="danger" @click="closeAlert" class="alert-button">确认</el-button>
  107. </div>
  108. </div>
  109. </teleport>
  110. <!-- 急诊弹窗提示 -->
  111. <teleport to="body">
  112. <div v-if="showEmergencyAlert" class="alert-overlay">
  113. <div class="alert-container">
  114. <div class="alert-icon">
  115. <img class="icon" src="@/assets/emergency.svg" />
  116. <span></span>
  117. </div>
  118. <div class="alert-message">确认要添加急诊吗</div>
  119. <div class="action-buttons">
  120. <el-button type="info" @click="cancelEmergency" class="confirm-button">取消</el-button>
  121. <el-button type="primary" @click="confirmEmergency" class="cancel-button">确认</el-button>
  122. </div>
  123. </div>
  124. </div>
  125. </teleport>
  126. <EmergencyResultDialog :result="emergencyResult!" :visible="isDialogVisible" @update:visible="confirmEmergencyWarn" />
  127. </template>
  128. <script setup lang="ts">
  129. import { ref, onMounted, onUnmounted, watch, onActivated, onDeactivated } from 'vue'
  130. import { useRouter, useRoute } from 'vue-router'
  131. import { useTestTubeStore, useConsumablesStore } from '../../../store'
  132. import { getBloodTypeLabel, processTubeSettings } from '../Utils'
  133. import {
  134. SampleDisplay,
  135. PlateDisplay,
  136. LittleBufferDisplay,
  137. EmergencyResultDialog,
  138. } from '../Components'
  139. import tubeItem from '../components/TestTube/Tube.vue'
  140. import BallGrid from '../Components/Consumables/BallGrid.vue'
  141. import { wasteArea, getTubeRackState } from '../../../services/index'
  142. import type { Subtank, TubeRackInfo } from '../../../types/Index'
  143. import { getRunningList } from '../../../services/Index/running/running'
  144. import { createWebSocket } from '../../../websocket/socket'
  145. import type { TubeHolderStateMessage, IncubationPlateStateMessage, EmergencyPosStateMessage, ProjectInfo } from '../../../websocket/socket'
  146. import { getServerInfo } from '../../../utils/getServerInfo'
  147. import { useEmergencyStore } from '../../../store/modules/emergency'
  148. const { wsUrl } = getServerInfo('/api/v1/app/ws/state')
  149. const ws = createWebSocket(wsUrl)
  150. const testTubeStore = useTestTubeStore()
  151. const emergencyStore = useEmergencyStore()
  152. const consumablesStore = useConsumablesStore()
  153. const router = useRouter()
  154. const route = useRoute()
  155. // 定义试管架状态
  156. const tubeRack = ref<TubeRackInfo>({
  157. tubes: [], // 默认空试管数据
  158. state: 'IDLE',
  159. hasTubeToBeProcessed: false,
  160. })
  161. // 假设这是10条真实数据
  162. // 模拟10条真实数据
  163. const tubes = [
  164. { userid: 'user1', projId: [1], bloodType: 'A' },
  165. { userid: 'user2', projId: [2], bloodType: 'B' },
  166. { userid: 'user3', projId: [1, 2], bloodType: 'AB' },
  167. { userid: 'user4', projId: [], bloodType: 'O' },
  168. { userid: 'user5', projId: [3], bloodType: 'A' },
  169. { userid: 'user6', projId: [4], bloodType: 'B' },
  170. { userid: 'user7', projId: [3, 4], bloodType: 'AB' },
  171. { userid: 'user8', projId: [], bloodType: 'O' },
  172. { userid: 'user9', projId: [5], bloodType: 'A' },
  173. { userid: 'user10', projId: [6], bloodType: 'B' }
  174. ]
  175. // 项目数据
  176. const projects = [
  177. { projId: 1, projName: 'Project Alpha', color: 'red' },
  178. { projId: 2, projName: 'Project Beta', color: 'blue' },
  179. { projId: 3, projName: 'Project Gamma', color: 'green' },
  180. { projId: 4, projName: 'Project Delta', color: 'yellow' },
  181. { projId: 5, projName: 'Project Epsilon', color: 'purple' },
  182. { projId: 6, projName: 'Project Zeta', color: 'orange' }
  183. ]
  184. // 血型数据
  185. const bloodTypes = [
  186. { key: 'A', name: 'Type A' },
  187. { key: 'B', name: 'Type B' },
  188. { key: 'AB', name: 'Type AB' },
  189. { key: 'O', name: 'Type O' }
  190. ]
  191. // 示例数据
  192. const sampleTube = {
  193. userid: 'user123',
  194. projId: [1, 2],
  195. bloodType: 'A'
  196. }
  197. const sampleProjects = [
  198. { projId: 1, projName: 'Project One', color: 'red' },
  199. { projId: 2, projName: 'Project Two', color: 'blue' }
  200. ]
  201. const sampleBloodTypes = [
  202. { key: 'A', name: 'Type A' },
  203. { key: 'B', name: 'Type B' }
  204. ]
  205. // 修改试管架状态的类型定义
  206. interface TubeHolderState {
  207. tubeHolderType: string
  208. tubes: Array<{
  209. sampleId: string | null
  210. pos: number
  211. isHighTube: boolean
  212. isEmergency: boolean
  213. bloodType: string
  214. sampleBarcode: string
  215. userid: string
  216. projInfo: any[]
  217. projIds: number[]
  218. state: string
  219. errors: string[]
  220. }>
  221. state: string
  222. }
  223. // 试管架状态
  224. const tubeHolderStateData = ref<TubeHolderState>({
  225. tubeHolderType: '',
  226. tubes: [],
  227. state: 'IDLE'
  228. })
  229. //处理试管架状态
  230. const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
  231. // console.log('试管架状态:', data)
  232. tubeHolderStateData.value = data
  233. }
  234. //处理孵育盘状态
  235. const handleIncubationPlateStateMessage = (data: IncubationPlateStateMessage['data']) => {
  236. console.log('孵育盘状态:', data)
  237. // incubationPlates.value = data.subtanks
  238. incubationPlates.value =[
  239. {
  240. pos: "A1",
  241. state: "REACTED",
  242. bloodType: "WHOLE_BLOOD",
  243. sampleBarcode: "BC001",
  244. userid: "U001",
  245. projInfo: {
  246. projId: 1,
  247. projName: "血液疾病检测项目",
  248. projShortName: "项目1",
  249. color: "red"
  250. },
  251. sampleId: "S001",
  252. projId: 1,
  253. startIncubatedTime: 1612345678,
  254. incubatedTimeSec: 1800,
  255. errors: []
  256. },
  257. {
  258. pos: "A2",
  259. state: "REACTED",
  260. bloodType: "PLASMA",
  261. sampleBarcode: "BC002",
  262. userid: "U002",
  263. projInfo: {
  264. projId: 1,
  265. projName: "血液疾病检测项目",
  266. projShortName: "项目1",
  267. color: "red"
  268. },
  269. sampleId: "S002",
  270. projId: 1,
  271. startIncubatedTime: 1612345778,
  272. incubatedTimeSec: 2100,
  273. errors: []
  274. },
  275. {
  276. pos: "A3",
  277. state: "PARTIAL_REACTION",
  278. bloodType: "SERUM",
  279. sampleBarcode: "BC003",
  280. userid: "U003",
  281. projInfo: {
  282. projId: 1,
  283. projName: "血液疾病检测项目",
  284. projShortName: "项目1",
  285. color: "red"
  286. },
  287. sampleId: "S003",
  288. projId: 1,
  289. startIncubatedTime: 1612345878,
  290. incubatedTimeSec: 1900,
  291. errors: []
  292. },
  293. {
  294. pos: "B1",
  295. state: "REACTED",
  296. bloodType: "WHOLE_BLOOD",
  297. sampleBarcode: "BC004",
  298. userid: "U004",
  299. projInfo: {
  300. projId: 2,
  301. projName: "血液成分分析项目",
  302. projShortName: "项目2",
  303. color: "blue"
  304. },
  305. sampleId: "S004",
  306. projId: 2,
  307. startIncubatedTime: 1612345978,
  308. incubatedTimeSec: 2200,
  309. errors: []
  310. },
  311. {
  312. pos: "B2",
  313. state: "NO_REACTION",
  314. bloodType: "PLASMA",
  315. sampleBarcode: "BC005",
  316. userid: "U005",
  317. projInfo: {
  318. projId: 2,
  319. projName: "血液成分分析项目",
  320. projShortName: "项目2",
  321. color: "blue"
  322. },
  323. sampleId: "S005",
  324. projId: 2,
  325. startIncubatedTime: 1612346078,
  326. incubatedTimeSec: 2000,
  327. errors: []
  328. },
  329. {
  330. pos: "B3",
  331. state: "REACTED",
  332. bloodType: "SERUM",
  333. sampleBarcode: "BC006",
  334. userid: "U006",
  335. projInfo: {
  336. projId: 2,
  337. projName: "血液成分分析项目",
  338. projShortName: "项目2",
  339. color: "blue"
  340. },
  341. sampleId: "S006",
  342. projId: 2,
  343. startIncubatedTime: 1612346178,
  344. incubatedTimeSec: 2300,
  345. errors: []
  346. },
  347. {
  348. pos: "C1",
  349. state: "PARTIAL_REACTION",
  350. bloodType: "WHOLE_BLOOD",
  351. sampleBarcode: "BC007",
  352. userid: "U007",
  353. projInfo: {
  354. projId: 3,
  355. projName: "特殊血液指标检测项目",
  356. projShortName: "项目3",
  357. color: "green"
  358. },
  359. sampleId: "S007",
  360. projId: 3,
  361. startIncubatedTime: 1612346278,
  362. incubatedTimeSec: 1950,
  363. errors: []
  364. },
  365. {
  366. pos: "C2",
  367. state: "REACTED",
  368. bloodType: "PLASMA",
  369. sampleBarcode: "BC008",
  370. userid: "U008",
  371. projInfo: {
  372. projId: 3,
  373. projName: "特殊血液指标检测项目",
  374. projShortName: "项目3",
  375. color: "green"
  376. },
  377. sampleId: "S008",
  378. projId: 3,
  379. startIncubatedTime: 1612346378,
  380. incubatedTimeSec: 2150,
  381. errors: []
  382. },
  383. {
  384. pos: "C3",
  385. state: "NO_REACTION",
  386. bloodType: "SERUM",
  387. sampleBarcode: "BC009",
  388. userid: "U009",
  389. projInfo: {
  390. projId: 3,
  391. projName: "特殊血液指标检测项目",
  392. projShortName: "项目3",
  393. color: "green"
  394. },
  395. sampleId: "S009",
  396. projId: 3,
  397. startIncubatedTime: 1612346478,
  398. incubatedTimeSec: 2050,
  399. errors: []
  400. },
  401. {
  402. pos: "D1",
  403. state: "REACTED",
  404. bloodType: "WHOLE_BLOOD",
  405. sampleBarcode: "BC010",
  406. userid: "U010",
  407. projInfo: {
  408. projId: 1,
  409. projName: "血液疾病检测项目",
  410. projShortName: "项目1",
  411. color: "red"
  412. },
  413. sampleId: "S010",
  414. projId: 1,
  415. startIncubatedTime: 1612346578,
  416. incubatedTimeSec: 2250,
  417. errors: []
  418. },
  419. {
  420. pos: "D2",
  421. state: "PARTIAL_REACTION",
  422. bloodType: "PLASMA",
  423. sampleBarcode: "BC011",
  424. userid: "U011",
  425. projInfo: {
  426. projId: 1,
  427. projName: "血液疾病检测项目",
  428. projShortName: "项目1",
  429. color: "red"
  430. },
  431. sampleId: "S011",
  432. projId: 1,
  433. startIncubatedTime: 1612346678,
  434. incubatedTimeSec: 1850,
  435. errors: []
  436. },
  437. {
  438. pos: "D3",
  439. state: "NO_REACTION",
  440. bloodType: "SERUM",
  441. sampleBarcode: "BC012",
  442. userid: "U012",
  443. projInfo: {
  444. projId: 1,
  445. projName: "血液疾病检测项目",
  446. projShortName: "项目1",
  447. color: "red"
  448. },
  449. sampleId: "S012",
  450. projId: 1,
  451. startIncubatedTime: 1612346778,
  452. incubatedTimeSec: 2000,
  453. errors: []
  454. },
  455. {
  456. pos: "E1",
  457. state: "REACTED",
  458. bloodType: "WHOLE_BLOOD",
  459. sampleBarcode: "BC013",
  460. userid: "U013",
  461. projInfo: {
  462. projId: 2,
  463. projName: "血液成分分析项目",
  464. projShortName: "项目2",
  465. color: "blue"
  466. },
  467. sampleId: "S013",
  468. projId: 2,
  469. startIncubatedTime: 1612346878,
  470. incubatedTimeSec: 2350,
  471. errors: []
  472. },
  473. {
  474. pos: "E2",
  475. state: "PARTIAL_REACTION",
  476. bloodType: "PLASMA",
  477. sampleBarcode: "BC014",
  478. userid: "U014",
  479. projInfo: {
  480. projId: 2,
  481. projName: "血液成分分析项目",
  482. projShortName: "项目2",
  483. color: "blue"
  484. },
  485. sampleId: "S014",
  486. projId: 2,
  487. startIncubatedTime: 1612346978,
  488. incubatedTimeSec: 1900,
  489. errors: []
  490. },
  491. {
  492. pos: "E3",
  493. state: "NO_REACTION",
  494. bloodType: "SERUM",
  495. sampleBarcode: "BC015",
  496. userid: "U015",
  497. projInfo: {
  498. projId: 2,
  499. projName: "血液成分分析项目",
  500. projShortName: "项目2",
  501. color: "blue"
  502. },
  503. sampleId: "S015",
  504. projId: 2,
  505. startIncubatedTime: 1612347078,
  506. incubatedTimeSec: 2100,
  507. errors: []
  508. },
  509. {
  510. pos: "F1",
  511. state: "REACTED",
  512. bloodType: "WHOLE_BLOOD",
  513. sampleBarcode: "BC016",
  514. userid: "U016",
  515. projInfo: {
  516. projId: 3,
  517. projName: "特殊血液指标检测项目",
  518. projShortName: "项目3",
  519. color: "green"
  520. },
  521. sampleId: "S016",
  522. projId: 3,
  523. startIncubatedTime: 1612347178,
  524. incubatedTimeSec: 2200,
  525. errors: []
  526. },
  527. {
  528. pos: "F2",
  529. state: "PARTIAL_REACTION",
  530. bloodType: "PLASMA",
  531. sampleBarcode: "BC017",
  532. userid: "U017",
  533. projInfo: {
  534. projId: 3,
  535. projName: "特殊血液指标检测项目",
  536. projShortName: "项目3",
  537. color: "green"
  538. },
  539. sampleId: "S017",
  540. projId: 3,
  541. startIncubatedTime: 1612347278,
  542. incubatedTimeSec: 1950,
  543. errors: []
  544. },
  545. {
  546. pos: "F3",
  547. state: "NO_REACTION",
  548. bloodType: "SERUM",
  549. sampleBarcode: "BC018",
  550. userid: "U018",
  551. projInfo: {
  552. projId: 3,
  553. projName: "特殊血液指标检测项目",
  554. projShortName: "项目3",
  555. color: "green"
  556. },
  557. sampleId: "S018",
  558. projId: 3,
  559. startIncubatedTime: 1612347378,
  560. incubatedTimeSec: 2050,
  561. errors: []
  562. }
  563. ]
  564. }
  565. // 定义方法以更新试管架状态
  566. const fetchTubeRackState = async () => {
  567. try {
  568. const response = await getTubeRackState()
  569. if (response && response.success) {
  570. tubeRack.value = response.data
  571. } else {
  572. console.error('获取试管架状态失败', response)
  573. }
  574. } catch (err) {
  575. console.error('请求试管架状态接口出错:', err)
  576. }
  577. }
  578. // 页面加载时获取试管架状态
  579. onMounted(() => {
  580. fetchTubeRackState()
  581. setInterval(fetchTubeRackState, 10000) // 每隔指定时间更新状态
  582. })
  583. onMounted(() => {
  584. ws.connect()
  585. ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
  586. ws.subscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
  587. })
  588. onActivated(() => {
  589. ws.connect()
  590. ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
  591. ws.subscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
  592. })
  593. onDeactivated(() => {
  594. ws.disconnect()
  595. ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
  596. ws.unsubscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
  597. })
  598. onUnmounted(() => {
  599. ws.disconnect()
  600. ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderStateMessage)
  601. ws.unsubscribe<IncubationPlateStateMessage>('IncubationPlateState', handleIncubationPlateStateMessage)
  602. })
  603. onMounted(() => {
  604. // 在页面加载时检查是否有急诊数据传递
  605. // 初次获取路由参数
  606. fetchIncubationData()
  607. // 初次加载,处理可能已经存在的数据
  608. updateProcessedTubeSettings()
  609. // 监听 testTubeStore 数据的变化
  610. watch(
  611. () => testTubeStore.tubeInfo?.tubeSettings, // 监听目标
  612. (newTubeSettings) => {
  613. if (newTubeSettings) {
  614. // 当 tubeSettings 发生变化时重新处理
  615. updateProcessedTubeSettings()
  616. }
  617. },
  618. { deep: true, immediate: true }, // 深度监听 + 初始调用
  619. )
  620. })
  621. // 监听急诊数据变化
  622. watch(
  623. () => emergencyStore.emergencyInfo,
  624. (newData) => {
  625. if (newData) {
  626. const emergencyInfo = newData as EmergencyPosStateMessage['data']['tube']
  627. if (emergencyInfo.projInfo && Array.isArray(emergencyInfo.projInfo)) {
  628. emergencyInfo.projInfo.forEach((project: ProjectInfo, index: number) => {
  629. const subtank: Subtank = {
  630. pos: `EMERGENCY-${index + 1}`,
  631. state: 'OCCUPIED',
  632. bloodType: emergencyInfo.bloodType,
  633. sampleBarcode: emergencyInfo.sampleBarcode,
  634. userid: emergencyInfo.userid,
  635. projInfo: project,
  636. sampleId: `${emergencyInfo.sampleId}-${index}`,
  637. projId: project.projId,
  638. startIncubatedTime: Date.now(),
  639. incubatedTimeSec: 300,
  640. errors: [],
  641. isEmergency: true
  642. }
  643. // 添加到孵育盘数据中
  644. // incubationPlates.value.push(subtank)
  645. })
  646. }
  647. fetchEmergencyData()
  648. }
  649. },
  650. )
  651. // 页面激活时触发
  652. onActivated(() => {
  653. fetchEmergencyData()
  654. })
  655. // 提取逻辑到独立函数
  656. const updateProcessedTubeSettings = () => {
  657. const tubeSettings = testTubeStore.tubeInfo?.tubeSettings || [] // 获取当前试管数据
  658. const plateData = plates.value || [] // 获取当前板数据
  659. processedTubeSettings.value = processTubeSettings(
  660. tubeSettings,
  661. plateData,
  662. getBloodTypeLabel,
  663. )
  664. }
  665. //急诊状态
  666. const showEmergencyAlert = ref(false)
  667. //确认添加急诊
  668. const confirmEmergency = () => {
  669. showEmergencyAlert.value = false
  670. router.push('/index/emergency')
  671. }
  672. //确认结果
  673. const confirmEmergencyWarn = (val: any) => {
  674. console.log('急诊结果:', val)
  675. isDialogVisible.value = false
  676. }
  677. //获取路由参数
  678. const fetchEmergencyData = () => {
  679. const emergencyQuery = route.query.emergencyData as string
  680. if (emergencyQuery) {
  681. emergencyData.value = JSON.parse(emergencyQuery)
  682. console.log('急诊数据:', emergencyData.value)
  683. }
  684. }
  685. //取消
  686. const cancelEmergency = () => {
  687. showEmergencyAlert.value = false
  688. }
  689. // 获取圆心样式:选中时显示蓝色边框
  690. const getCenterSquareStyle = () => ({
  691. borderColor: selectedItem.value ? 'blue' : '#ffffff',
  692. borderStyle: selectedItem.value ? 'solid' : 'none',
  693. })
  694. //被选中的样本id列表
  695. const selectedSamples = ref<number[]>([])
  696. // 外圈矩形元素的数量
  697. // 孵育盘列表数据
  698. const incubationPlates = ref<Subtank[]>([])
  699. const selectedItem = ref<Subtank | null>(null) // 当前选中的样本
  700. const selectedItemId = ref<string | null>(null) //
  701. const TOTAL_SLOTS = 20 // 总矩形数
  702. const emergencyData = ref<Subtank | null>(null)
  703. // 修改获取旋转样式的方法
  704. const getRotationStyle = (item: Subtank, index: number) => {
  705. const totalItems = TOTAL_SLOTS
  706. const angleStep = 360 / totalItems
  707. const angle = index * angleStep
  708. return {
  709. transform: `
  710. translate(-50%, -50%) /* 将矩形中心点移到圆心 */
  711. rotate(${angle}deg) /* 旋转到对应角度 */
  712. translateY(-400px) /* 向上偏移到圆环位置 */
  713. `,
  714. backgroundColor: item.projInfo?.color || '#ffffff',
  715. borderColor: item.isPlaceholder ? '#f0f0f0' :
  716. selectedItemId.value === item.sampleId ? '#1890ff' :
  717. getRemainingTime(item) === '已完成' ? '#ff4d4f' : 'transparent',
  718. borderWidth: '3px',
  719. borderStyle: 'solid',
  720. }
  721. }
  722. // 获取当前元素样式:若为选中状态则添加蓝色边框,若为完成状态则添加红色边框
  723. const getItemStyle = (item: Subtank) => {
  724. if (item.isPlaceholder) {
  725. return {
  726. borderColor: '#ffffff', // 占位符边框为白色
  727. borderStyle: 'solid',
  728. backgroundColor: '#f8f8f8', // 占位符背景色
  729. }
  730. }
  731. const remainingTime = getRemainingTime(item)
  732. return {
  733. backgroundColor: item.pos === 'EMERGENCY' ? '#ffeded' : item.projInfo.color, // 急诊位特殊背景
  734. borderColor:
  735. selectedItemId.value === item.sampleId
  736. ? 'blue'
  737. : remainingTime === '已完成'
  738. ? 'red'
  739. : 'transparent',
  740. borderStyle:
  741. selectedItemId.value === item.sampleId || remainingTime === '已完成'
  742. ? 'solid'
  743. : 'none',
  744. }
  745. }
  746. // 切换选中状态
  747. let currentIndex = ref(0)
  748. const toggleSelectItem = (item: Subtank,index: number) => {
  749. // 如果点击的是已选中的样本,则取消选中
  750. if (selectedItemId.value === item.sampleId) {
  751. selectedItem.value = null
  752. selectedItemId.value = null
  753. } else {
  754. // 否则选中新的样本
  755. selectedItem.value = item
  756. selectedItemId.value = item.sampleId
  757. currentIndex.value = index
  758. }
  759. }
  760. //反应板数据
  761. const plates = ref(consumablesStore.plates)
  762. //样本数据
  763. const processedTubeSettings = ref()
  764. // 更新选中的样本列表
  765. const updateSelectedSamples = (sampleIds: number[]) => {
  766. selectedSamples.value = sampleIds
  767. }
  768. // 计算填充样式
  769. const getFillStyle = (item: any) => {
  770. const percentage = (item.tipNum / 120) * 100
  771. return {
  772. background: `linear-gradient(to top, #bbd3fb ${percentage}%,#c9c9c9 ${percentage}%)`,
  773. }
  774. }
  775. //控制废料区的状态
  776. const isFull = ref(consumablesStore.wasteStatus)
  777. const showWasteAlert = ref(false)
  778. // 获取废料区样式
  779. const getWasteStyle = () => ({
  780. backgroundColor: isFull.value ? '#d9534f' : '#5cb85c', // 红色表示满,绿色表示未满
  781. transition: 'background-color 0.3s ease', // 增加平滑过渡效果
  782. })
  783. const closeAlert = () => {
  784. showWasteAlert.value = false
  785. if (wasteAlertTimeout) clearTimeout(wasteAlertTimeout) // 确保清除防抖计时器
  786. }
  787. let wasteAlertTimeout: ReturnType<typeof setTimeout> | null = null
  788. watch(isFull, (newValue) => {
  789. if (newValue) {
  790. console.log('废料箱已满,更新样式并展示弹窗')
  791. } else {
  792. console.log('废料箱未满,恢复样式')
  793. }
  794. })
  795. const pollWasteAreaStatus = async () => {
  796. try {
  797. const response = await wasteArea()
  798. if (response.success) {
  799. const isWasteBinFull = response.data.wasteBinFullFlag
  800. // 更新废料箱状态
  801. if (isFull.value !== isWasteBinFull) {
  802. isFull.value = isWasteBinFull
  803. // 如果废料箱满,且未展示弹窗,则展示弹窗
  804. if (isFull.value && !showWasteAlert.value) {
  805. if (wasteAlertTimeout) clearTimeout(wasteAlertTimeout) // 清除可能存在的防抖计时器
  806. wasteAlertTimeout = setTimeout(() => {
  807. showWasteAlert.value = true
  808. }, 300) // 防抖时间,避免连续触发
  809. }
  810. }
  811. }
  812. } catch (error) {
  813. console.error('废料区接口轮询失败:', error)
  814. }
  815. }
  816. //轮询孵育盘列表
  817. // 轮询间隔
  818. const POLL_INTERVAL = 1000 // 每 秒获取一次最新据
  819. let pollIntervalId: ReturnType<typeof setInterval>
  820. // 获取当前孵育盘列表数据,并初始化倒计时
  821. const fetchIncubationData = async () => {
  822. try {
  823. const response = await getRunningList()
  824. let data: Subtank[] = []
  825. if (response && response.success) {
  826. // console.log('获取孵育盘列表成功:', response.data)
  827. // 处理服务器返回的数据
  828. data = response.data.subtanks.map((plate: Subtank) => ({
  829. ...plate,
  830. isSelected: plate.sampleId === selectedItemId.value,
  831. }))
  832. }
  833. // 添加急诊数据到末位(如果存在急诊数据)
  834. const filledData = emergencyData.value
  835. ? [...data, emergencyData.value]
  836. : data
  837. // 填充占位符,确保总数为 20 个
  838. while (filledData.length < TOTAL_SLOTS) {
  839. filledData.push({
  840. pos: `PLACEHOLDER-${filledData.length + 1}`,
  841. state: 'EMPTY',
  842. bloodType: '',
  843. sampleBarcode: '',
  844. userid: '',
  845. projInfo: {
  846. projId: 0,
  847. projName: '',
  848. projShortName: '',
  849. color: '',
  850. },
  851. startIncubatedTime: 0,
  852. incubatedTimeSec: 0,
  853. sampleId: `PLACEHOLDER-${filledData.length + 1}`,
  854. projId: 0,
  855. errors: [],
  856. isPlaceholder: true, // 标记为占位符
  857. })
  858. }
  859. // 更新 `incubationPlates` 数据
  860. // incubationPlates.value = filledData
  861. // 更新开始时间戳
  862. updateStartTimes()
  863. } catch (error) {
  864. console.error('获取孵育盘列表失败:', error)
  865. // 即使失败,也需要填充占位符
  866. const filledData = emergencyData.value ? [emergencyData.value] : []
  867. while (filledData.length < TOTAL_SLOTS) {
  868. filledData.push({
  869. pos: `PLACEHOLDER-${filledData.length + 1}`,
  870. state: 'EMPTY',
  871. bloodType: '',
  872. sampleBarcode: '',
  873. userid: '',
  874. projInfo: {
  875. projId: 0,
  876. projName: '',
  877. projShortName: '',
  878. color: '',
  879. },
  880. startIncubatedTime: 0,
  881. incubatedTimeSec: 0,
  882. sampleId: `PLACEHOLDER-${filledData.length + 1}`,
  883. projId: 0,
  884. errors: [],
  885. isPlaceholder: true,
  886. })
  887. }
  888. // incubationPlates.value = filledData
  889. }
  890. }
  891. // 更新开始时间戳(表示计时起点)
  892. const startTimes = ref<Record<string, number>>({})
  893. const updateStartTimes = () => {
  894. const currentTime = Date.now() // 获取当前时间戳
  895. incubationPlates.value?.forEach((plate) => {
  896. if (!startTimes.value[plate.pos]) {
  897. startTimes.value[plate.pos] = currentTime // 记录初始时戳
  898. }
  899. })
  900. }
  901. // 计算剩余时间
  902. const getRemainingTime = (plate: Subtank) => {
  903. const startTime = startTimes.value[plate.pos] || Date.now()
  904. const elapsed = (Date.now() - startTime) / 1000 // 已经过的秒数
  905. const remaining = Math.max(0, plate.incubatedTimeSec - elapsed) // 剩余时间,最小为0
  906. if (remaining === 0) return '已完成'
  907. const minutes = Math.floor(remaining / 60)
  908. const seconds = Math.floor(remaining % 60)
  909. return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
  910. }
  911. // 开始轮询
  912. const startPolling = () => {
  913. fetchIncubationData() // 初始化获取数据
  914. pollIntervalId = setInterval(fetchIncubationData, POLL_INTERVAL) // 设置轮询
  915. }
  916. // 结束轮询
  917. const stopPolling = () => {
  918. if (pollIntervalId) clearInterval(pollIntervalId)
  919. }
  920. //设置轮询
  921. let pollInterval: ReturnType<typeof setInterval>
  922. onMounted(() => {
  923. pollInterval = setInterval(pollWasteAreaStatus, 1000) //每一秒轮询一次
  924. startPolling()
  925. })
  926. onUnmounted(() => {
  927. clearInterval(pollInterval)
  928. stopPolling()
  929. })
  930. const emergencyResult = ref() // 存储急诊结果
  931. const isDialogVisible = ref(false) // 控制弹窗的显示
  932. let emergencyCompleted = ref(false) // 标志位,确保急诊结果只显示一次
  933. // 监听急诊位的反应时间并触���弹窗
  934. const watchEmergencyCompletion = () => {
  935. watch(
  936. () => incubationPlates.value,
  937. (newPlates) => {
  938. const emergencyPlate = newPlates.find(
  939. (plate) => plate.pos === 'EMERGENCY',
  940. )
  941. if (emergencyPlate && getRemainingTime(emergencyPlate) === '已完成') {
  942. if (!emergencyCompleted.value) {
  943. // 如果急诊结果还没显示过,显示弹窗并更新结果
  944. emergencyResult.value = {
  945. date: new Date().toLocaleString(),
  946. sampleBarcode: emergencyPlate.sampleBarcode,
  947. projInfo: emergencyPlate.projInfo,
  948. bloodType: emergencyPlate.bloodType,
  949. operator: 'admin', // 示例操作人
  950. expiryDate: '2025-12-31', // 示例数据
  951. serialNumber: 'SN12345678', // 示例数据
  952. reference: '参考值', // 示例数据
  953. value: '5.24 mg/L', // 示例数据
  954. }
  955. isDialogVisible.value = true
  956. emergencyCompleted.value = true // 设置为已显示
  957. }
  958. }
  959. },
  960. { deep: true },
  961. )
  962. }
  963. onMounted(() => {
  964. watchEmergencyCompletion()
  965. })
  966. // 停止监听的逻辑
  967. onUnmounted(() => {
  968. isDialogVisible.value = false
  969. emergencyResult.value = null
  970. })
  971. // 修改试管架状态的类型定义
  972. interface TubeHolderState {
  973. tubeHolderType: string
  974. tubes: Array<{
  975. sampleId: string | null
  976. pos: number
  977. isHighTube: boolean
  978. isEmergency: boolean
  979. bloodType: string
  980. sampleBarcode: string
  981. userid: string
  982. projInfo: any[]
  983. projIds: number[]
  984. state: string
  985. errors: string[]
  986. }>
  987. state: string
  988. }
  989. // 试管架状态
  990. const tubeHolderState = ref<TubeHolderState>({
  991. tubeHolderType: '',
  992. tubes: [],
  993. state: 'IDLE'
  994. })
  995. // 处理 WebSocket 消息
  996. const handleTubeHolderState = (data: TubeHolderStateMessage['data']) => {
  997. tubeHolderState.value = data
  998. }
  999. onMounted(() => {
  1000. // 订阅 WebSocket 消息
  1001. ws.subscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderState)
  1002. })
  1003. onUnmounted(() => {
  1004. // 取消订阅
  1005. ws.unsubscribe<TubeHolderStateMessage>('TubeHolderState', handleTubeHolderState)
  1006. })
  1007. </script>
  1008. <style lang="less" scoped>
  1009. #running-container {
  1010. display: flex;
  1011. flex-direction: column;
  1012. justify-content: space-around;
  1013. align-items: center;
  1014. background-color: #f9fafb;
  1015. height: 92vh;
  1016. /* 更柔和的背景色 */
  1017. // 孵育盘
  1018. .circular-loader {
  1019. position: relative;
  1020. width: 800px;
  1021. height: 800px;
  1022. border-radius: 50%;
  1023. background-color: #f0f2f5;
  1024. display: flex;
  1025. align-items: center;
  1026. justify-content: center;
  1027. margin: 0 auto;
  1028. border: 8px solid #ffffff;
  1029. box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
  1030. /* 轻微阴影增加立体感 */
  1031. .center-square {
  1032. width: 380px; // 调整中心矩形的宽度
  1033. height: 380px; // 调整中心矩形的高度
  1034. background-color: #ffffff;
  1035. border-radius: 50%;
  1036. position: absolute;
  1037. border: 8px solid #ffffff;
  1038. box-shadow: inset 0px 4px 10px rgba(0, 0, 0, 0.1);
  1039. display: flex;
  1040. flex-direction: column;
  1041. justify-content: space-around;
  1042. gap: 20px;
  1043. z-index: 2;
  1044. span {
  1045. text-align: center;
  1046. }
  1047. span:nth-child(3),
  1048. span:nth-child(6) {
  1049. font-size: 32px;
  1050. font-weight: bold;
  1051. }
  1052. span:nth-child(1),
  1053. span:nth-child(2),
  1054. span:nth-child(4),
  1055. span:nth-child(5) {
  1056. font-size: 24px;
  1057. font-weight: 300;
  1058. }
  1059. }
  1060. .rectangular-item {
  1061. position: absolute;
  1062. left: 50%; // 将所有矩形的起始位置设置在圆心
  1063. top: 74.5%; // 将所有矩形的起始位置设置在圆心
  1064. width: 75px;
  1065. height: 170px;
  1066. transform-origin: center -110px; // 调整旋转中心点
  1067. border-radius: 12px;
  1068. background-color: #721616;
  1069. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  1070. display: flex;
  1071. flex-direction: column;
  1072. align-items: center;
  1073. padding: 15px 8px;
  1074. box-sizing: border-box;
  1075. cursor: pointer;
  1076. .icon {
  1077. width: 60px;
  1078. height: 60px;
  1079. margin: 0 auto;
  1080. }
  1081. .text {
  1082. position: absolute;
  1083. top: 60%;
  1084. left: 50%;
  1085. transform: translate(-50%, -50%);
  1086. font-size: 24px;
  1087. color: #fff;
  1088. font-weight: bold;
  1089. }
  1090. &.placeholder {
  1091. background-color: #f8f8f8;
  1092. box-shadow: none;
  1093. opacity: 0.6;
  1094. }
  1095. .barcode {
  1096. font-size: 16px;
  1097. font-weight: bold;
  1098. color: #333;
  1099. margin-bottom: 10px;
  1100. word-break: break-all; // 自动换行
  1101. text-align: center;
  1102. }
  1103. .blood-type {
  1104. font-size: 18px;
  1105. color: #666;
  1106. margin-bottom: auto;
  1107. }
  1108. .time {
  1109. font-size: 20px;
  1110. font-weight: bold;
  1111. color: #333;
  1112. position: absolute;
  1113. bottom: 15px;
  1114. &.completed {
  1115. color: #ff4d4f;
  1116. }
  1117. }
  1118. }
  1119. }
  1120. // 耗材区
  1121. .consumables-container {
  1122. width: 100%;
  1123. box-sizing: border-box;
  1124. padding: 0 30px;
  1125. display: flex;
  1126. flex-direction: column;
  1127. background-color: #ffffff;
  1128. box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05);
  1129. border-radius: 20px 20px 0 0;
  1130. // 第一行
  1131. .row-first {
  1132. display: flex;
  1133. align-items: center;
  1134. justify-content: flex-start;
  1135. // 急诊按钮
  1136. .emergency-button {
  1137. background: linear-gradient(135deg, #ff6b6b, #ff4757);
  1138. border-radius: 20px;
  1139. display: flex;
  1140. align-items: center;
  1141. justify-content: center;
  1142. cursor: pointer;
  1143. transition: all 0.3s ease;
  1144. box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3);
  1145. &:hover {
  1146. transform: translateY(-2px);
  1147. box-shadow: 0 6px 20px rgba(255, 107, 107, 0.4);
  1148. }
  1149. span {
  1150. font-size: 32px;
  1151. color: #ffffff;
  1152. font-weight: bold;
  1153. }
  1154. }
  1155. // 试管架区域
  1156. .test-tube-rack-area {
  1157. display: flex;
  1158. align-items: center;
  1159. gap: 20px;
  1160. .tube-project-tab {
  1161. display: flex;
  1162. align-items: center;
  1163. justify-content: center;
  1164. font-weight: bold;
  1165. color: #1976d2;
  1166. position: relative;
  1167. box-shadow: 0 2px 8px rgba(25, 118, 210, 0.1);
  1168. &::after {
  1169. content: '';
  1170. width: 3px;
  1171. height: 120px;
  1172. background: linear-gradient(to bottom, #e0341d, #fa4f0b);
  1173. position: absolute;
  1174. right: -30px;
  1175. }
  1176. }
  1177. }
  1178. }
  1179. //第二行
  1180. .row-second {
  1181. display: grid;
  1182. grid-template-columns: 3fr 4fr 2fr 1fr;
  1183. padding: 10px 0;
  1184. .tips-and-big-buffer {
  1185. display: flex;
  1186. flex-direction: column;
  1187. gap: 20px;
  1188. .tips-item {
  1189. width: 200px;
  1190. height: 137px;
  1191. border-radius: 16px;
  1192. display: flex;
  1193. align-items: center;
  1194. justify-content: center;
  1195. position: relative;
  1196. background: #f8f9fa;
  1197. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  1198. overflow: hidden;
  1199. .tip-fill {
  1200. position: absolute;
  1201. width: 100%;
  1202. height: 100%;
  1203. border-radius: 16px;
  1204. transition: all 0.3s ease;
  1205. background: linear-gradient(to top, rgba(92, 184, 92, 0.1), transparent);
  1206. }
  1207. .tip-text {
  1208. font-size: 36px;
  1209. color: #2c3e50;
  1210. font-weight: bold;
  1211. z-index: 1;
  1212. text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
  1213. }
  1214. }
  1215. }
  1216. .buffer-grid {
  1217. margin-top: -10px;
  1218. }
  1219. .waste-area {
  1220. border-radius: 25px;
  1221. display: flex;
  1222. flex-direction: column;
  1223. justify-content: center;
  1224. align-items: center;
  1225. transition: all 0.3s ease;
  1226. background: #f8f9fa;
  1227. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  1228. .waste-text {
  1229. font-size: 28px;
  1230. font-weight: 700;
  1231. color: #ffffff;
  1232. writing-mode: vertical-rl;
  1233. text-orientation: upright;
  1234. letter-spacing: 4px;
  1235. text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  1236. }
  1237. &:hover {
  1238. transform: translateY(-2px);
  1239. }
  1240. }
  1241. }
  1242. // 添加响应式设计
  1243. @media screen and (max-width: 800px) {
  1244. padding: 15px 20px; // 修改padding值以适应小屏幕
  1245. .row-first {
  1246. margin-bottom: 20px;
  1247. gap: 20px;
  1248. .emergency-button {
  1249. width: 200px;
  1250. height: 100px;
  1251. span {
  1252. font-size: 28px;
  1253. }
  1254. }
  1255. }
  1256. .row-second {
  1257. gap: 15px;
  1258. .tips-item {
  1259. width: 180px;
  1260. height: 120px;
  1261. .tip-text {
  1262. font-size: 32px;
  1263. }
  1264. }
  1265. .waste-area {
  1266. width: 90px;
  1267. height: 285px;
  1268. .waste-text {
  1269. font-size: 24px;
  1270. }
  1271. }
  1272. }
  1273. }
  1274. }
  1275. }
  1276. .alert-overlay {
  1277. position: fixed;
  1278. top: 0;
  1279. left: 0;
  1280. width: 100%;
  1281. height: 100%;
  1282. background-color: rgba(0, 0, 0, 0.7);
  1283. backdrop-filter: blur(5px);
  1284. display: flex;
  1285. justify-content: center;
  1286. align-items: center;
  1287. z-index: 1000;
  1288. .alert-container {
  1289. background-color: #ffffff;
  1290. padding: 40px;
  1291. border-radius: 20px;
  1292. text-align: center;
  1293. max-width: 600px;
  1294. animation: slideIn 0.3s ease;
  1295. position: relative;
  1296. .alert-icon {
  1297. .icon {
  1298. width: 80px;
  1299. height: 80px;
  1300. margin: 0 auto;
  1301. }
  1302. span {
  1303. font-size: 32px;
  1304. position: absolute;
  1305. top: 30%;
  1306. left: 50%;
  1307. transform: translate(-50%, -50%);
  1308. color: #fff;
  1309. }
  1310. }
  1311. .alert-title {
  1312. font-size: 36px;
  1313. font-weight: bold;
  1314. color: #d9534f;
  1315. margin-bottom: 20px;
  1316. }
  1317. .alert-message {
  1318. font-size: 28px;
  1319. color: #495057;
  1320. margin-bottom: 30px;
  1321. }
  1322. .action-buttons {
  1323. display: flex;
  1324. justify-content: center;
  1325. gap: 20px;
  1326. .el-button {
  1327. min-width: 160px;
  1328. height: 50px;
  1329. font-size: 24px;
  1330. border-radius: 25px;
  1331. transition: all 0.3s ease;
  1332. &:hover {
  1333. transform: translateY(-2px);
  1334. }
  1335. }
  1336. }
  1337. }
  1338. }
  1339. @keyframes slideIn {
  1340. from {
  1341. opacity: 0;
  1342. transform: translateY(-20px);
  1343. }
  1344. to {
  1345. opacity: 1;
  1346. transform: translateY(0);
  1347. }
  1348. }
  1349. // 添加动画
  1350. @keyframes pulse {
  1351. 0% {
  1352. transform: scale(1);
  1353. }
  1354. 50% {
  1355. transform: scale(1.05);
  1356. }
  1357. 100% {
  1358. transform: scale(1);
  1359. }
  1360. }
  1361. .quan {
  1362. width: 18px;
  1363. height: 18px;
  1364. line-height: 18px;
  1365. text-align: center;
  1366. border-radius: 50%;
  1367. border: 2px solid red;
  1368. font-size: 13px;
  1369. }
  1370. .tube-container {
  1371. display: flex;
  1372. flex-direction: row;
  1373. flex-wrap: nowrap;
  1374. justify-content: space-around;
  1375. align-items: center;
  1376. height: auto;
  1377. padding: 20px;
  1378. width: 100%;
  1379. }
  1380. </style>