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

662 lines
16 KiB

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