消毒机设备
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
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
4 weeks ago
2 months ago
2 months ago
4 weeks ago
2 months ago
4 weeks ago
4 weeks ago
3 weeks ago
2 months ago
3 weeks ago
2 months ago
4 weeks ago
2 months ago
4 weeks ago
2 months ago
4 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. router.push('/login')
  130. })
  131. }
  132. // 切换语言的方法
  133. const toggleLanguage = () => {
  134. locale.value = locale.value === 'zh' ? 'en' : 'zh'
  135. localStorage.setItem('locale', locale.value) // 保存到本地存储
  136. }
  137. const statusMap = {
  138. info: {
  139. type: 'info',
  140. name: '设备弹窗信息',
  141. },
  142. success: {
  143. type: 'success',
  144. name: '设备执行信息',
  145. },
  146. check: {
  147. type: 'danger',
  148. name: '设备错误信息',
  149. },
  150. warn: {
  151. type: 'warning',
  152. name: '设备告警信息',
  153. },
  154. }
  155. </script>
  156. <template>
  157. <el-container class="main">
  158. <el-header class="header">
  159. <div class="logo">
  160. <img src="../assets/images/logo.svg" alt="">
  161. </div>
  162. <div class="header-right">
  163. <div class="header-menu">
  164. <div class="aside">
  165. <el-tag
  166. v-for="item in authRoutes.filter(item => item.meta!.isDefault)"
  167. :key="item.path"
  168. class="menu-tag"
  169. :class="{ 'aside-item-active': router.currentRoute.value.path.includes(item.path) }"
  170. @click="router.push(item.path)"
  171. >
  172. <template #default>
  173. <div class="menu-tags">
  174. <img
  175. class="swing-icon"
  176. :src="
  177. (router.currentRoute.value.path.includes(item.path)
  178. ? item.meta!.activeIcon
  179. : item.meta!.icon) as string
  180. "
  181. alt=""
  182. >
  183. <span class="text" :class="{ 'active-text': router.currentRoute.value.path.includes(item.path) }">{{
  184. item.meta!.title
  185. }}</span>
  186. </div>
  187. </template>
  188. </el-tag>
  189. </div>
  190. </div>
  191. <div class="user">
  192. <span v-if="deviceStore.deviceInfo.deviceType === deviceStore.deviceTypeMap.LargeSpaceDM_B">
  193. <img v-if="websocketConnected" width="20" :src="WifiConnSvg" alt="">
  194. <img v-else :src="WifiUnconnSvg" width="20" alt="">
  195. </span>
  196. <el-select v-model="languageType" class="select-language" :disabled="false" @change="toggleLanguage">
  197. <el-option
  198. v-for="language in languages"
  199. :key="language.value"
  200. style="height: 2rem"
  201. :value="language.value"
  202. :label="language.name"
  203. >
  204. {{ language.name }}
  205. </el-option>
  206. </el-select>
  207. <bt-button type="primary" button-text="注销" @click="onLogout" />
  208. </div>
  209. </div>
  210. </el-header>
  211. <el-container class="container">
  212. <el-main>
  213. <router-view v-slot="{ Component }" class="content">
  214. <transition name="el-fade-in-linear">
  215. <keep-alive include="seal">
  216. <component :is="Component" />
  217. </keep-alive>
  218. </transition>
  219. </router-view>
  220. </el-main>
  221. </el-container>
  222. <el-footer class="footer">
  223. <el-row>
  224. <el-col :span="6">
  225. <div class="ip-info">
  226. IP : {{ deviceInfo.ip }}
  227. </div>
  228. </el-col>
  229. <el-col :span="12">
  230. <div class="footer-left">
  231. <img src="../assets/images/run.svg" alt="">
  232. <span v-if="!systemStore.systemLogList.length" class="text">设备运行状态</span>
  233. <el-popover v-else width="auto" trigger="click" placement="top">
  234. <template #reference>
  235. <el-tag
  236. style="width: 100%"
  237. :type="statusMap[systemStore.systemLogList[0]?.status as keyof typeof statusMap].type"
  238. >
  239. {{ statusMap[systemStore.systemLogList[0]?.status as keyof typeof statusMap].name }}:
  240. {{ systemStore.systemLogList[0]?.name }}
  241. {{ systemStore.systemLogList[0]?.time }}
  242. </el-tag>
  243. </template>
  244. <template #default>
  245. <div class="log-box">
  246. <el-tag
  247. v-for="(item, key) in systemStore.systemLogList"
  248. :key
  249. :type="statusMap[item?.status as keyof typeof statusMap].type"
  250. >
  251. <div style="display: flex; justify-content: space-between; width: 100%">
  252. <span>
  253. <span>{{ statusMap[item.status as keyof typeof statusMap].name }}: </span>
  254. <span>{{ item.name }}</span>
  255. </span>
  256. <span>{{ item.time }}</span>
  257. </div>
  258. </el-tag>
  259. </div>
  260. </template>
  261. </el-popover>
  262. </div>
  263. </el-col>
  264. <el-col :span="6">
  265. <div class="time">
  266. {{ currentTime }}
  267. </div>
  268. </el-col>
  269. </el-row>
  270. </el-footer>
  271. <NetReconnection />
  272. <ErrorEventsModal />
  273. </el-container>
  274. </template>
  275. <style scoped lang="scss">
  276. .main {
  277. box-sizing: border-box;
  278. height: 100%;
  279. background: #fafafa;
  280. .header {
  281. height: 50px;
  282. width: 100%;
  283. display: flex;
  284. align-items: center;
  285. padding: 10px 15px;
  286. }
  287. .footer {
  288. height: 50px;
  289. width: 100%;
  290. display: flex;
  291. align-items: center;
  292. padding: 10px 15px;
  293. position: sticky;
  294. }
  295. .header {
  296. color: #393f46;
  297. box-shadow: 0px 1px 5px 0px rgba(9, 39, 62, 0.15);
  298. .logo {
  299. height: 22px;
  300. width: 100px;
  301. display: flex;
  302. align-items: center;
  303. .title {
  304. margin: 0 10px;
  305. color: #8799ab;
  306. font-weight: 600;
  307. }
  308. img {
  309. height: 100%;
  310. }
  311. .expand-icon {
  312. height: 15px;
  313. transition: all 0.3s;
  314. }
  315. .fold-icon {
  316. height: 15px;
  317. transform: rotate(90deg);
  318. transition: all 0.3s;
  319. }
  320. }
  321. .header-right {
  322. display: flex;
  323. align-items: center;
  324. height: 100%;
  325. .header-menu {
  326. width: 68vw;
  327. }
  328. .wifi-icon {
  329. width: 40px;
  330. height: 100%;
  331. background: #fff;
  332. border-radius: 5px;
  333. display: flex;
  334. align-items: center;
  335. justify-content: center;
  336. img {
  337. height: 50%;
  338. }
  339. }
  340. }
  341. .user {
  342. width: 20vw;
  343. text-align: right;
  344. right: 5px;
  345. display: flex;
  346. align-items: center;
  347. gap: 25px;
  348. padding-left: 10px;
  349. .select-language {
  350. width: 100px;
  351. border-radius: 5px;
  352. margin-right: 5px;
  353. }
  354. .user-logout {
  355. margin-left: auto;
  356. }
  357. }
  358. }
  359. .container {
  360. height: calc(100% - 100px);
  361. background: #fff;
  362. }
  363. }
  364. .log-box {
  365. width: 500px;
  366. height: 400px;
  367. overflow: auto;
  368. :deep(.el-tag) {
  369. margin: 5px 0;
  370. width: 100%;
  371. .el-tag__content {
  372. width: 100%;
  373. }
  374. }
  375. }
  376. .aside {
  377. overflow: hidden;
  378. padding-left: 10px;
  379. display: flex;
  380. align-items: center;
  381. .menu-tag {
  382. height: 30px;
  383. border: 0;
  384. width: 10rem;
  385. display: flex;
  386. gap: 5px;
  387. font-size: 1.6vw;
  388. background: rgba(0, 0, 0, 0);
  389. transition: background-color 0.5s;
  390. }
  391. .menu-tags {
  392. display: flex;
  393. align-items: center;
  394. .text {
  395. padding-left: 10px;
  396. color: #191919;
  397. }
  398. .active-text {
  399. color: #ffffff;
  400. }
  401. }
  402. .aside-item {
  403. height: 50px;
  404. border-radius: 10px;
  405. margin: 10px 0;
  406. padding: 0 10px;
  407. display: flex;
  408. align-items: center;
  409. overflow: hidden;
  410. justify-content: center;
  411. min-width: 6rem;
  412. img {
  413. margin-right: 10px;
  414. }
  415. }
  416. .aside-item-active {
  417. background: #1989fa;
  418. display: flex;
  419. justify-content: center;
  420. }
  421. }
  422. .aside-off {
  423. width: 70px;
  424. //transition: all 0.1s ease;
  425. .aside-item {
  426. .text {
  427. opacity: 0;
  428. }
  429. }
  430. .aside-item-active {
  431. background: rgba(0, 0, 0, 0);
  432. color: #fff;
  433. }
  434. }
  435. .user-dropdown-item {
  436. display: flex;
  437. align-items: center;
  438. height: 100%;
  439. color: #393f46;
  440. font-weight: bold;
  441. img {
  442. height: 30px;
  443. margin-right: 10px;
  444. }
  445. }
  446. .el-main {
  447. padding: 0 1px;
  448. height: 100%;
  449. position: relative;
  450. }
  451. .content {
  452. width: 100%;
  453. height: $main-container-height;
  454. padding: 10px;
  455. }
  456. .footer-expand {
  457. padding: 10px 15px 10px 85px !important;
  458. }
  459. .main .footer {
  460. padding: 10px;
  461. .el-row {
  462. width: 100%;
  463. height: 100%;
  464. .el-col {
  465. height: 100%;
  466. }
  467. }
  468. .footer-left,
  469. .footer-right {
  470. width: 100%;
  471. height: 100%;
  472. background: #fff;
  473. border-radius: 5px;
  474. display: flex;
  475. align-items: center;
  476. padding: 0 20px;
  477. }
  478. .footer-left {
  479. border-right: 5px solid #f6f6f6;
  480. align-items: center;
  481. justify-content: center;
  482. img {
  483. height: 60%;
  484. }
  485. .text {
  486. color: #1c1c1c;
  487. margin-left: 10px;
  488. font-size: 14px;
  489. }
  490. }
  491. .footer-right {
  492. border-left: 10px solid #f6f6f6;
  493. .status {
  494. width: 15px;
  495. height: 15px;
  496. border-radius: 50%;
  497. background: #4ee993;
  498. }
  499. .text {
  500. color: #1c1c1c;
  501. margin-left: 10px;
  502. font-size: 14px;
  503. }
  504. }
  505. .ip-info {
  506. font-size: 1.3rem;
  507. width: 22vw;
  508. padding-left: 1.3vw;
  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. width: 22.5vw;
  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>