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

582 lines
14 KiB

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