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.

1171 lines
30 KiB

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
7 months ago
8 months ago
8 months ago
7 months ago
8 months ago
8 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
8 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
8 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
8 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
8 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
7 months ago
8 months ago
8 months ago
8 months ago
7 months ago
8 months ago
8 months ago
7 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
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
7 months ago
8 months ago
8 months ago
8 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
  1. <template>
  2. <div id="index-container">
  3. <div class="loading-overlay" v-if="isLoading">
  4. <div class="loading-content">
  5. <div class="loading-spinner"></div>
  6. <span class="loading-text">{{ shutdownMessage }}</span>
  7. </div>
  8. </div>
  9. <el-header class="nav-bar">
  10. <div class="logo">
  11. <img src="@/assets/Index/logo.svg" alt="" width="150px" />
  12. </div>
  13. <div class="nav-tabs" @click="handleTabClick">
  14. <!-- 使用 router-link 进行路由跳转 -->
  15. <router-link
  16. to="/index/regular"
  17. class="nav-tab"
  18. :class="{ active: selectedTab === '常规' }"
  19. data-tab="常规"
  20. >常规</router-link
  21. >
  22. <router-link
  23. to="/index/history"
  24. class="nav-tab"
  25. :class="{ active: selectedTab === '历史' }"
  26. data-tab="历史"
  27. >历史</router-link
  28. >
  29. <router-link
  30. to="/index/setting"
  31. class="nav-tab"
  32. :class="{ active: selectedTab === '设置' }"
  33. data-tab="设置"
  34. >设置</router-link
  35. >
  36. </div>
  37. <div
  38. v-if="deviceStore.deviceState.workState === 'IDLE'"
  39. class="test-control"
  40. >
  41. <!-- 显示开始测试按钮 -->
  42. <el-button type="primary" class="start-test" @click="startTest"
  43. >开始测试</el-button
  44. >
  45. </div>
  46. <div v-else class="test-control">
  47. <!-- 显示暂停和停止按钮 -->
  48. <el-button
  49. type="warning"
  50. class="pause-test"
  51. @click="pauseTest"
  52. v-if="deviceStore.deviceState.workState !== 'PAUSE'"
  53. >暂停</el-button
  54. >
  55. <el-button
  56. type="success"
  57. class="continue-test"
  58. @click="continueTest"
  59. v-else
  60. >继续</el-button
  61. >
  62. <el-button type="danger" class="stop-test" @click="stopTest"
  63. >停止</el-button
  64. >
  65. </div>
  66. </el-header>
  67. <!-- 间隔线 -->
  68. <div class="interval">
  69. <div
  70. class="blue-line"
  71. :style="{ width: `${lineWidth}px`, left: `${lineLeft}px` }"
  72. ></div>
  73. </div>
  74. <!-- 主内容区域 -->
  75. <main class="main-content">
  76. <router-view v-slot="{ Component }">
  77. <keep-alive :exclude="['TubeUserId', 'EmergencyForm']">
  78. <component :is="Component" />
  79. </keep-alive>
  80. </router-view>
  81. </main>
  82. <!-- 底部操作信息 -->
  83. <el-footer class="footer-info">
  84. <el-dropdown placement="top-start" style="margin-left:-10px">
  85. <div class="user-card">
  86. <img class="user-logo" src="@/assets/Index/user.svg" />
  87. <div class="user-name">
  88. {{
  89. deviceStore.contextState.loginFlag
  90. ? deviceStore.contextState.loginUser.account
  91. : '未登录'
  92. }}
  93. </div>
  94. </div>
  95. <template #dropdown>
  96. <el-dropdown-menu style="width:100px">
  97. <el-dropdown-item >
  98. <div @click="onInitDevice" style="display: flex;">
  99. <img :src="InitSvg" alt="Icon" width="15" />
  100. <div class="pd-5">初始化</div>
  101. </div>
  102. </el-dropdown-item>
  103. <el-dropdown-item >
  104. <div @click="onCloseDevice" style="display: flex;">
  105. <img :src="CloseSvg" alt="Icon" width="15" />
  106. <div class="pd-5">关机</div>
  107. </div>
  108. </el-dropdown-item>
  109. <el-dropdown-item>
  110. <div @click="onLogout" style="display: flex;">
  111. <img :src="LogoutSvg" alt="Icon" width="15" />
  112. <div class="pd-5">退出</div>
  113. </div>
  114. </el-dropdown-item>
  115. </el-dropdown-menu>
  116. </template>
  117. </el-dropdown>
  118. <div class="equipment-status" @click="showRecentMsgDialog = true">
  119. <div
  120. class="status-text"
  121. :class="deviceStore.messageState.topMessage.messageLevel"
  122. >
  123. {{ deviceStore.messageState.topMessage.message }}
  124. </div>
  125. </div>
  126. <div class="time-card">
  127. <img class="time-logo" src="@/assets/Index/clock.svg" />
  128. <div class="time-text">
  129. <Time></Time>
  130. </div>
  131. </div>
  132. </el-footer>
  133. <el-dialog
  134. class="recent-msg-dialog"
  135. v-model="showRecentMsgDialog"
  136. title="最近消息"
  137. width="700"
  138. >
  139. <div class="msg-container">
  140. <div
  141. class="msg-item"
  142. v-for="item in deviceStore.messageState.messageBoxList"
  143. :key="item.time"
  144. :class="[item.messageLevel]"
  145. >
  146. <span>{{ formatDate(item.time) }}</span>
  147. <span>{{ item.message }}</span>
  148. </div>
  149. </div>
  150. </el-dialog>
  151. <el-dialog
  152. class="event-report-dialog"
  153. v-model="showEventReportDlg"
  154. width="600"
  155. :show-close="false"
  156. >
  157. <template #header>
  158. <div class="report-title">
  159. {{ currEventReport ? currEventReport.prompt.title : '' }}
  160. </div>
  161. <div class="report-desc">
  162. {{ currEventReport ? currEventReport.prompt.info : '' }}
  163. </div>
  164. </template>
  165. <div class="report-detail">
  166. <div
  167. v-if="
  168. currEventReport && currEventReport.prompt.detailInfoType === 'Forms'
  169. "
  170. >
  171. <div
  172. class="report-form-item"
  173. v-for="(item, index) in currEventReport.prompt.detailInfo"
  174. :key="index"
  175. >
  176. <span>{{ item.name + ' :' }}</span>
  177. <span>{{ item.description }}</span>
  178. </div>
  179. </div>
  180. <div
  181. v-else-if="
  182. currEventReport && currEventReport.prompt.detailInfoType === 'Table'
  183. "
  184. >
  185. <div class="report-table-container">
  186. <div class="report-table-header">
  187. <div
  188. v-for="(item, index) in currEventReport.prompt.detailInfo
  189. .header"
  190. :key="index"
  191. >
  192. <span>{{ item }}</span>
  193. </div>
  194. </div>
  195. <div class="report-table-rows">
  196. <div
  197. class="report-table-row"
  198. v-for="(row, index) in currEventReport.prompt.detailInfo.vars"
  199. :key="index"
  200. >
  201. <div v-for="(col, idx) in row" :key="idx">
  202. {{ col.val }}
  203. </div>
  204. </div>
  205. </div>
  206. </div>
  207. </div>
  208. <span v-else>
  209. {{ currEventReport ? currEventReport.prompt.detailInfo : '' }}
  210. </span>
  211. </div>
  212. <template #footer>
  213. <div class="dialog-footer">
  214. <el-button type="primary" @click="onConfirmReport"> 确定 </el-button>
  215. </div>
  216. </template>
  217. </el-dialog>
  218. <InitWarn
  219. v-if="showModal"
  220. :visible="showModal"
  221. title="确认操作"
  222. message="确认试管架中的试管槽是否取下,避免自检/自动复位时对设备产生损坏!"
  223. icon="/src/assets/Warn.svg"
  224. cancelText="返回"
  225. confirmText="确认取下/开始复位"
  226. @close="showModal = false"
  227. @confirm="handleConfirm"
  228. />
  229. <!-- 通用加载弹窗组件 -->
  230. <LoadingModal
  231. v-if="showDeviceResettingModal"
  232. :visible="showDeviceResettingModal"
  233. title="正在自检/自动复位中"
  234. message="请不要有任何手动操作!"
  235. cancelText="返回"
  236. confirmText="确认取下/开始复位"
  237. disableButtons
  238. @close="showDeviceResettingModal = false"
  239. />
  240. <LoadingModal
  241. v-if="showDeviceWaitingModal"
  242. :visible="showDeviceWaitingModal"
  243. :title="deviceWaitingModelInfo.title"
  244. :message="deviceWaitingModelInfo.message"
  245. :showBtns="false"
  246. />
  247. <!-- 自动自检已完成 -->
  248. <InitWarn
  249. v-if="showAlreadyModal"
  250. :visible="showAlreadyModal"
  251. title="确认操作"
  252. message="自检/自动复位已完成!"
  253. icon="/src/assets/OK.svg"
  254. cancelText="返回"
  255. confirmText="确认"
  256. @close="showAlreadyModal = false"
  257. @confirm="handleAlreadyConfirm"
  258. />
  259. <!-- 自动自检失败 -->
  260. <InitWarn
  261. v-if="showFailModal"
  262. :visible="showFailModal"
  263. :title="failMessage.title"
  264. :message="failMessage.message"
  265. icon="/src/assets/Warn.svg"
  266. cancelText="返回"
  267. confirmText="重试"
  268. @close="showFailModal = false"
  269. @confirm="confirmFail"
  270. />
  271. <InitWarn
  272. v-if="idCardInserted"
  273. :visible="idCardInserted"
  274. title="检测到id卡插入,是否保存?"
  275. :message="idCardMessage"
  276. cancelText="返回"
  277. icon="/src/assets/update-pin-icon.svg"
  278. confirmText="确认保存"
  279. @close="idCardInserted = false"
  280. @confirm="saveIdInfo"
  281. />
  282. <InitWarn
  283. v-if="showErrorModal"
  284. :visible="showErrorModal"
  285. title="错误提示"
  286. :message="ErrorMessage"
  287. icon="/src/assets/Warn.svg"
  288. cancelText="返回"
  289. confirmText="确认"
  290. @close="confirmError"
  291. @confirm="confirmError"
  292. />
  293. <InitWarn
  294. v-if="showWarnModal"
  295. :visible="showWarnModal"
  296. title="注意"
  297. :message="WarnMessage"
  298. cancelText="返回"
  299. icon="/src/assets/update-pin-icon.svg"
  300. confirmText="确认"
  301. @close="confirmWarn"
  302. @confirm="confirmWarn"
  303. />
  304. <Confirm :isLoading="confirmVisible" :confirmInfo="confirmInfo"></Confirm>
  305. </div>
  306. </template>
  307. <script setup lang="ts">
  308. import { useRouter } from 'vue-router'
  309. import { ref, onMounted, computed, watch } from 'vue'
  310. import { ElDialog } from 'element-plus'
  311. import { Time, InitWarn, LoadingModal } from './components/Consumables'
  312. import {
  313. startWork,
  314. pauseWork,
  315. continueWork,
  316. stopWork,
  317. getDeviceWorkState,
  318. getInitState,
  319. initDevice,
  320. saveMountedCardInfo,
  321. closeBuzzer,
  322. getProjectInfo,
  323. getBloodTypes,
  324. confirmPromptInfo,
  325. } from '../../services/index'
  326. import {
  327. useConsumablesStore,
  328. useDeviceStore,
  329. useEmergencyStore,
  330. useSettingTestTubeStore,
  331. useTestTubeStore,
  332. } from '../../store'
  333. import { createWebSocket } from '../../websocket/socket'
  334. import type {
  335. AppEventMessage,
  336. ConsumablesStateMessage,
  337. DeviceContextStateMessage,
  338. DeviceWorkStateMessage,
  339. EmergencyPosStateMessage,
  340. EventReport,
  341. FooterMessageState,
  342. IncubationPlateStateMessage,
  343. OptScanModuleStateMessage,
  344. SensorStateMessage,
  345. TubeHolderSettingMessage,
  346. TubeHolderStateMessage,
  347. } from '../../websocket/socket'
  348. import { getServerInfo } from '../../utils/getServerInfo'
  349. import { eventBus } from '../../eventBus'
  350. import { logout, shutdown } from '@/services/Login/login'
  351. import { useRunningStore } from '@/store/modules/running'
  352. import { formatDate } from '@/utils/formDate'
  353. import { eMessage } from './utils'
  354. import InitSvg from '@/assets/init.svg'
  355. import CloseSvg from '@/assets/close.svg'
  356. import LogoutSvg from '@/assets/logout.svg'
  357. const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规')
  358. const lineWidth = ref(0)
  359. const lineLeft = ref(0)
  360. const showModal = ref(false)
  361. const showDeviceResettingModal = ref(false)
  362. const showDeviceWaitingModal = ref(false)
  363. const deviceWaitingModelInfo = ref({
  364. title: '设备正在响应中',
  365. message: '请不要有任何手动操作!',
  366. })
  367. const showAlreadyModal = ref(false)
  368. const showFailModal = ref(false)
  369. // const checkData = ref<CheckItem[]>([]);
  370. // const failItems = ref<CheckItem[]>([]);
  371. const consumableStore = useConsumablesStore()
  372. const deviceStore = useDeviceStore()
  373. const runningStore = useRunningStore()
  374. const settingTubeStore = useSettingTestTubeStore()
  375. const tubeRackStore = useTestTubeStore()
  376. const emergencyStore = useEmergencyStore()
  377. //关机中message
  378. const shutdownMessage = ref('正在关机中…')
  379. const confirmInfo = ref({})
  380. const confirmVisible = ref(false)
  381. const idCardMessage = ref('是否保存id卡信息')
  382. const failMessage = ref({
  383. title: '检测失败',
  384. message: '',
  385. }) // 存储动态生成的错误信息
  386. const idCardInserted = ref(false) // id卡插入状态
  387. //事件状态
  388. const EventText = ref<string>('闲置...')
  389. const showWarnModal = ref(false)
  390. const ErrorMessage = ref<string>('')
  391. const showErrorModal = ref(false)
  392. const WarnMessage = ref<string>('')
  393. const showRecentMsgDialog = ref(false)
  394. // WebSocket 实例
  395. const eventUrl = getServerInfo('/api/v1/app/ws/event')
  396. const wsEvent = createWebSocket(eventUrl.wsUrl)
  397. const stateUrl = getServerInfo('/api/v1/app/ws/state')
  398. const wsState = createWebSocket(stateUrl.wsUrl)
  399. const eventReports = ref<EventReport[]>([])
  400. const showEventReportDlg = ref(false)
  401. const currEventReport = computed(() => {
  402. return eventReports.value.length > 0 ? eventReports.value[0] : undefined
  403. })
  404. const onConfirmReport = async () => {
  405. if (eventReports.value.length > 0) {
  406. const report = eventReports.value.shift()
  407. const res = await confirmPromptInfo(report!.eventId)
  408. if (res && res.success) {
  409. // do nothing
  410. } else {
  411. res && res.data && res.data.info && console.error(res.data.info)
  412. }
  413. }
  414. if (eventReports.value.length === 0) {
  415. showEventReportDlg.value = false
  416. }
  417. }
  418. const handleSocketClose = (num: number) => {
  419. if (num === 0) {
  420. // socket连接断开
  421. deviceWaitingModelInfo.value = {
  422. title: '系统产生错误,请重启系统',
  423. message: '请尽快重启系统',
  424. }
  425. showDeviceWaitingModal.value = true
  426. } else if (num === -1) {
  427. // 从断开 到 恢复连接, 关闭弹框
  428. showDeviceWaitingModal.value = false
  429. }
  430. // else if (num === 5) {
  431. // // 重试达到最大次数,重试,取消。
  432. // failMessage.value = {
  433. // title: '恢复连接失败',
  434. // message: '未能恢复连接,建议重启设备',
  435. // }
  436. // showFailModal.value = true
  437. // }
  438. }
  439. // 处理应用事件消息
  440. // const formatDate = (date: any) => new Date(date).toLocaleDateString()
  441. const handleAppEvent = (data: AppEventMessage['data']) => {
  442. console.log('🚀 ~ handleAppEvent ~ data:', data)
  443. if (data.typeName === 'AppPromptEvent') {
  444. eventReports.value.push(data)
  445. showEventReportDlg.value = true
  446. } else if (data.typeName === 'AppIDCardMountEvent') {
  447. consumableStore.setIdCardInserted(true)
  448. let projectInfo = data.projectInfo
  449. consumableStore.setIdCardInfo(projectInfo)
  450. //显示部分id卡信息。 项目名称,过期时间,批次号
  451. idCardInserted.value = true
  452. //接收端是用v-html处理
  453. idCardMessage.value = `
  454. <div style='line-height:0.5;margin-top: -90px;'>
  455. <div style='margin-left:25%'>
  456. <div style='display:flex'><div>项目名称</div><div>${projectInfo.projName}</div></div>
  457. <div style='display:flex'><div>批次号</div><div>${projectInfo.lotId}</div></div>
  458. <div style='display:flex'><div>过期时间</div><div>${formatDate(projectInfo.expiryDate)}</div></div>
  459. </div>
  460. </div>`
  461. } else if (data.typeName === 'AppIDCardUnmountEvent') {
  462. consumableStore.setIdCardInserted(false)
  463. consumableStore.setIdCardInfo(null)
  464. idCardInserted.value = false
  465. }
  466. }
  467. //注销用户
  468. const router = useRouter()
  469. const onLogout = () => {
  470. logout().then(() => {
  471. router.push({
  472. path: '/login',
  473. })
  474. sessionStorage.setItem('token', '')
  475. })
  476. }
  477. const confirmFail = async () => {
  478. showFailModal.value = false
  479. await startInit()
  480. }
  481. //确认错误事件
  482. const confirmError = async () => {
  483. showErrorModal.value = false
  484. //关闭蜂鸣器
  485. await closeBuzzer()
  486. EventText.value = '闲置...'
  487. }
  488. //确认警告事件
  489. const confirmWarn = async () => {
  490. showWarnModal.value = false
  491. }
  492. //保存id卡信息
  493. const saveIdInfo = async () => {
  494. const res = await saveMountedCardInfo()
  495. if (res.success) {
  496. console.log('保存id卡信息成功')
  497. idCardInserted.value = false
  498. }
  499. }
  500. //初始化设备
  501. const onInitDevice = ()=> {
  502. eventBus.emit('initDevice')
  503. }
  504. //关机
  505. let isLoading = ref(false)
  506. const onCloseDevice = ()=> {
  507. // ElMessageBox.alert()
  508. //检查设备状态 设备运行时不可关机
  509. const deviceState = deviceStore.deviceState.workState;
  510. if(deviceState == 'WORKING'){
  511. showWarnModal.value = true;
  512. WarnMessage.value = '设备正在运行中,不能进行关机操作'
  513. return
  514. }
  515. confirmVisible.value = true
  516. confirmInfo.value = {
  517. title: '提示',
  518. message: '请确认是否进行关机操作?',
  519. cancelBtn: '取消',
  520. OkBtn: '确认',
  521. confirmCallback:()=>{
  522. //关机时的遮罩层
  523. isLoading.value = true;
  524. confirmVisible.value = false;
  525. //有时接口返回的太快,看不到loading状态,延迟1s,看一到loading状态。
  526. setTimeout(async ()=>{
  527. await shutdown()
  528. shutdownMessage.value = '设备已关机,请拔掉电源'
  529. },1000)
  530. },
  531. cancelCallback: ()=>{
  532. confirmVisible.value = false;
  533. }
  534. }
  535. }
  536. const showInitDeviceAlert = () => {
  537. showModal.value = true
  538. }
  539. const handleDeviceState = (data: DeviceWorkStateMessage['data']) => {
  540. deviceStore.setDeviceState(data)
  541. }
  542. const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => {
  543. deviceStore.setContextState(data)
  544. }
  545. const handleSensorState = (data: SensorStateMessage['data']) => {
  546. deviceStore.setSensorState(data)
  547. }
  548. const handleFooterState = (data: FooterMessageState['data']) => {
  549. deviceStore.setMessageState(data)
  550. }
  551. const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
  552. runningStore.setTubeHolderState(data)
  553. }
  554. const handleTubeHolderSettingMessage = (
  555. data: TubeHolderSettingMessage['data'],
  556. ) => {
  557. tubeRackStore.setTubeRacks(data)
  558. }
  559. const handleOptScanModuleStateMessage = (
  560. data: OptScanModuleStateMessage['data'],
  561. ) => {
  562. runningStore.setOptScanModuleState(data)
  563. }
  564. const handleIncubationPlateStateMessage = (
  565. data: IncubationPlateStateMessage['data'],
  566. ) => {
  567. runningStore.setSubTanks(data.subtanks)
  568. }
  569. const handleConsumablesState = (data: ConsumablesStateMessage['data']) => {
  570. consumableStore.setConsumablesData(data)
  571. }
  572. const handleEmergencyPosState = (data: EmergencyPosStateMessage['data']) => {
  573. emergencyStore.setInfo(data.tube)
  574. }
  575. const getProjectList = async () => {
  576. const res = await getProjectInfo()
  577. if (res.success) {
  578. settingTubeStore.setSupportedProjects(res.data)
  579. }
  580. }
  581. const getBloodTypeList = async () => {
  582. const res = await getBloodTypes()
  583. if (res.success) {
  584. settingTubeStore.setBloodTypes(res.data)
  585. }
  586. }
  587. onMounted(() => {
  588. eventBus.on('initDevice', showInitDeviceAlert)
  589. eventBus.on('socketClosed', handleSocketClose)
  590. wsEvent.subscribe<AppEventMessage>('AppEvent', handleAppEvent)
  591. wsEvent.connect()
  592. wsState.subscribe<ConsumablesStateMessage>(
  593. 'ConsumablesState',
  594. handleConsumablesState,
  595. )
  596. wsState.subscribe<DeviceWorkStateMessage>(
  597. 'DeviceWorkState',
  598. handleDeviceState,
  599. )
  600. wsState.subscribe<DeviceContextStateMessage>(
  601. 'DeviceContext',
  602. handleDeviceContextState,
  603. )
  604. wsState.subscribe<SensorStateMessage>('SensorState', handleSensorState)
  605. wsState.subscribe<FooterMessageState>('MessageBoxState', handleFooterState)
  606. wsState.subscribe<TubeHolderStateMessage>(
  607. 'TubeHolderState',
  608. handleTubeHolderStateMessage,
  609. )
  610. wsState.subscribe<TubeHolderSettingMessage>(
  611. 'TubeHolderSetting',
  612. handleTubeHolderSettingMessage,
  613. )
  614. wsState.subscribe<IncubationPlateStateMessage>(
  615. 'IncubationPlateState',
  616. handleIncubationPlateStateMessage,
  617. )
  618. wsState.subscribe<OptScanModuleStateMessage>(
  619. 'OptScanModuleState',
  620. handleOptScanModuleStateMessage,
  621. )
  622. wsState.subscribe<EmergencyPosStateMessage>(
  623. 'EmergencyPosState',
  624. handleEmergencyPosState,
  625. )
  626. wsState.connect()
  627. getProjectList()
  628. getBloodTypeList()
  629. })
  630. // onBeforeUnmount(() => {
  631. // eventBus.off('initDevice', showInitDeviceAlert)
  632. // ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent)
  633. // ws.disconnect()
  634. // })
  635. // 动态生成错误信息
  636. // const generateErrorMessages = (data: CheckItem[]): string[] => {
  637. // return data
  638. // .filter(item => !item.pass)
  639. // .map(item => `错误:${item.typechinfo} 检测未通过,请检查设备状态。`);
  640. // };
  641. const untilDeviceReady = async () => {
  642. deviceWaitingModelInfo.value = {
  643. title: '设备正在响应中',
  644. message: '请不要有任何手动操作!',
  645. }
  646. showDeviceWaitingModal.value = true
  647. const res = await getDeviceWorkState()
  648. if (res.ecode === 'SUC') {
  649. if (res.data.pending) {
  650. setTimeout(async () => await untilDeviceReady(), 250)
  651. } else {
  652. showDeviceWaitingModal.value = false
  653. }
  654. } else {
  655. showDeviceWaitingModal.value = false
  656. }
  657. }
  658. // 开始测试
  659. const startTest = async () => {
  660. const res = await getInitState()
  661. if (res.ecode === 'SUC' && !res.data.deviceInited) {
  662. eventBus.emit('initDevice')
  663. return
  664. }
  665. try {
  666. deviceWaitingModelInfo.value = {
  667. title: '设备正在响应中',
  668. message: '请不要有任何手动操作!',
  669. }
  670. showDeviceWaitingModal.value = true
  671. const res = await startWork()
  672. showDeviceWaitingModal.value = false
  673. if (res && res.success) {
  674. await untilDeviceReady()
  675. } else {
  676. res && res.data && res.data.info && eMessage.error(res.data.info)
  677. }
  678. } catch (error) {
  679. console.error('开始测试失败:', error)
  680. }
  681. }
  682. // 暂停测试
  683. const pauseTest = async () => {
  684. deviceWaitingModelInfo.value = {
  685. title: '设备正在响应中',
  686. message: '请不要有任何手动操作!',
  687. }
  688. showDeviceWaitingModal.value = true
  689. const res = await pauseWork()
  690. showDeviceWaitingModal.value = false
  691. if (res && res.success) {
  692. await untilDeviceReady()
  693. } else {
  694. res && res.data && res.data.info && eMessage.error(res.data.info)
  695. }
  696. }
  697. // 停止测试时清除标记
  698. const stopTest = async () => {
  699. deviceWaitingModelInfo.value = {
  700. title: '设备正在响应中',
  701. message: '请不要有任何手动操作!',
  702. }
  703. showDeviceWaitingModal.value = true
  704. const res = await stopWork()
  705. showDeviceWaitingModal.value = false
  706. if (res && res.success) {
  707. await untilDeviceReady()
  708. } else {
  709. res && res.data && res.data.info && eMessage.error(res.data.info)
  710. }
  711. }
  712. //继续测试
  713. const continueTest = async () => {
  714. deviceWaitingModelInfo.value = {
  715. title: '设备正在响应中',
  716. message: '请不要有任何手动操作!',
  717. }
  718. showDeviceWaitingModal.value = true
  719. const res = await continueWork()
  720. showDeviceWaitingModal.value = false
  721. if (res && res.success) {
  722. await untilDeviceReady()
  723. } else {
  724. res && res.data && res.data.info && eMessage.error(res.data.info)
  725. }
  726. }
  727. const handleConfirm = async () => {
  728. showModal.value = false // 关闭初始弹窗
  729. await startInit()
  730. }
  731. const startInit = async () => {
  732. await initDevice()
  733. await pollingInitState()
  734. }
  735. const pollingInitState = async () => {
  736. showDeviceResettingModal.value = true // 显示 LoadingModal
  737. const res = await getInitState()
  738. if (res.ecode === 'SUC') {
  739. if (res.data.isBusy) {
  740. setTimeout(async () => await pollingInitState(), 500)
  741. } else {
  742. showDeviceResettingModal.value = false
  743. if (res.data.passed) {
  744. console.log('初始化成功')
  745. sessionStorage.setItem('deviceResetFinished', 'true')
  746. showAlreadyModal.value = true
  747. } else {
  748. const infos = res.data.promopt.detailInfos
  749. failMessage.value = {
  750. title: '检测失败',
  751. message:
  752. infos && infos.length > 0
  753. ? infos.map((d: any) => d.name).join('\n')
  754. : res.data.promopt.info,
  755. }
  756. showFailModal.value = true // 显示失败弹窗
  757. }
  758. }
  759. } else {
  760. showDeviceResettingModal.value = false
  761. }
  762. }
  763. const handleAlreadyConfirm = () => {
  764. console.log('用户确认操作')
  765. showAlreadyModal.value = false
  766. showFailModal.value = false
  767. }
  768. onMounted(() => {
  769. checkInit()
  770. })
  771. // 开始检测
  772. const checkInit = () => {
  773. const hasExecutedReset = sessionStorage.getItem('deviceResetFinished')
  774. if (!hasExecutedReset || hasExecutedReset === 'false') {
  775. showModal.value = true
  776. }
  777. }
  778. // 更新蓝线的宽度和左偏移量
  779. const updateLinePosition = (element: any) => {
  780. lineWidth.value = element.offsetWidth // 当前选中标签的宽度
  781. lineLeft.value = element.offsetLeft // 当前选中标签的左偏移量
  782. }
  783. // 处理标签点击事件
  784. const handleTabClick = (event: any) => {
  785. const tabElement = event.target.closest('.nav-tab')
  786. if (tabElement) {
  787. const tab = tabElement.dataset.tab
  788. selectedTab.value = tab
  789. sessionStorage.setItem('selectedTab', tab) // 将选中标签存储到 localStorage
  790. updateLinePosition(tabElement) // 更新蓝线的位置和宽度
  791. }
  792. }
  793. // 页面加载时默认选中"常规"标签,并设置蓝线的位置
  794. onMounted(() => {
  795. const defaultTab = document.querySelector(
  796. `.nav-tab[data-tab="${selectedTab.value}"]`,
  797. )
  798. if (defaultTab) {
  799. updateLinePosition(defaultTab)
  800. }
  801. })
  802. watch(
  803. () => deviceStore.contextState,
  804. (newVal) => {
  805. if (!newVal.loginFlag) {
  806. router.push({
  807. path: '/login',
  808. })
  809. sessionStorage.setItem('token', '')
  810. }
  811. },
  812. )
  813. </script>
  814. <style scoped lang="less">
  815. #index-container {
  816. margin: 0;
  817. padding: 0;
  818. width: 100vw;
  819. height: 100vh;
  820. background-color: #fff;
  821. position: relative;
  822. box-sizing: border-box;
  823. display: flex;
  824. flex-direction: column;
  825. .fade-slide-enter-active,
  826. .fade-slide-leave-active {
  827. transition:
  828. opacity 0.5s,
  829. transform 0.5s;
  830. /* 控制透明度和滑动的过渡时间 */
  831. }
  832. .fade-slide-enter-from {
  833. opacity: 0;
  834. transform: translateX(20px);
  835. /* 从右侧滑入 */
  836. }
  837. .fade-slide-leave-to {
  838. opacity: 0;
  839. transform: translateX(-20px);
  840. /* 向左滑出 */
  841. }
  842. .nav-bar {
  843. width: 100%;
  844. height: 60px;
  845. margin-bottom: 2px;
  846. display: flex;
  847. justify-content: space-between;
  848. align-items: center;
  849. .logo {
  850. width: 100px;
  851. height: 75px;
  852. padding-left: 10px;
  853. }
  854. .nav-tabs {
  855. width: 60%;
  856. font-size: 24px;
  857. display: flex;
  858. justify-content: space-around;
  859. font-size: 40px;
  860. .nav-tab {
  861. cursor: pointer;
  862. padding-bottom: 10px;
  863. color: black;
  864. transition: color 0.3s;
  865. position: relative;
  866. }
  867. .nav-tab.active {
  868. color: #478ffe;
  869. }
  870. }
  871. .test-control {
  872. display: flex;
  873. justify-content: space-evenly;
  874. padding-right: 10px;
  875. .start-test {
  876. width: 200px;
  877. height: 48px;
  878. background-color: #73bc54;
  879. font-size: 20px;
  880. }
  881. .pause-test,
  882. .stop-test,
  883. .continue-test {
  884. width: 100px;
  885. height: 48px;
  886. font-size: 20px;
  887. }
  888. }
  889. }
  890. .interval {
  891. width: 100%;
  892. height: 5px;
  893. background-color: #f5f5f5;
  894. position: relative;
  895. .blue-line {
  896. position: absolute;
  897. bottom: 0;
  898. height: 5px;
  899. background-color: #478ffe;
  900. transition: all 0.3s ease;
  901. }
  902. }
  903. .footer-info {
  904. width: 100%;
  905. height: 50px;
  906. display: flex;
  907. justify-content: end;
  908. align-items: center;
  909. position: relative;
  910. bottom: 0;
  911. margin-top: auto;
  912. > *:first-child {
  913. margin-right: auto;
  914. }
  915. .user-logo,
  916. .time-logo {
  917. width: 36px;
  918. height: 36px;
  919. margin-right: 8px;
  920. }
  921. .user-name {
  922. white-space: nowrap;
  923. overflow: hidden;
  924. text-overflow: ellipsis;
  925. }
  926. .user-card,
  927. .time-card {
  928. display: flex;
  929. align-items: center;
  930. font-size: 24px;
  931. }
  932. .equipment-status {
  933. width: 440px;
  934. height: 44px;
  935. line-height: $height;
  936. text-align: center;
  937. border-radius: 10px;
  938. margin-right: 20px;
  939. .status-text {
  940. border-radius: 8px;
  941. font-size: 22px;
  942. white-space: nowrap;
  943. overflow: hidden;
  944. text-overflow: ellipsis;
  945. &.Info {
  946. color: #333;
  947. background-color: #f5f5f5;
  948. }
  949. &.Warn {
  950. color: #fa9d3b;
  951. background-color: rgb(239, 230, 220);
  952. }
  953. &.Error {
  954. color: #d81212;
  955. background-color: rgb(236, 216, 216);
  956. }
  957. }
  958. }
  959. .logout {
  960. width: 70px;
  961. text-align: center;
  962. font-size: 20px;
  963. margin: auto;
  964. }
  965. }
  966. .main-content {
  967. flex: 1;
  968. min-height: 0; // 添加:防止溢出
  969. }
  970. }
  971. .msg-container {
  972. max-height: 850px;
  973. padding: 4px;
  974. overflow-y: auto;
  975. .msg-item {
  976. padding: 0 10px;
  977. height: 45px;
  978. line-height: $height;
  979. font-size: 22px;
  980. display: flex;
  981. gap: 10px;
  982. &:nth-child(even) {
  983. background-color: rgb(250, 250, 250);
  984. }
  985. &:nth-child(odd) {
  986. background-color: #f2f2f2;
  987. }
  988. &.Warn {
  989. color: #fa9d3b;
  990. background-color: rgb(239, 230, 220);
  991. }
  992. &.Error {
  993. color: #d81212;
  994. background-color: rgb(236, 216, 216);
  995. }
  996. }
  997. }
  998. .event-report-dialog {
  999. font-size: 1.5rem;
  1000. .report-title {
  1001. font-size: 30px;
  1002. margin: 18px 0;
  1003. }
  1004. .report-desc {
  1005. font-size: 26px;
  1006. }
  1007. .report-detail {
  1008. font-size: 22px;
  1009. margin-bottom: 14px;
  1010. .report-form-item {
  1011. display: flex;
  1012. gap: 20px;
  1013. margin: 0 20px;
  1014. padding: 20px;
  1015. border-bottom: 1px solid lightgray;
  1016. }
  1017. .report-table-container {
  1018. }
  1019. .report-table-header {
  1020. // background-color: lightgray;
  1021. display: flex;
  1022. text-align: left;
  1023. font-size: 22px;
  1024. height: 45px;
  1025. line-height: $height;
  1026. padding: 0 20px;
  1027. > * {
  1028. flex: 1 1 33%;
  1029. font-weight: 600;
  1030. }
  1031. }
  1032. .report-table-row {
  1033. display: flex;
  1034. text-align: left;
  1035. height: 45px;
  1036. line-height: $height;
  1037. padding: 0 20px;
  1038. > * {
  1039. flex: 1 1 33%;
  1040. }
  1041. &:nth-child(even) {
  1042. background-color: rgb(250, 250, 250);
  1043. }
  1044. &:nth-child(odd) {
  1045. background-color: #f2f2f2;
  1046. }
  1047. }
  1048. }
  1049. .dialog-footer .el-button {
  1050. height: 50px;
  1051. width: 100px;
  1052. font-size: 1.5rem;
  1053. }
  1054. }
  1055. .pd-5{
  1056. padding: 0 5px;
  1057. font-size: 28px;
  1058. }
  1059. .loading-overlay {
  1060. position: fixed;
  1061. top: 0;
  1062. left: 0;
  1063. width: 100vw;
  1064. height: 100vh;
  1065. background: rgba(0, 0, 0, 0.7);
  1066. backdrop-filter: blur(4px);
  1067. display: flex;
  1068. justify-content: center;
  1069. align-items: center;
  1070. z-index: 9998;
  1071. }
  1072. .loading-content {
  1073. display: flex;
  1074. flex-direction: column;
  1075. align-items: center;
  1076. gap: 20px;
  1077. }
  1078. .loading-spinner {
  1079. width: 60px;
  1080. height: 60px;
  1081. border: 4px solid #f3f3f3;
  1082. border-top: 4px solid #4caf50;
  1083. border-radius: 50%;
  1084. animation: spin 1s linear infinite;
  1085. }
  1086. .loading-text {
  1087. color: white;
  1088. font-size: 24px;
  1089. font-weight: 500;
  1090. }
  1091. .loading-progress {
  1092. width: 300px;
  1093. height: 6px;
  1094. background: rgba(255, 255, 255, 0.2);
  1095. border-radius: 3px;
  1096. overflow: hidden;
  1097. }
  1098. </style>