消毒机设备
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.

614 lines
15 KiB

3 weeks ago
1 month ago
4 weeks ago
2 months ago
4 weeks ago
2 months ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 weeks ago
3 weeks ago
1 month ago
2 months ago
2 months ago
2 months ago
4 weeks ago
3 weeks ago
4 weeks ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 weeks ago
2 months ago
2 months ago
4 weeks ago
3 weeks ago
3 weeks ago
2 months ago
3 weeks ago
2 months ago
4 weeks ago
2 months ago
2 months ago
3 weeks ago
2 months ago
  1. <script setup lang="ts">
  2. import WifiConnSvg from 'assets/images/wifi-conn.svg'
  3. import WifiUnconnSvg from 'assets/images/wifi-unconn.svg'
  4. import ErrorEventsModal from 'components/system/ErrorEventsModal.vue'
  5. import NetReconnection from 'components/system/NetReconnection.vue'
  6. import { delToken } from 'libs/token'
  7. import { formatDateTime } from 'libs/utils'
  8. import { authRoutes } from 'router/routes'
  9. import { useDeviceStore } from 'stores/deviceStore'
  10. import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'
  11. import { useI18n } from 'vue-i18n'
  12. import { useRouter } from 'vue-router'
  13. import { getDeviceStatus } from '@/libs/deviceComm'
  14. import { FtMessageBox } from '@/libs/messageBox'
  15. import { useHomeStore } from '@/stores/homeStore'
  16. import { useLiquidStore } from '@/stores/liquidStore'
  17. import { useSealStore } from '@/stores/sealStore'
  18. import { useSystemStore } from '@/stores/systemStore'
  19. // const routes = generateRoutes()
  20. // const childrenRoutes = routes.find(r => r.path === '/')?.children || []
  21. const { locale } = useI18n()
  22. const router = useRouter()
  23. const liquidStore = useLiquidStore()
  24. const sealStore = useSealStore()
  25. const homeStore = useHomeStore()
  26. const deviceStore = useDeviceStore()
  27. const systemStore = useSystemStore()
  28. const languages = systemStore.languages
  29. const currentTime = ref(formatDateTime('YYYY-MM-DD HH:mm:ss'))
  30. const timeInterval = ref()
  31. const languageType = ref('zh-cn')
  32. const deviceInfo = ref<Device.DeviceInfo>(deviceStore.deviceInfo)
  33. const workStateName = ref<string>('空闲')
  34. const disinfectState = ref(homeStore.disinfectionState.state)
  35. const liquidAddState = ref(liquidStore.liquidAddWorkState)
  36. const liquidDrainState = ref(liquidStore.liquidDrainWorkState)
  37. const deviceState = ref(deviceStore.deviceState)
  38. const sealInfo = ref(sealStore.sealInfo)
  39. const websocketConnected = ref(systemStore.websocketConnected)
  40. const deviceType = __DEVICE_TYPE__
  41. const menuList = computed(() => {
  42. return authRoutes.filter((item) => {
  43. if (
  44. (deviceType === deviceStore.deviceTypeMap.LargeSpaceDM_B && (item.name === 'seal' || item.name === 'liquid'))
  45. || (deviceType === deviceStore.deviceTypeMap.LargeSpaceDM && item.name === 'seal')
  46. || (deviceType === deviceStore.deviceTypeMap.SmallSpaceDM && item.name === 'seal')
  47. || (deviceType === deviceStore.deviceTypeMap.DrawBarDM && item.name === 'liquid')
  48. ) {
  49. return false
  50. }
  51. return !(item.name === 'debug' && systemStore.systemUser?.name !== 'admin')
  52. })
  53. })
  54. onMounted(() => {
  55. runSystemTime(systemStore.systemTime)
  56. console.log(systemStore.systemUser)
  57. // 连续3次点击任意地方,全屏显示
  58. // onFullScreen()
  59. })
  60. // const touchStartTime = 0
  61. // const touchCount = 0
  62. // const onFullScreen = () => {
  63. // document.body.addEventListener('touchstart', (event) => {
  64. // const now = Date.now()
  65. // const timeDiff = now - touchStartTime
  66. // if (timeDiff < 300 && timeDiff > 0) {
  67. // touchCount++
  68. // if (touchCount === 3) {
  69. // event.preventDefault()
  70. // openFullscreen()
  71. // }
  72. // }
  73. // else {
  74. // touchCount = 1
  75. // }
  76. // touchStartTime = now
  77. // })
  78. // }
  79. const showDeviceStateName = () => {
  80. if (deviceState.value.state?.toLocaleLowerCase() !== 'idle') {
  81. if (disinfectState.value !== 'idle' && disinfectState.value !== 'finished') {
  82. workStateName.value = homeStore.disinfectionState.statedisplayName
  83. }
  84. else if (liquidAddState.value.workState !== 'idle') {
  85. workStateName.value = liquidAddState.value.workStateDisplay
  86. }
  87. else if (liquidDrainState.value.workState !== 'idle') {
  88. workStateName.value = liquidDrainState.value.workStateDisplay
  89. }
  90. else if (sealInfo.value.workState !== 'idle') {
  91. workStateName.value = sealInfo.value.workStateDisplay
  92. }
  93. }
  94. else {
  95. workStateName.value = '空闲'
  96. }
  97. }
  98. watchEffect(() => {
  99. deviceInfo.value = deviceStore.deviceInfo
  100. websocketConnected.value = systemStore.websocketConnected
  101. // 消毒状态
  102. disinfectState.value = homeStore.disinfectionState.state
  103. // 加液/排液状态
  104. liquidAddState.value = liquidStore.liquidStateData
  105. liquidDrainState.value = liquidStore.liquidDrainWorkState
  106. deviceState.value = deviceStore.deviceState
  107. // 密封测试状态
  108. sealInfo.value = sealStore.sealInfo
  109. showDeviceStateName()
  110. })
  111. watch(
  112. () => systemStore.systemTime,
  113. (newVal) => {
  114. if (timeInterval.value) {
  115. clearInterval(timeInterval.value)
  116. }
  117. runSystemTime(newVal)
  118. },
  119. { deep: true },
  120. )
  121. const runSystemTime = (time: number) => {
  122. if (time) {
  123. let sysTime = time
  124. timeInterval.value = setInterval(() => {
  125. sysTime = sysTime + 1000
  126. currentTime.value = formatDateTime('YYYY-MM-DD HH:mm:ss', sysTime)
  127. }, 1000)
  128. }
  129. }
  130. onUnmounted(() => {
  131. clearInterval(timeInterval.value)
  132. })
  133. // const showErrorModal = () => {
  134. // ErrorBox.alert('这是一条警告信息')
  135. // }
  136. const onLogout = () => {
  137. // 判断是否有正在执行的操作
  138. const hasEx = getDeviceStatus()
  139. if (hasEx) {
  140. // 有正在执行的操作,给出提示
  141. FtMessageBox.error('退出登录前请停止当前的操作')
  142. return
  143. }
  144. FtMessageBox.warning('请确认是否退出登录?').then(() => {
  145. localStorage.removeItem('user')
  146. delToken()
  147. useSystemStore().updateUser(null)
  148. router.push('/login')
  149. })
  150. }
  151. // 切换语言的方法
  152. const toggleLanguage = () => {
  153. locale.value = locale.value === 'zh' ? 'en' : 'zh'
  154. localStorage.setItem('locale', locale.value) // 保存到本地存储
  155. }
  156. const statusMap = {
  157. info: {
  158. type: 'info',
  159. name: '设备弹窗信息',
  160. },
  161. success: {
  162. type: 'success',
  163. name: '设备执行信息',
  164. },
  165. check: {
  166. type: 'danger',
  167. name: '设备错误信息',
  168. },
  169. warn: {
  170. type: 'warning',
  171. name: '设备告警信息',
  172. },
  173. }
  174. </script>
  175. <template>
  176. <el-container class="main">
  177. <el-header class="header">
  178. <div class="logo">
  179. <img src="../assets/images/logo.svg" alt="">
  180. </div>
  181. <div class="header-right">
  182. <div class="header-menu">
  183. <div class="aside">
  184. <el-tag
  185. v-for="item in menuList.filter(item => item.meta!.isDefault)"
  186. :key="item.path"
  187. class="menu-tag"
  188. :class="{ 'aside-item-active': router.currentRoute.value.path.includes(item.path) }"
  189. @click="router.push(item.path)"
  190. >
  191. <template #default>
  192. <div class="menu-tags">
  193. <img
  194. class="swing-icon"
  195. :src="
  196. (router.currentRoute.value.path.includes(item.path)
  197. ? item.meta!.activeIcon
  198. : item.meta!.icon) as string
  199. "
  200. alt=""
  201. >
  202. <span class="text" :class="{ 'active-text': router.currentRoute.value.path.includes(item.path) }">{{
  203. item.meta!.title
  204. }}</span>
  205. </div>
  206. </template>
  207. </el-tag>
  208. </div>
  209. </div>
  210. <div class="user">
  211. <span v-if="deviceType === deviceStore.deviceTypeMap.LargeSpaceDM_B">
  212. <img v-if="websocketConnected" width="20" :src="WifiConnSvg" alt="">
  213. <img v-else :src="WifiUnconnSvg" width="20" alt="">
  214. </span>
  215. <el-select v-model="languageType" class="select-language" :disabled="false" @change="toggleLanguage">
  216. <el-option
  217. v-for="language in languages"
  218. :key="language.value"
  219. style="height: 2rem; font-size: 1rem"
  220. :value="language.value"
  221. :label="language.name"
  222. >
  223. {{ language.name }}
  224. </el-option>
  225. </el-select>
  226. <bt-button type="primary" button-text="注销" @click="onLogout" />
  227. </div>
  228. </div>
  229. </el-header>
  230. <el-container class="container">
  231. <el-main>
  232. <router-view v-slot="{ Component }" class="content">
  233. <transition name="el-fade-in-linear">
  234. <keep-alive include="seal">
  235. <component :is="Component" />
  236. </keep-alive>
  237. </transition>
  238. </router-view>
  239. </el-main>
  240. </el-container>
  241. <el-footer class="footer">
  242. <el-row>
  243. <el-col :span="6">
  244. <div class="ip-info">
  245. IP : {{ deviceInfo.ip }}
  246. </div>
  247. </el-col>
  248. <el-col :span="12" class="warning-border">
  249. <div class="footer-left">
  250. <img src="../assets/images/run.svg" alt="" style="padding-right: 5px">
  251. <span v-if="!systemStore.systemLogList.length" class="text">设备运行状态</span>
  252. <el-popover v-else width="auto" trigger="click" placement="top">
  253. <template #reference>
  254. <el-tag
  255. style="width: 100%"
  256. :type="statusMap[systemStore.systemLogList[0]?.status as keyof typeof statusMap].type"
  257. >
  258. {{ statusMap[systemStore.systemLogList[0]?.status as keyof typeof statusMap].name }}:
  259. {{ systemStore.systemLogList[0]?.name }}
  260. {{ systemStore.systemLogList[0]?.time }}
  261. </el-tag>
  262. </template>
  263. <template #default>
  264. <div class="log-box">
  265. <el-tag
  266. v-for="(item, key) in systemStore.systemLogList"
  267. :key
  268. :type="statusMap[item?.status as keyof typeof statusMap].type"
  269. >
  270. <div style="display: flex; justify-content: space-between; width: 100%">
  271. <span>
  272. <span>{{ statusMap[item.status as keyof typeof statusMap].name }}: </span>
  273. <span>{{ item.name }}</span>
  274. </span>
  275. <span>{{ item.time }}</span>
  276. </div>
  277. </el-tag>
  278. </div>
  279. </template>
  280. </el-popover>
  281. </div>
  282. </el-col>
  283. <el-col :span="6">
  284. <div class="time">
  285. {{ currentTime }}
  286. </div>
  287. </el-col>
  288. </el-row>
  289. </el-footer>
  290. <NetReconnection />
  291. <ErrorEventsModal />
  292. </el-container>
  293. </template>
  294. <style scoped lang="scss">
  295. .main {
  296. box-sizing: border-box;
  297. height: 100vh;
  298. background: #fafafa;
  299. .header {
  300. height: 50px;
  301. width: 100%;
  302. display: flex;
  303. align-items: center;
  304. padding: 10px 15px;
  305. }
  306. .footer {
  307. height: 50px;
  308. width: 100%;
  309. display: flex;
  310. align-items: center;
  311. padding: 10px 15px;
  312. position: sticky;
  313. }
  314. .header {
  315. color: #393f46;
  316. box-shadow: 0px 1px 5px 0px rgba(9, 39, 62, 0.15);
  317. .logo {
  318. height: 22px;
  319. width: 100px;
  320. display: flex;
  321. align-items: center;
  322. .title {
  323. margin: 0 10px;
  324. color: #8799ab;
  325. font-weight: 600;
  326. }
  327. img {
  328. height: 100%;
  329. }
  330. .expand-icon {
  331. height: 15px;
  332. transition: all 0.3s;
  333. }
  334. .fold-icon {
  335. height: 15px;
  336. transform: rotate(90deg);
  337. transition: all 0.3s;
  338. }
  339. }
  340. .header-right {
  341. display: flex;
  342. align-items: center;
  343. height: 100%;
  344. .header-menu {
  345. width: 68vw;
  346. }
  347. .wifi-icon {
  348. width: 40px;
  349. height: 100%;
  350. background: #fff;
  351. border-radius: 5px;
  352. display: flex;
  353. align-items: center;
  354. justify-content: center;
  355. img {
  356. height: 50%;
  357. }
  358. }
  359. }
  360. .user {
  361. width: 20vw;
  362. text-align: right;
  363. right: 5px;
  364. display: flex;
  365. align-items: center;
  366. gap: 25px;
  367. padding-left: 10px;
  368. .select-language {
  369. width: 100px;
  370. border-radius: 5px;
  371. margin-right: 5px;
  372. }
  373. .user-logout {
  374. margin-left: auto;
  375. }
  376. }
  377. }
  378. .container {
  379. height: calc(100% - 100px);
  380. background: #fff;
  381. }
  382. }
  383. .log-box {
  384. width: 500px;
  385. height: 400px;
  386. overflow: auto;
  387. :deep(.el-tag) {
  388. margin: 5px 0;
  389. width: 100%;
  390. .el-tag__content {
  391. width: 100%;
  392. }
  393. }
  394. }
  395. .aside {
  396. overflow: hidden;
  397. padding-left: 10px;
  398. display: flex;
  399. align-items: center;
  400. .menu-tag {
  401. height: 30px;
  402. border: 0;
  403. width: 10rem;
  404. display: flex;
  405. gap: 5px;
  406. font-size: 1.6vw;
  407. background: rgba(0, 0, 0, 0);
  408. transition: background-color 0.5s;
  409. }
  410. .menu-tags {
  411. display: flex;
  412. align-items: center;
  413. .text {
  414. padding-left: 10px;
  415. color: #191919;
  416. }
  417. .active-text {
  418. color: #ffffff;
  419. }
  420. }
  421. .aside-item {
  422. height: 50px;
  423. border-radius: 10px;
  424. margin: 10px 0;
  425. padding: 0 10px;
  426. display: flex;
  427. align-items: center;
  428. overflow: hidden;
  429. justify-content: center;
  430. min-width: 6rem;
  431. img {
  432. margin-right: 10px;
  433. }
  434. }
  435. .aside-item-active {
  436. background: #1989fa;
  437. display: flex;
  438. justify-content: center;
  439. }
  440. }
  441. .aside-off {
  442. width: 70px;
  443. //transition: all 0.1s ease;
  444. .aside-item {
  445. .text {
  446. opacity: 0;
  447. }
  448. }
  449. .aside-item-active {
  450. background: rgba(0, 0, 0, 0);
  451. color: #fff;
  452. }
  453. }
  454. .user-dropdown-item {
  455. display: flex;
  456. align-items: center;
  457. height: 100%;
  458. color: #393f46;
  459. font-weight: bold;
  460. img {
  461. height: 30px;
  462. margin-right: 10px;
  463. }
  464. }
  465. .el-main {
  466. padding: 0 1px;
  467. height: 100%;
  468. position: relative;
  469. }
  470. .content {
  471. width: 100%;
  472. height: $main-container-height;
  473. padding: 10px;
  474. }
  475. .footer-expand {
  476. padding: 10px 15px 10px 85px !important;
  477. }
  478. .main .footer {
  479. padding: 10px;
  480. .el-row {
  481. width: 100%;
  482. height: 100%;
  483. .el-col {
  484. height: 100%;
  485. }
  486. }
  487. .footer-left,
  488. .footer-right {
  489. width: 100%;
  490. height: 100%;
  491. background: #fff;
  492. border-radius: 5px;
  493. display: flex;
  494. align-items: center;
  495. padding: 0 20px;
  496. }
  497. .footer-left {
  498. border-right: 5px solid #f6f6f6;
  499. align-items: center;
  500. justify-content: center;
  501. img {
  502. height: 60%;
  503. }
  504. .text {
  505. color: #1c1c1c;
  506. margin-left: 10px;
  507. font-size: 14px;
  508. }
  509. }
  510. .footer-right {
  511. border-left: 10px solid #f6f6f6;
  512. .status {
  513. width: 15px;
  514. height: 15px;
  515. border-radius: 50%;
  516. background: #4ee993;
  517. }
  518. .text {
  519. color: #1c1c1c;
  520. margin-left: 10px;
  521. font-size: 14px;
  522. }
  523. }
  524. .alarm-info {
  525. font-size: 1.5rem;
  526. width: 53vw;
  527. //padding-left: 1.3vw;
  528. background: #f5f5f5;
  529. height: 5vh;
  530. display: flex;
  531. align-items: center;
  532. justify-content: center;
  533. gap: 5px;
  534. .alarm-workState {
  535. margin-left: 5px;
  536. }
  537. }
  538. .ip-info {
  539. font-size: 1.3rem;
  540. margin-left: 2vw;
  541. text-align: left;
  542. }
  543. .time {
  544. font-size: 1.3rem;
  545. margin-right: 2vw;
  546. text-align: right;
  547. }
  548. }
  549. .aside-item {
  550. .swing-icon {
  551. // animation: swing 1s ease-in-out;
  552. width: 1.25rem;
  553. }
  554. }
  555. .logout {
  556. display: flex;
  557. img {
  558. width: 15px;
  559. margin-right: 10px;
  560. }
  561. }
  562. @keyframes swing {
  563. 0% {
  564. transform: rotate(0deg);
  565. }
  566. 25% {
  567. transform: rotate(-30deg);
  568. }
  569. 50% {
  570. transform: rotate(30deg);
  571. }
  572. 75% {
  573. transform: rotate(-15deg);
  574. }
  575. 100% {
  576. transform: rotate(0deg);
  577. }
  578. }
  579. @keyframes border-pulse {
  580. 0% {
  581. box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.5);
  582. }
  583. 100% {
  584. box-shadow: 0 0 0 5px rgba(255, 0, 0, 0);
  585. }
  586. }
  587. .warning-border {
  588. position: relative;
  589. border: 1px solid #ff4d4d;
  590. border-radius: 3px;
  591. animation: border-pulse 1.5s infinite;
  592. }
  593. </style>