颜色滴定
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.

562 lines
15 KiB

3 weeks ago
3 weeks ago
  1. <script setup lang="ts">
  2. import { getContainerList } from 'apis/container'
  3. import { craftList } from 'apis/crafts'
  4. import { getSolsList } from 'apis/solution'
  5. import logoutIcon from 'assets/images/logout.svg'
  6. import CheckCraft from 'components/home/CheckCraft/index.vue'
  7. import Liquid from 'components/home/Liquid/index.vue'
  8. import Check from 'components/system/Check/index.vue'
  9. import EditDate from 'components/system/EditDate/index.vue'
  10. import Stop from 'components/system/Stop/index.vue'
  11. import { ElMessageBox } from 'element-plus'
  12. import { useActivateDebug } from 'hooks/useActivateDebug'
  13. import { useServerTime } from 'hooks/useServerTime'
  14. import { FtMessage } from 'libs/message'
  15. import { isClose, socket } from 'libs/socket'
  16. import { authRoutes } from 'router/routes'
  17. import { useDebugStore } from 'stores/debugStore'
  18. import { useSystemStore } from 'stores/systemStore'
  19. import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
  20. import { useRouter } from 'vue-router'
  21. const { handleLogoClick } = useActivateDebug()
  22. const { currentTime } = useServerTime()
  23. const systemStore = useSystemStore()
  24. const router = useRouter()
  25. onMounted(() => {
  26. socket.init(receiveMessage, 'alarm')
  27. socket.init(receiveMessage1, 'warn')
  28. })
  29. onUnmounted(() => {
  30. socket.unregisterCallback(receiveMessage, 'alarm')
  31. socket.unregisterCallback(receiveMessage, 'warn')
  32. })
  33. let flag = false
  34. const receiveMessage1 = (data: any) => {
  35. FtMessage.warning(data)
  36. }
  37. const receiveMessage = async (data: any) => {
  38. if (flag) {
  39. return
  40. }
  41. flag = true
  42. await ElMessageBox.confirm(
  43. data,
  44. '提示',
  45. {
  46. confirmButtonText: '确认',
  47. showClose: false,
  48. showCancelButton: false,
  49. closeOnClickModal: false,
  50. closeOnPressEscape: false,
  51. type: 'warning',
  52. },
  53. )
  54. flag = false
  55. }
  56. watch(() => currentTime.value, () => {
  57. systemStore.currentTime = currentTime.value
  58. })
  59. watch (() => isClose.value, async (newVal) => {
  60. if (newVal) {
  61. await checkCraft()
  62. }
  63. })
  64. watch(() => systemStore.systemStatus, () => {
  65. if (!systemStore.systemStatus.currentUser) {
  66. router.push('/login')
  67. }
  68. if (!systemStore.systemStatus.selfTest && systemStore.systemStatus.currentUser && systemStore.systemStatus.currentUser?.username !== 'test') {
  69. isCheck.value = true
  70. }
  71. })
  72. onMounted(async () => {
  73. socket.init(receiveMessage, 'alarm')
  74. console.log('systemStatus', systemStore.systemStatus)
  75. if (!systemStore.systemStatus.currentUser) {
  76. await router.push('/login')
  77. }
  78. solutionList.value = (await getSolsList()).list
  79. if (!systemStore.systemStatus.selfTest && systemStore.systemStatus.currentUser && systemStore.systemStatus.currentUser?.username !== 'test') {
  80. isCheck.value = true
  81. }
  82. await checkCraft()
  83. })
  84. const checkCraftVisible = ref(false)
  85. const checkCraft = async () => {
  86. const res = await craftList()
  87. systemStore.errorCraft = res && res.length > 0
  88. systemStore.errCraftList = res
  89. }
  90. // meta的isDefault=true 并且 当isDebug=true时,把debug路由的页面也显示
  91. const menuList = computed(() => {
  92. return authRoutes.filter((item) => {
  93. if (item.meta?.isDefault) {
  94. return true
  95. }
  96. return (['/debug', '/positionDebug'].includes(item.path) && systemStore.isDebug)
  97. })
  98. })
  99. watch(() => systemStore.isDebug, () => {
  100. console.log('isDebug', systemStore.isDebug)
  101. if (!systemStore.isDebug && router.currentRoute.value.path === '/debug') {
  102. router.push('/')
  103. }
  104. })
  105. const isCheck = ref(false)
  106. const containerList = ref<Container.ContainerItem[]>()
  107. const solutionList = ref<Solution.SolutionItem[]>()
  108. const getContainer = async () => {
  109. containerList.value = await getContainerList()
  110. }
  111. const statusMap = {
  112. start: {
  113. type: 'primary',
  114. name: '指令开始执行',
  115. },
  116. success: {
  117. type: 'success',
  118. name: '指令执行成功',
  119. },
  120. fail: {
  121. type: 'danger',
  122. name: '指令执行异常',
  123. },
  124. }
  125. const commandHandle = async (command: string, params?: unknown) => {
  126. const data = {
  127. commandId: Date.now().toString(),
  128. command,
  129. params,
  130. }
  131. await useDebugStore().sendControl(data)
  132. }
  133. const editDateVisible = ref(false)
  134. const containerStatus = computed(() => {
  135. const empty = systemStore.systemStatus.solutionModule.solutionContainer.find(item => item.empty)
  136. if (empty) {
  137. return 'empty'
  138. }
  139. return 'full'
  140. // return 'empty'
  141. })
  142. </script>
  143. <template>
  144. <el-container class="main">
  145. <el-header class="header">
  146. <div class="logo">
  147. <img src="../assets/images/logo.svg" alt="" @click="handleLogoClick">
  148. <span class="title" @click="handleLogoClick">长春黄金研究院有限公司</span>
  149. <img :class="systemStore.menuExpand ? 'expand-icon' : 'fold-icon'" src="../assets/images/expand.svg" alt="" @click="systemStore.updateMenuExpand()">
  150. </div>
  151. <div class="header-right">
  152. <ft-button v-if="systemStore.isDebug" type="primary" :click-handle="() => commandHandle('stop_all_motor')">
  153. 停止所有电机
  154. </ft-button>
  155. <el-dropdown class="wifi-dropdown" trigger="click">
  156. <div class="wifi-icon">
  157. <img v-if="isClose" src="../assets/images/wifi.svg" alt="">
  158. <img v-else src="../assets/images/wifi-active.svg" alt="">
  159. </div>
  160. <template #dropdown>
  161. <el-dropdown-menu>
  162. <el-dropdown-item>
  163. <div class="logout">
  164. <span v-if="!isClose">已连接</span>
  165. <span v-else>已断开</span>
  166. </div>
  167. </el-dropdown-item>
  168. </el-dropdown-menu>
  169. </template>
  170. </el-dropdown>
  171. <div class="time" @click="editDateVisible = true">
  172. {{ currentTime }}
  173. </div>
  174. <div class="user">
  175. <el-dropdown class="user-dropdown" trigger="click">
  176. <div class="user-dropdown-item">
  177. <img src="../assets/images/user.svg" alt="">
  178. <span>{{ systemStore.systemStatus.currentUser?.nickname || systemStore.systemStatus.currentUser?.username }}</span>
  179. </div>
  180. <template #dropdown>
  181. <el-dropdown-menu>
  182. <el-dropdown-item @click="systemStore.logout()">
  183. <div class="logout">
  184. <img :src="logoutIcon" alt="">
  185. <span>退出登录</span>
  186. </div>
  187. </el-dropdown-item>
  188. </el-dropdown-menu>
  189. </template>
  190. </el-dropdown>
  191. </div>
  192. </div>
  193. </el-header>
  194. <el-container class="container">
  195. <el-aside class="aside" :class="{ 'aside-off': !systemStore.menuExpand }">
  196. <div
  197. v-for="item in menuList"
  198. :key="item.path"
  199. class="aside-item"
  200. :class="{ 'aside-item-active': router.currentRoute.value.path.includes(item.path) }"
  201. @click="router.push(item.path)"
  202. >
  203. <img class="swing-icon" :src="((router.currentRoute.value.path.includes(item.path) ? item.meta!.activeIcon : item.meta!.icon) as string)" alt="">
  204. <span class="text">{{ item.meta!.title }}</span>
  205. </div>
  206. </el-aside>
  207. <el-main>
  208. <router-view v-slot="{ Component }" class="content">
  209. <transition name="el-fade-in-linear">
  210. <component :is="Component" />
  211. </transition>
  212. </router-view>
  213. </el-main>
  214. </el-container>
  215. <el-footer class="footer" :class="{ 'footer-expand': !systemStore.menuExpand }">
  216. <el-row>
  217. <el-col :span="16">
  218. <div class="footer-left">
  219. <img src="../assets/images/run.svg" alt="">
  220. <span v-if="!systemStore.systemLogList.length" class="text">设备运行状态</span>
  221. <el-popover v-else width="auto" trigger="click" placement="top">
  222. <template #reference>
  223. <el-tag style="width: 100%" :type="statusMap[systemStore.systemLogList[0]?.status as keyof typeof statusMap].type">
  224. {{ systemStore.systemLogList[0]?.cmdName }}: {{ statusMap[systemStore.systemLogList[0]?.status as keyof typeof statusMap].name }}
  225. {{ systemStore.systemLogList[0]?.time }}
  226. </el-tag>
  227. </template>
  228. <template #default>
  229. <div class="log-box">
  230. <el-tag
  231. v-for="(item, key) in systemStore.systemLogList"
  232. :key
  233. :type="statusMap[item?.status as keyof typeof statusMap].type"
  234. >
  235. <div style="display: flex;justify-content: space-between;width: 100%">
  236. <span>
  237. <span>{{ item.cmdName }}: </span>
  238. <span>{{ statusMap[item.status as keyof typeof statusMap].name }}</span>
  239. </span>
  240. <span>{{ item.time }}</span>
  241. </div>
  242. <!-- {{ item.cmdName }}: {{ statusMap[item.status as keyof typeof statusMap].name }} -->
  243. <!-- {{ item.time }} -->
  244. </el-tag>
  245. </div>
  246. </template>
  247. </el-popover>
  248. </div>
  249. </el-col>
  250. <el-col :span="8">
  251. <el-popover
  252. placement="top-start"
  253. width="auto"
  254. trigger="click"
  255. @show="getContainer"
  256. >
  257. <template #reference>
  258. <div class="footer-right">
  259. <div class="status" :class="{ 'status-error': containerStatus === 'empty' }" />
  260. <span class="text">{{ containerStatus === 'empty' ? '容器溶液余量有空置' : '容器溶液余量正常' }}</span>
  261. </div>
  262. </template>
  263. <template #default>
  264. <div class="container-box">
  265. <div
  266. v-for="item in containerList"
  267. :key="item.id"
  268. class="container-main"
  269. >
  270. <Liquid
  271. :data="item"
  272. />
  273. <span>{{ solutionList?.find(s => s.id === item.solutionId)?.name }}</span>
  274. </div>
  275. </div>
  276. </template>
  277. </el-popover>
  278. </el-col>
  279. </el-row>
  280. </el-footer>
  281. <FtStream :visible="systemStore.streamVisible" />
  282. <Check v-if="isCheck" @close="isCheck = false" />
  283. <Stop v-if="systemStore.systemStatus.emergencyStop" />
  284. <CheckCraft v-if="checkCraftVisible" @close="checkCraftVisible = false" />
  285. <EditDate v-if="editDateVisible" @close="editDateVisible = false" @ok="editDateVisible = false" />
  286. </el-container>
  287. </template>
  288. <style scoped lang="scss">
  289. .main {
  290. box-sizing: border-box;
  291. height: 100%;
  292. background: #F6F6F6;
  293. .header, .footer {
  294. height: 50px;
  295. width: 100%;
  296. display: flex;
  297. align-items: center;
  298. justify-content: space-between;
  299. padding: 10px 15px;
  300. }
  301. .header {
  302. color: #393F46;
  303. .logo {
  304. height: 100%;
  305. display: flex;
  306. align-items: center;
  307. .title {
  308. margin:0 10px;
  309. color: #8799AB;
  310. font-weight: 600;
  311. }
  312. img {
  313. height: 100%;
  314. }
  315. .expand-icon {
  316. height: 15px;
  317. transition: all 0.3s;
  318. }
  319. .fold-icon {
  320. height: 15px;
  321. transform: rotate(90deg);
  322. transition: all 0.3s;
  323. }
  324. }
  325. .header-right {
  326. display: flex;
  327. align-items: center;
  328. height: 100%;
  329. .wifi-dropdown {
  330. height: 100%;
  331. .wifi-icon {
  332. width: 40px;
  333. height: 100%;
  334. background: #fff;
  335. border-radius: 5px;
  336. display: flex;
  337. align-items: center;
  338. justify-content: center;
  339. img {
  340. height: 50%;
  341. }
  342. }
  343. }
  344. .time {
  345. margin:0 10px;
  346. height: 100%;
  347. width: 170px;
  348. padding: 0 10px;
  349. display: flex;
  350. align-items: center;
  351. justify-content: center;
  352. background: #fff;
  353. border-radius: 5px;
  354. }
  355. }
  356. }
  357. .container {
  358. height: calc(100% - 100px);
  359. }
  360. }
  361. .aside {
  362. width: 170px;
  363. overflow: auto;
  364. padding-left: 10px;
  365. //transition: all 0.1s ease;
  366. .aside-item {
  367. width: 100%;
  368. height: 50px;
  369. border-radius: 10px;
  370. color: #1989FA;
  371. margin: 10px 0;
  372. padding: 0 10px;
  373. display: flex;
  374. align-items: center;
  375. overflow: hidden;
  376. img {
  377. height: 80%;
  378. margin-right: 20px;
  379. }
  380. .text {
  381. transition: opacity 0.3s ease;
  382. white-space: nowrap;
  383. }
  384. }
  385. .aside-item-active {
  386. background: #1989FA;
  387. color: #fff;
  388. }
  389. }
  390. .aside-off {
  391. width: 70px;
  392. //transition: all 0.1s ease;
  393. .aside-item {
  394. .text {
  395. opacity: 0
  396. }
  397. }
  398. .aside-item-active {
  399. background: rgba(0,0,0,0);
  400. color: #fff;
  401. }
  402. }
  403. .user-dropdown-item {
  404. display: flex;
  405. align-items: center;
  406. height: 100%;
  407. color: #393F46;
  408. font-weight: bold;
  409. img {
  410. height: 30px;
  411. margin-right: 10px;
  412. }
  413. }
  414. .el-main {
  415. padding: 0 15px;
  416. height: 100%;
  417. position: relative;
  418. }
  419. .content {
  420. width: 100%;
  421. height: 100%;
  422. background: #fff;
  423. border-radius: 10px;
  424. box-shadow: 0 0 1px rgba(0, 0, 0, 0.1);
  425. padding: 10px;
  426. overflow: hidden;
  427. }
  428. .footer-expand {
  429. padding: 10px 15px 10px 85px !important;
  430. }
  431. .main .footer {
  432. padding: 10px 15px 10px 185px;
  433. .el-row {
  434. width: 100%;
  435. height: 100%;
  436. .el-col {
  437. height: 100%;
  438. }
  439. }
  440. .footer-left, .footer-right {
  441. width: 100%;
  442. height: 100%;
  443. background: #fff;
  444. border-radius: 5px;
  445. display: flex ;
  446. align-items: center;
  447. padding: 0 20px;
  448. }
  449. .footer-left {
  450. border-right: 5px solid #F6F6F6;
  451. img {
  452. height: 60%;
  453. margin-right: 20px;
  454. }
  455. .text {
  456. color: #1C1C1C;
  457. margin-left: 10px;
  458. font-size: 14px;
  459. }
  460. }
  461. .footer-right {
  462. border-left: 10px solid #F6F6F6;
  463. .status-error {
  464. background: #F56C6C !important;
  465. }
  466. .status {
  467. width: 15px;
  468. height: 15px;
  469. border-radius: 50%;
  470. background: #4EE993;
  471. }
  472. .text {
  473. color: #1C1C1C ;
  474. margin-left: 10px;
  475. font-size: 14px;
  476. }
  477. }
  478. }
  479. .aside-item:hover {
  480. .swing-icon {
  481. animation: swing 1s ease-in-out;
  482. }
  483. }
  484. .logout {
  485. display: flex;
  486. img {
  487. width: 15px;
  488. margin-right: 10px;
  489. }
  490. }
  491. .container-box {
  492. width: 400px;
  493. display: grid;
  494. grid-template-columns: repeat(4, 1fr);
  495. grid-template-rows: repeat(1, 1fr);
  496. grid-gap: 10px;
  497. .container-main {
  498. display: flex;
  499. flex-direction: column;
  500. align-items: center;
  501. }
  502. }
  503. .log-box {
  504. width: 500px;
  505. height: 400px;
  506. overflow: auto;
  507. :deep(.el-tag) {
  508. margin: 5px 0;
  509. width: 100%;
  510. .el-tag__content {
  511. width: 100%;
  512. }
  513. }
  514. }
  515. @keyframes swing {
  516. 0% {
  517. transform: rotate(0deg);
  518. }
  519. 25% {
  520. transform: rotate(-30deg);
  521. }
  522. 50% {
  523. transform: rotate(30deg);
  524. }
  525. 75% {
  526. transform: rotate(-15deg);
  527. }
  528. 100% {
  529. transform: rotate(0deg);
  530. }
  531. }
  532. </style>