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.

1165 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
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. idCardMessage.value = `<div>
  453. <div>项目名称${projectInfo.projName}, 批次号${projectInfo.lotId}, 过期时间${formatDate(projectInfo.expiryDate)}</div>
  454. <div>id卡已插入是否确保</div>
  455. </div>`
  456. } else if (data.typeName === 'AppIDCardUnmountEvent') {
  457. consumableStore.setIdCardInserted(false)
  458. consumableStore.setIdCardInfo(null)
  459. idCardInserted.value = false
  460. }
  461. }
  462. //注销用户
  463. const router = useRouter()
  464. const onLogout = () => {
  465. logout().then(() => {
  466. router.push({
  467. path: '/login',
  468. })
  469. sessionStorage.setItem('token', '')
  470. })
  471. }
  472. const confirmFail = async () => {
  473. showFailModal.value = false
  474. await startInit()
  475. }
  476. //确认错误事件
  477. const confirmError = async () => {
  478. showErrorModal.value = false
  479. //关闭蜂鸣器
  480. await closeBuzzer()
  481. EventText.value = '闲置...'
  482. }
  483. //确认警告事件
  484. const confirmWarn = async () => {
  485. showWarnModal.value = false
  486. }
  487. //保存id卡信息
  488. const saveIdInfo = async () => {
  489. const res = await saveMountedCardInfo()
  490. if (res.success) {
  491. console.log('保存id卡信息成功')
  492. idCardInserted.value = false
  493. }
  494. }
  495. //初始化设备
  496. const onInitDevice = ()=> {
  497. eventBus.emit('initDevice')
  498. }
  499. //关机
  500. let isLoading = ref(false)
  501. const onCloseDevice = ()=> {
  502. // ElMessageBox.alert()
  503. //检查设备状态 设备运行时不可关机
  504. const deviceState = deviceStore.deviceState.workState;
  505. if(deviceState == 'WORKING'){
  506. showWarnModal.value = true;
  507. WarnMessage.value = '设备正在运行中,不能进行关机操作'
  508. return
  509. }
  510. confirmVisible.value = true
  511. confirmInfo.value = {
  512. title: '提示',
  513. message: '请确认是否进行关机操作?',
  514. cancelBtn: '取消',
  515. OkBtn: '确认',
  516. confirmCallback:()=>{
  517. //关机时的遮罩层
  518. isLoading.value = true;
  519. confirmVisible.value = false;
  520. //有时接口返回的太快,看不到loading状态,延迟1s,看一到loading状态。
  521. setTimeout(async ()=>{
  522. await shutdown()
  523. shutdownMessage.value = '设备已关机,请拔掉电源'
  524. },1000)
  525. },
  526. cancelCallback: ()=>{
  527. confirmVisible.value = false;
  528. }
  529. }
  530. }
  531. const showInitDeviceAlert = () => {
  532. showModal.value = true
  533. }
  534. const handleDeviceState = (data: DeviceWorkStateMessage['data']) => {
  535. deviceStore.setDeviceState(data)
  536. }
  537. const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => {
  538. deviceStore.setContextState(data)
  539. }
  540. const handleSensorState = (data: SensorStateMessage['data']) => {
  541. deviceStore.setSensorState(data)
  542. }
  543. const handleFooterState = (data: FooterMessageState['data']) => {
  544. deviceStore.setMessageState(data)
  545. }
  546. const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => {
  547. runningStore.setTubeHolderState(data)
  548. }
  549. const handleTubeHolderSettingMessage = (
  550. data: TubeHolderSettingMessage['data'],
  551. ) => {
  552. tubeRackStore.setTubeRacks(data)
  553. }
  554. const handleOptScanModuleStateMessage = (
  555. data: OptScanModuleStateMessage['data'],
  556. ) => {
  557. runningStore.setOptScanModuleState(data)
  558. }
  559. const handleIncubationPlateStateMessage = (
  560. data: IncubationPlateStateMessage['data'],
  561. ) => {
  562. runningStore.setSubTanks(data.subtanks)
  563. }
  564. const handleConsumablesState = (data: ConsumablesStateMessage['data']) => {
  565. consumableStore.setConsumablesData(data)
  566. }
  567. const handleEmergencyPosState = (data: EmergencyPosStateMessage['data']) => {
  568. emergencyStore.setInfo(data.tube)
  569. }
  570. const getProjectList = async () => {
  571. const res = await getProjectInfo()
  572. if (res.success) {
  573. settingTubeStore.setSupportedProjects(res.data)
  574. }
  575. }
  576. const getBloodTypeList = async () => {
  577. const res = await getBloodTypes()
  578. if (res.success) {
  579. settingTubeStore.setBloodTypes(res.data)
  580. }
  581. }
  582. onMounted(() => {
  583. eventBus.on('initDevice', showInitDeviceAlert)
  584. eventBus.on('socketClosed', handleSocketClose)
  585. wsEvent.subscribe<AppEventMessage>('AppEvent', handleAppEvent)
  586. wsEvent.connect()
  587. wsState.subscribe<ConsumablesStateMessage>(
  588. 'ConsumablesState',
  589. handleConsumablesState,
  590. )
  591. wsState.subscribe<DeviceWorkStateMessage>(
  592. 'DeviceWorkState',
  593. handleDeviceState,
  594. )
  595. wsState.subscribe<DeviceContextStateMessage>(
  596. 'DeviceContext',
  597. handleDeviceContextState,
  598. )
  599. wsState.subscribe<SensorStateMessage>('SensorState', handleSensorState)
  600. wsState.subscribe<FooterMessageState>('MessageBoxState', handleFooterState)
  601. wsState.subscribe<TubeHolderStateMessage>(
  602. 'TubeHolderState',
  603. handleTubeHolderStateMessage,
  604. )
  605. wsState.subscribe<TubeHolderSettingMessage>(
  606. 'TubeHolderSetting',
  607. handleTubeHolderSettingMessage,
  608. )
  609. wsState.subscribe<IncubationPlateStateMessage>(
  610. 'IncubationPlateState',
  611. handleIncubationPlateStateMessage,
  612. )
  613. wsState.subscribe<OptScanModuleStateMessage>(
  614. 'OptScanModuleState',
  615. handleOptScanModuleStateMessage,
  616. )
  617. wsState.subscribe<EmergencyPosStateMessage>(
  618. 'EmergencyPosState',
  619. handleEmergencyPosState,
  620. )
  621. wsState.connect()
  622. getProjectList()
  623. getBloodTypeList()
  624. })
  625. // onBeforeUnmount(() => {
  626. // eventBus.off('initDevice', showInitDeviceAlert)
  627. // ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent)
  628. // ws.disconnect()
  629. // })
  630. // 动态生成错误信息
  631. // const generateErrorMessages = (data: CheckItem[]): string[] => {
  632. // return data
  633. // .filter(item => !item.pass)
  634. // .map(item => `错误:${item.typechinfo} 检测未通过,请检查设备状态。`);
  635. // };
  636. const untilDeviceReady = async () => {
  637. deviceWaitingModelInfo.value = {
  638. title: '设备正在响应中',
  639. message: '请不要有任何手动操作!',
  640. }
  641. showDeviceWaitingModal.value = true
  642. const res = await getDeviceWorkState()
  643. if (res.ecode === 'SUC') {
  644. if (res.data.pending) {
  645. setTimeout(async () => await untilDeviceReady(), 250)
  646. } else {
  647. showDeviceWaitingModal.value = false
  648. }
  649. } else {
  650. showDeviceWaitingModal.value = false
  651. }
  652. }
  653. // 开始测试
  654. const startTest = async () => {
  655. const res = await getInitState()
  656. if (res.ecode === 'SUC' && !res.data.deviceInited) {
  657. eventBus.emit('initDevice')
  658. return
  659. }
  660. try {
  661. deviceWaitingModelInfo.value = {
  662. title: '设备正在响应中',
  663. message: '请不要有任何手动操作!',
  664. }
  665. showDeviceWaitingModal.value = true
  666. const res = await startWork()
  667. showDeviceWaitingModal.value = false
  668. if (res && res.success) {
  669. await untilDeviceReady()
  670. } else {
  671. res && res.data && res.data.info && eMessage.error(res.data.info)
  672. }
  673. } catch (error) {
  674. console.error('开始测试失败:', error)
  675. }
  676. }
  677. // 暂停测试
  678. const pauseTest = async () => {
  679. deviceWaitingModelInfo.value = {
  680. title: '设备正在响应中',
  681. message: '请不要有任何手动操作!',
  682. }
  683. showDeviceWaitingModal.value = true
  684. const res = await pauseWork()
  685. showDeviceWaitingModal.value = false
  686. if (res && res.success) {
  687. await untilDeviceReady()
  688. } else {
  689. res && res.data && res.data.info && eMessage.error(res.data.info)
  690. }
  691. }
  692. // 停止测试时清除标记
  693. const stopTest = async () => {
  694. deviceWaitingModelInfo.value = {
  695. title: '设备正在响应中',
  696. message: '请不要有任何手动操作!',
  697. }
  698. showDeviceWaitingModal.value = true
  699. const res = await stopWork()
  700. showDeviceWaitingModal.value = false
  701. if (res && res.success) {
  702. await untilDeviceReady()
  703. } else {
  704. res && res.data && res.data.info && eMessage.error(res.data.info)
  705. }
  706. }
  707. //继续测试
  708. const continueTest = async () => {
  709. deviceWaitingModelInfo.value = {
  710. title: '设备正在响应中',
  711. message: '请不要有任何手动操作!',
  712. }
  713. showDeviceWaitingModal.value = true
  714. const res = await continueWork()
  715. showDeviceWaitingModal.value = false
  716. if (res && res.success) {
  717. await untilDeviceReady()
  718. } else {
  719. res && res.data && res.data.info && eMessage.error(res.data.info)
  720. }
  721. }
  722. const handleConfirm = async () => {
  723. showModal.value = false // 关闭初始弹窗
  724. await startInit()
  725. }
  726. const startInit = async () => {
  727. await initDevice()
  728. await pollingInitState()
  729. }
  730. const pollingInitState = async () => {
  731. showDeviceResettingModal.value = true // 显示 LoadingModal
  732. const res = await getInitState()
  733. if (res.ecode === 'SUC') {
  734. if (res.data.isBusy) {
  735. setTimeout(async () => await pollingInitState(), 500)
  736. } else {
  737. showDeviceResettingModal.value = false
  738. if (res.data.passed) {
  739. console.log('初始化成功')
  740. sessionStorage.setItem('deviceResetFinished', 'true')
  741. showAlreadyModal.value = true
  742. } else {
  743. const infos = res.data.promopt.detailInfos
  744. failMessage.value = {
  745. title: '检测失败',
  746. message:
  747. infos && infos.length > 0
  748. ? infos.map((d: any) => d.name).join('\n')
  749. : res.data.promopt.info,
  750. }
  751. showFailModal.value = true // 显示失败弹窗
  752. }
  753. }
  754. } else {
  755. showDeviceResettingModal.value = false
  756. }
  757. }
  758. const handleAlreadyConfirm = () => {
  759. console.log('用户确认操作')
  760. showAlreadyModal.value = false
  761. showFailModal.value = false
  762. }
  763. onMounted(() => {
  764. checkInit()
  765. })
  766. // 开始检测
  767. const checkInit = () => {
  768. const hasExecutedReset = sessionStorage.getItem('deviceResetFinished')
  769. if (!hasExecutedReset || hasExecutedReset === 'false') {
  770. showModal.value = true
  771. }
  772. }
  773. // 更新蓝线的宽度和左偏移量
  774. const updateLinePosition = (element: any) => {
  775. lineWidth.value = element.offsetWidth // 当前选中标签的宽度
  776. lineLeft.value = element.offsetLeft // 当前选中标签的左偏移量
  777. }
  778. // 处理标签点击事件
  779. const handleTabClick = (event: any) => {
  780. const tabElement = event.target.closest('.nav-tab')
  781. if (tabElement) {
  782. const tab = tabElement.dataset.tab
  783. selectedTab.value = tab
  784. sessionStorage.setItem('selectedTab', tab) // 将选中标签存储到 localStorage
  785. updateLinePosition(tabElement) // 更新蓝线的位置和宽度
  786. }
  787. }
  788. // 页面加载时默认选中"常规"标签,并设置蓝线的位置
  789. onMounted(() => {
  790. const defaultTab = document.querySelector(
  791. `.nav-tab[data-tab="${selectedTab.value}"]`,
  792. )
  793. if (defaultTab) {
  794. updateLinePosition(defaultTab)
  795. }
  796. })
  797. watch(
  798. () => deviceStore.contextState,
  799. (newVal) => {
  800. if (!newVal.loginFlag) {
  801. router.push({
  802. path: '/login',
  803. })
  804. sessionStorage.setItem('token', '')
  805. }
  806. },
  807. )
  808. </script>
  809. <style scoped lang="less">
  810. #index-container {
  811. margin: 0;
  812. padding: 0;
  813. width: 100vw;
  814. height: 100vh;
  815. background-color: #fff;
  816. position: relative;
  817. box-sizing: border-box;
  818. display: flex;
  819. flex-direction: column;
  820. .fade-slide-enter-active,
  821. .fade-slide-leave-active {
  822. transition:
  823. opacity 0.5s,
  824. transform 0.5s;
  825. /* 控制透明度和滑动的过渡时间 */
  826. }
  827. .fade-slide-enter-from {
  828. opacity: 0;
  829. transform: translateX(20px);
  830. /* 从右侧滑入 */
  831. }
  832. .fade-slide-leave-to {
  833. opacity: 0;
  834. transform: translateX(-20px);
  835. /* 向左滑出 */
  836. }
  837. .nav-bar {
  838. width: 100%;
  839. height: 60px;
  840. margin-bottom: 2px;
  841. display: flex;
  842. justify-content: space-between;
  843. align-items: center;
  844. .logo {
  845. width: 100px;
  846. height: 75px;
  847. padding-left: 10px;
  848. }
  849. .nav-tabs {
  850. width: 60%;
  851. font-size: 24px;
  852. display: flex;
  853. justify-content: space-around;
  854. font-size: 40px;
  855. .nav-tab {
  856. cursor: pointer;
  857. padding-bottom: 10px;
  858. color: black;
  859. transition: color 0.3s;
  860. position: relative;
  861. }
  862. .nav-tab.active {
  863. color: #478ffe;
  864. }
  865. }
  866. .test-control {
  867. display: flex;
  868. justify-content: space-evenly;
  869. padding-right: 10px;
  870. .start-test {
  871. width: 200px;
  872. height: 48px;
  873. background-color: #73bc54;
  874. font-size: 20px;
  875. }
  876. .pause-test,
  877. .stop-test,
  878. .continue-test {
  879. width: 100px;
  880. height: 48px;
  881. font-size: 20px;
  882. }
  883. }
  884. }
  885. .interval {
  886. width: 100%;
  887. height: 5px;
  888. background-color: #f5f5f5;
  889. position: relative;
  890. .blue-line {
  891. position: absolute;
  892. bottom: 0;
  893. height: 5px;
  894. background-color: #478ffe;
  895. transition: all 0.3s ease;
  896. }
  897. }
  898. .footer-info {
  899. width: 100%;
  900. height: 50px;
  901. display: flex;
  902. justify-content: end;
  903. align-items: center;
  904. position: relative;
  905. bottom: 0;
  906. margin-top: auto;
  907. > *:first-child {
  908. margin-right: auto;
  909. }
  910. .user-logo,
  911. .time-logo {
  912. width: 36px;
  913. height: 36px;
  914. margin-right: 8px;
  915. }
  916. .user-name {
  917. white-space: nowrap;
  918. overflow: hidden;
  919. text-overflow: ellipsis;
  920. }
  921. .user-card,
  922. .time-card {
  923. display: flex;
  924. align-items: center;
  925. font-size: 24px;
  926. }
  927. .equipment-status {
  928. width: 440px;
  929. height: 44px;
  930. line-height: $height;
  931. text-align: center;
  932. border-radius: 10px;
  933. margin-right: 20px;
  934. .status-text {
  935. border-radius: 8px;
  936. font-size: 22px;
  937. white-space: nowrap;
  938. overflow: hidden;
  939. text-overflow: ellipsis;
  940. &.Info {
  941. color: #333;
  942. background-color: #f5f5f5;
  943. }
  944. &.Warn {
  945. color: #fa9d3b;
  946. background-color: rgb(239, 230, 220);
  947. }
  948. &.Error {
  949. color: #d81212;
  950. background-color: rgb(236, 216, 216);
  951. }
  952. }
  953. }
  954. .logout {
  955. width: 70px;
  956. text-align: center;
  957. font-size: 20px;
  958. margin: auto;
  959. }
  960. }
  961. .main-content {
  962. flex: 1;
  963. min-height: 0; // 添加:防止溢出
  964. }
  965. }
  966. .msg-container {
  967. max-height: 850px;
  968. padding: 4px;
  969. overflow-y: auto;
  970. .msg-item {
  971. padding: 0 10px;
  972. height: 45px;
  973. line-height: $height;
  974. font-size: 22px;
  975. display: flex;
  976. gap: 10px;
  977. &:nth-child(even) {
  978. background-color: rgb(250, 250, 250);
  979. }
  980. &:nth-child(odd) {
  981. background-color: #f2f2f2;
  982. }
  983. &.Warn {
  984. color: #fa9d3b;
  985. background-color: rgb(239, 230, 220);
  986. }
  987. &.Error {
  988. color: #d81212;
  989. background-color: rgb(236, 216, 216);
  990. }
  991. }
  992. }
  993. .event-report-dialog {
  994. font-size: 1.5rem;
  995. .report-title {
  996. font-size: 30px;
  997. margin: 18px 0;
  998. }
  999. .report-desc {
  1000. font-size: 26px;
  1001. }
  1002. .report-detail {
  1003. font-size: 22px;
  1004. margin-bottom: 14px;
  1005. .report-form-item {
  1006. display: flex;
  1007. gap: 20px;
  1008. margin: 0 20px;
  1009. padding: 20px;
  1010. border-bottom: 1px solid lightgray;
  1011. }
  1012. .report-table-container {
  1013. }
  1014. .report-table-header {
  1015. // background-color: lightgray;
  1016. display: flex;
  1017. text-align: left;
  1018. font-size: 22px;
  1019. height: 45px;
  1020. line-height: $height;
  1021. padding: 0 20px;
  1022. > * {
  1023. flex: 1 1 33%;
  1024. font-weight: 600;
  1025. }
  1026. }
  1027. .report-table-row {
  1028. display: flex;
  1029. text-align: left;
  1030. height: 45px;
  1031. line-height: $height;
  1032. padding: 0 20px;
  1033. > * {
  1034. flex: 1 1 33%;
  1035. }
  1036. &:nth-child(even) {
  1037. background-color: rgb(250, 250, 250);
  1038. }
  1039. &:nth-child(odd) {
  1040. background-color: #f2f2f2;
  1041. }
  1042. }
  1043. }
  1044. .dialog-footer .el-button {
  1045. height: 50px;
  1046. width: 100px;
  1047. font-size: 1.5rem;
  1048. }
  1049. }
  1050. .pd-5{
  1051. padding: 0 5px;
  1052. }
  1053. .loading-overlay {
  1054. position: fixed;
  1055. top: 0;
  1056. left: 0;
  1057. width: 100vw;
  1058. height: 100vh;
  1059. background: rgba(0, 0, 0, 0.7);
  1060. backdrop-filter: blur(4px);
  1061. display: flex;
  1062. justify-content: center;
  1063. align-items: center;
  1064. z-index: 9998;
  1065. }
  1066. .loading-content {
  1067. display: flex;
  1068. flex-direction: column;
  1069. align-items: center;
  1070. gap: 20px;
  1071. }
  1072. .loading-spinner {
  1073. width: 60px;
  1074. height: 60px;
  1075. border: 4px solid #f3f3f3;
  1076. border-top: 4px solid #4caf50;
  1077. border-radius: 50%;
  1078. animation: spin 1s linear infinite;
  1079. }
  1080. .loading-text {
  1081. color: white;
  1082. font-size: 24px;
  1083. font-weight: 500;
  1084. }
  1085. .loading-progress {
  1086. width: 300px;
  1087. height: 6px;
  1088. background: rgba(255, 255, 255, 0.2);
  1089. border-radius: 3px;
  1090. overflow: hidden;
  1091. }
  1092. </style>