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.

503 lines
15 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
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
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="index-container">
  3. <el-header class="nav-bar">
  4. <div class="logo">
  5. <img src="@/assets/Index/logo.svg" alt="" width="150px">
  6. </div>
  7. <div class="nav-tabs" @click="handleTabClick">
  8. <!-- 使用 router-link 进行路由跳转 -->
  9. <router-link to="/index/regular" class="nav-tab" :class="{ active: selectedTab === '常规' }"
  10. data-tab="常规">常规</router-link>
  11. <router-link to="/index/history" class="nav-tab" :class="{ active: selectedTab === '历史' }"
  12. data-tab="历史">历史</router-link>
  13. <router-link to="/index/setting" class="nav-tab" :class="{ active: selectedTab === '设置' }"
  14. data-tab="设置">设置</router-link>
  15. </div>
  16. <div v-if="!isTesting" class="test-control">
  17. <!-- 显示开始测试按钮 -->
  18. <el-button type="primary" class="start-test" @click="startTest">开始测试</el-button>
  19. </div>
  20. <div v-else class="test-control">
  21. <!-- 显示暂停和停止按钮 -->
  22. <el-button type="warning" class="pause-test" @click="pauseTest" v-if="!isPaused">暂停</el-button>
  23. <el-button type="success" class="continue-test" @click="continueTest" v-else>继续</el-button>
  24. <el-button type="danger" class="stop-test" @click="stopTest">停止</el-button>
  25. </div>
  26. </el-header>
  27. <!-- 间隔线 -->
  28. <div class="interval">
  29. <div class="blue-line" :style="{ width: `${lineWidth}px`, left: `${lineLeft}px` }"></div>
  30. </div>
  31. <!-- 主内容区域 -->
  32. <main class="main-content">
  33. <router-view v-slot="{ Component }">
  34. <keep-alive>
  35. <component :is="Component" />
  36. </keep-alive>
  37. </router-view>
  38. </main>
  39. <!-- 底部操作信息 -->
  40. <el-footer class="footer-info">
  41. <div class="user-card">
  42. <img class="user-logo" src="@/assets/Index/user.svg"></img>
  43. <div class="user-name">操作人:{{ username || "未登录" }}</div>
  44. </div>
  45. <div class="equipment-status">
  46. <div class="status-text">系统:{{ EventText }}</div>
  47. </div>
  48. <div class="time-card">
  49. <img class="time-logo" src="@/assets/Index/clock.svg"></img>
  50. <div class="time-text">
  51. <Time></Time>
  52. </div>
  53. </div>
  54. </el-footer>
  55. </div>
  56. <InitWarn v-if="showModal" :visible="showModal" title="确认操作" message="确认试管架中的试管槽是否取下,避免自检/自动复位时对设备产生损坏!"
  57. icon="/src/assets/Warn.svg" cancelText="返回" confirmText="确认取下/开始复位" @close="showModal = false"
  58. @confirm="handleConfirm" />
  59. <!-- 通用加载弹窗组件 -->
  60. <LoadingModal v-if="showLoadingModal" :visible="showLoadingModal" title="正在自检/自动复位中" message="请不要有任何手动操作!"
  61. cancelText="返回" confirmText="确认取下/开始复位" disableButtons @close="showLoadingModal = false" />
  62. <!-- 自动自检已完成 -->
  63. <InitWarn v-if="showAlreadyModal" :visible="showAlreadyModal" title="确认操作" message="自检/自动复位已完成!"
  64. icon="/src/assets/OK.svg" cancelText="返回" confirmText="确认" @close="showAlreadyModal = false"
  65. @confirm="handleAlreadyConfirm" />
  66. <!-- 自动自检失败 -->
  67. <InitWarn v-if="showFailModal" :visible="showFailModal" title="检测失败" :message="failMessage"
  68. icon="/src/assets/Warn.svg" cancelText="返回" confirmText="重试" @close="showFailModal = false"
  69. @confirm="checkIfResetCompleted" />
  70. <InitWarn v-if="idCardInserted" :visible="idCardInserted" title="检测到id卡插入" message="是否保存id卡信息" cancelText="返回"
  71. icon="/src/assets/update-pin-icon.svg" confirmText="确认保存" @close="idCardInserted = false" @confirm="saveIdInfo" />
  72. <InitWarn v-if="showErrorModal" :visible="showErrorModal" title="错误提示" :message="ErrorMessage"
  73. icon="/src/assets/Warn.svg" cancelText="返回" confirmText="确认" @close="confirmError" @confirm="confirmError" />
  74. <InitWarn v-if="showWarnModal" :visible="showWarnModal" title="注意" :message="WarnMessage" cancelText="返回"
  75. icon="/src/assets/update-pin-icon.svg" confirmText="确认" @close="confirmWarn" @confirm="confirmWarn" />
  76. </template>
  77. <script setup lang="ts">
  78. import { ref, onMounted, onBeforeUnmount } from 'vue';
  79. import { Time, InitWarn, LoadingModal } from './components/Consumables';
  80. import { getCheckList, startWork, pauseWork, continueWork, stopWork, getInitState, initDevice, saveMountedCardInfo, openBuzzer, closeBuzzer } from '../../services/index';
  81. import { CheckItem, User } from '../../types/Index';
  82. import { useConsumablesStore } from '../../store';
  83. import { createWebSocket } from '../../websocket/socket';
  84. import type { AppEventMessage } from '../../websocket/socket';
  85. import { getServerInfo } from '../../utils/getServerInfo';
  86. const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规');
  87. const lineWidth = ref(0);
  88. const lineLeft = ref(0);
  89. const showModal = ref(false);
  90. const showLoadingModal = ref(false);
  91. const showAlreadyModal = ref(false);
  92. const showFailModal = ref(false);
  93. const checkData = ref<CheckItem[]>([]);
  94. const failItems = ref<CheckItem[]>([]);
  95. const consumableStore = useConsumablesStore();
  96. // 新增的变量
  97. const user = ref<User>(JSON.parse(sessionStorage.getItem('token') || '{}') as unknown as User)
  98. const isTesting = ref(false); // 用于控制按钮的显示
  99. const isPaused = ref(false); // 用于控制是否暂停测试
  100. const username = ref<string>(user.value.account)
  101. const failMessage = ref(''); // 存储动态生成的错误信息
  102. const idCardInserted = ref(false) // id卡插入状态
  103. //事件状态
  104. const EventText = ref<string>('闲置...')
  105. const showWarnModal = ref(false)
  106. const ErrorMessage = ref<string>('')
  107. const showErrorModal = ref(false)
  108. const WarnMessage = ref<string>('')
  109. // WebSocket 实例
  110. const { wsUrl } = getServerInfo('/api/v1/app/ws/event')
  111. const ws = createWebSocket(wsUrl)
  112. // 处理应用事件消息
  113. const handleAppEvent = (data: AppEventMessage['data']) => {
  114. console.log('🚀 ~ handleAppEvent ~ data:', data)
  115. if (data.typeName === 'AppPromptEvents' && data.prompt) {
  116. data.prompt.forEach(async (item) => {
  117. if (item.type === 'Error') {
  118. showErrorModal.value = true;
  119. ErrorMessage.value = item.info;
  120. await openBuzzer();
  121. } else if (item.type === 'Warn') {
  122. showWarnModal.value = true;
  123. WarnMessage.value = item.info;
  124. }
  125. });
  126. }
  127. else if (data.typeName === 'AppIDCardMountEvent') {
  128. consumableStore.isIdCardInserted = true;
  129. idCardInserted.value = true;
  130. EventText.value = "id卡已插入"
  131. } else if (data.typeName === 'AppIDCardUnmountEvent') {
  132. consumableStore.isIdCardInserted = false;
  133. idCardInserted.value = false;
  134. EventText.value = "id卡已拔出"
  135. } else if (data.typeName === 'AppTubeholderSettingUpdateEvent') {
  136. EventText.value = "试管架配置更新"
  137. } else if (data.typeName === "DoA8kStepActionEvent") {
  138. EventText.value = data.actionStepName!
  139. } else {
  140. EventText.value = "闲置..."
  141. }
  142. };
  143. //确认错误事件
  144. const confirmError = async () => {
  145. showErrorModal.value = false
  146. //关闭蜂鸣器
  147. await closeBuzzer()
  148. EventText.value = "闲置..."
  149. }
  150. //确认警告事件
  151. const confirmWarn = async () => {
  152. showWarnModal.value = false
  153. return
  154. }
  155. //保存id卡信息
  156. const saveIdInfo = async () => {
  157. const res = await saveMountedCardInfo();
  158. if (res.success) {
  159. console.log("保存id卡信息成功")
  160. idCardInserted.value = false;
  161. }
  162. }
  163. onMounted(() => {
  164. ws.connect();
  165. ws.subscribe<AppEventMessage>('AppEvent', handleAppEvent);
  166. });
  167. onBeforeUnmount(() => {
  168. ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent);
  169. ws.disconnect();
  170. });
  171. // 动态生成错误信息
  172. const generateErrorMessages = (data: CheckItem[]): string[] => {
  173. return data
  174. .filter(item => !item.pass)
  175. .map(item => `错误:${item.typechinfo} 检测未通过,请检查设备状态。`);
  176. };
  177. // 开始测试
  178. const startTest = async () => {
  179. try {
  180. const res = await startWork();
  181. if (res.success) {
  182. isTesting.value = true;
  183. }
  184. } catch (error) {
  185. console.error('开始测试失败:', error);
  186. isTesting.value = false;
  187. }
  188. const isCheck = sessionStorage.getItem('testStarted');
  189. if (!isCheck) {
  190. isTesting.value = false;
  191. await checkIfResetCompleted()
  192. }
  193. };
  194. // 暂停测试
  195. const pauseTest = async () => {
  196. const res = await pauseWork();
  197. if (res.success) {
  198. isPaused.value = true;
  199. }
  200. // 在这里添加暂停测试的逻辑
  201. };
  202. // 停止测试时清除标记
  203. const stopTest = async () => {
  204. console.log('测试已停止');
  205. const res = await stopWork();
  206. if (res.success) {
  207. isTesting.value = false;
  208. }
  209. };
  210. //继续测试
  211. const continueTest = async () => {
  212. const res = await continueWork();
  213. if (res.success) {
  214. isPaused.value = false;
  215. console.log('继续测试');
  216. }
  217. };
  218. const handleConfirm = async () => {
  219. showModal.value = false; // 关闭初始弹窗
  220. await initDevice(); // 初始化设备
  221. await checkIfResetCompleted(); // 检测是否复位完成
  222. };
  223. // 检查是否复位完成
  224. const checkIfResetCompleted = async () => {
  225. if (showFailModal.value) {
  226. showFailModal.value = false;
  227. }
  228. showLoadingModal.value = true; // 显示 LoadingModal
  229. // 模拟 2 秒延时
  230. await new Promise(resolve => setTimeout(resolve, 2000));
  231. //通过获取初始化状态来判断是否初始化成功
  232. const initState = await getInitState();
  233. if (initState.ecode === "SUC") {
  234. //检测初始化是否成功
  235. if (initState.data.passed) {
  236. console.log("初始化成功")
  237. sessionStorage.setItem('testStarted', "true");
  238. showLoadingModal.value = false;
  239. showAlreadyModal.value = true;
  240. } else {
  241. console.log("初始化失败")
  242. await getCheckData(); // 获取检查数据找到错误项
  243. const failedItems = checkData.value.filter(item => !item.pass);
  244. if (failedItems.length > 0) {
  245. const errorMessages = generateErrorMessages(failedItems);
  246. console.log('生成的错误信息:', errorMessages);
  247. failItems.value = failedItems; // 更新失败的检查项
  248. failMessage.value = errorMessages.join('\n'); // 更新错误信息
  249. showLoadingModal.value = false; // 隐藏 LoadingModal
  250. showFailModal.value = true; // 显示失败弹窗
  251. } else {
  252. console.log("初始化失败,但是没有失败项")
  253. showLoadingModal.value = false; // 隐藏 LoadingModal
  254. showAlreadyModal.value = true; // 显示已完成弹窗
  255. sessionStorage.setItem('testStarted', "true");
  256. }
  257. }
  258. }
  259. };
  260. const isTestTubeSlotReady = ref(false); // 试管槽状态标记
  261. // 在获取检测数据后,判断试管槽状态
  262. const checkTestTubeSlotStatus = (data: CheckItem[]) => {
  263. const slotCheck = data.find(item => item.type === 'CHECK_TEST_TUBE_SLOT_READY');
  264. isTestTubeSlotReady.value = slotCheck?.pass ?? false;
  265. console.log('试管槽状态:', isTestTubeSlotReady.value ? '准备就绪' : '未准备好');
  266. };
  267. const handleAlreadyConfirm = () => {
  268. console.log('用户确认操作');
  269. showAlreadyModal.value = false;
  270. showFailModal.value = false;
  271. };
  272. onMounted(() => {
  273. openTest();
  274. });
  275. // 开始检测
  276. const openTest = () => {
  277. const hasExecuted = sessionStorage.getItem('testStarted');
  278. // 如果没有标记,执行 openTest 并设置标记
  279. if (!hasExecuted) {
  280. showModal.value = true;
  281. }
  282. };
  283. // 获取检测数据
  284. const getCheckData = async () => {
  285. try {
  286. const res = await getCheckList();
  287. if (res.data && res.data.ecode == "SUC") {
  288. checkData.value = res.data;
  289. console.log('设备检查的结果:', checkData.value);
  290. // 检查试管槽状态
  291. checkTestTubeSlotStatus(checkData.value);
  292. } else {
  293. console.log("获取检测数据失败")
  294. showFailModal.value = true;
  295. failMessage.value = "获取检测数据失败,请检查设备状态。"
  296. }
  297. } catch (error) {
  298. console.error('获取检测数据失败:', error);
  299. showFailModal.value = true;
  300. failMessage.value = "获取检测数据失败,请检查设备状态。"
  301. }
  302. };
  303. // 更新蓝线的宽度和左偏移量
  304. const updateLinePosition = (element: any) => {
  305. lineWidth.value = element.offsetWidth; // 当前选中标签的宽度
  306. lineLeft.value = element.offsetLeft; // 当前选中标签的左偏移量
  307. };
  308. // 处理标签点击事件
  309. const handleTabClick = (event: any) => {
  310. const tabElement = event.target.closest('.nav-tab');
  311. if (tabElement) {
  312. const tab = tabElement.dataset.tab;
  313. selectedTab.value = tab;
  314. sessionStorage.setItem('selectedTab', tab); // 将选中标签存储到 localStorage
  315. updateLinePosition(tabElement); // 更新蓝线的位置和宽度
  316. }
  317. };
  318. // 页面加载时默认选中"常规"标签,并设置蓝线的位置
  319. onMounted(() => {
  320. const defaultTab = document.querySelector(`.nav-tab[data-tab="${selectedTab.value}"]`);
  321. if (defaultTab) {
  322. updateLinePosition(defaultTab);
  323. }
  324. });
  325. </script>
  326. <style scoped lang="less">
  327. #index-container {
  328. margin: 0;
  329. padding: 0;
  330. width: 100vw;
  331. height: 100vh;
  332. background-color: #fff;
  333. position: relative;
  334. box-sizing: border-box;
  335. display: flex;
  336. flex-direction: column;
  337. .fade-slide-enter-active,
  338. .fade-slide-leave-active {
  339. transition: opacity 0.5s, transform 0.5s;
  340. /* 控制透明度和滑动的过渡时间 */
  341. }
  342. .fade-slide-enter-from {
  343. opacity: 0;
  344. transform: translateX(20px);
  345. /* 从右侧滑入 */
  346. }
  347. .fade-slide-leave-to {
  348. opacity: 0;
  349. transform: translateX(-20px);
  350. /* 向左滑出 */
  351. }
  352. .nav-bar {
  353. width: 100%;
  354. height: 60px;
  355. margin-bottom: 2px;
  356. display: flex;
  357. justify-content: space-between;
  358. align-items: center;
  359. .logo {
  360. width: 100px;
  361. height: 75px;
  362. padding-left: 10px;
  363. }
  364. .nav-tabs {
  365. width: 60%;
  366. font-size: 24px;
  367. display: flex;
  368. justify-content: space-around;
  369. font-size: 40px;
  370. .nav-tab {
  371. cursor: pointer;
  372. padding-bottom: 10px;
  373. color: black;
  374. transition: color 0.3s;
  375. position: relative;
  376. }
  377. .nav-tab.active {
  378. color: #478ffe;
  379. }
  380. }
  381. .test-control {
  382. display: flex;
  383. justify-content: space-evenly;
  384. padding-right: 10px;
  385. .start-test {
  386. width: 200px;
  387. height: 40px;
  388. background-color: #73bc54;
  389. font-size: 20px;
  390. }
  391. .pause-test,
  392. .stop-test,
  393. .continue-test {
  394. width: 100px;
  395. height: 40px;
  396. font-size: 20px;
  397. }
  398. }
  399. }
  400. .interval {
  401. width: 100%;
  402. height: 5px;
  403. background-color: #f5f5f5;
  404. position: relative;
  405. .blue-line {
  406. position: absolute;
  407. bottom: 0;
  408. height: 5px;
  409. background-color: #478ffe;
  410. transition: all 0.3s ease;
  411. /* 添加平滑过渡效果 */
  412. }
  413. }
  414. .footer-info {
  415. width: 100%;
  416. height: 40px;
  417. display: flex;
  418. justify-content: space-between;
  419. align-items: center;
  420. position: relative;
  421. bottom: 0;
  422. margin-top: auto;
  423. .user-logo,
  424. .time-logo {
  425. width: 40px;
  426. height: 40px;
  427. }
  428. .user-card,
  429. .time-card {
  430. display: flex;
  431. align-items: center;
  432. font-size: 26px;
  433. }
  434. .equipment-status {
  435. background-color: #f5f5f5;
  436. width: 400px;
  437. height: 40px;
  438. line-height: 40px;
  439. text-align: center;
  440. border-radius: 10px;
  441. .status-text {
  442. font-size: 26px;
  443. }
  444. }
  445. }
  446. .main-content {
  447. flex: 1;
  448. min-height: 0; // 添加:防止溢出
  449. }
  450. }
  451. </style>