feat: 问题诊断列表弹窗对接接口数据,抽离轮询时间配置,添加设备切换按钮
| | |
| | | "xstate": "^4.38.3" |
| | | }, |
| | | "devDependencies": { |
| | | "@iconify-json/bx": "^1.1.7", |
| | | "@iconify-json/material-symbols-light": "^1.1.0", |
| | | "@iconify-json/mdi": "^1.1.55", |
| | | "@rushstack/eslint-patch": "^1.3.3", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", |
| | | "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", |
| | | "dev": true |
| | | }, |
| | | "node_modules/@iconify-json/bx": { |
| | | "version": "1.1.7", |
| | | "resolved": "https://registry.npmmirror.com/@iconify-json/bx/-/bx-1.1.7.tgz", |
| | | "integrity": "sha512-Ugh8uUU9VtK8fI9BBnhLA7VvhPh7erSmJz+eqjvl8HCRRjkz5mbMO5/KYpCOriUVdiiKB9Yv1ObMqS73WLMSwA==", |
| | | "dev": true, |
| | | "dependencies": { |
| | | "@iconify/types": "*" |
| | | } |
| | | }, |
| | | "node_modules/@iconify-json/material-symbols-light": { |
| | | "version": "1.1.0", |
| | |
| | | "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", |
| | | "dev": true |
| | | }, |
| | | "@iconify-json/bx": { |
| | | "version": "1.1.7", |
| | | "resolved": "https://registry.npmmirror.com/@iconify-json/bx/-/bx-1.1.7.tgz", |
| | | "integrity": "sha512-Ugh8uUU9VtK8fI9BBnhLA7VvhPh7erSmJz+eqjvl8HCRRjkz5mbMO5/KYpCOriUVdiiKB9Yv1ObMqS73WLMSwA==", |
| | | "dev": true, |
| | | "requires": { |
| | | "@iconify/types": "*" |
| | | } |
| | | }, |
| | | "@iconify-json/material-symbols-light": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmmirror.com/@iconify-json/material-symbols-light/-/material-symbols-light-1.1.0.tgz", |
| | |
| | | "xstate": "^4.38.3" |
| | | }, |
| | | "devDependencies": { |
| | | "@iconify-json/bx": "^1.1.7", |
| | | "@iconify-json/material-symbols-light": "^1.1.0", |
| | | "@iconify-json/mdi": "^1.1.55", |
| | | "@rushstack/eslint-patch": "^1.3.3", |
| | |
| | | export interface Devices { |
| | | /** 本机设备编码 */ |
| | | systemDeviceID: string |
| | | currentDeviceID: string |
| | | systemDeviceStatus: number |
| | |
| | | import type { PLCResponse } from './plc' |
| | | import type { Devices } from './device' |
| | | import type { CraftModel } from './craftModel' |
| | | import type { Problem } from './problem' |
| | | |
| | | export interface BaseResponse<T = any> { |
| | | code: number |
| | |
| | | data: params |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 获取问题诊断问题列表 |
| | | */ |
| | | export function apiGetProblemList() { |
| | | return request<BaseResponse<Problem[]>>({ |
| | | url: '/v1/system/problemList', |
| | | method: 'get' |
| | | }) |
| | | } |
New file |
| | |
| | | export interface Problem { |
| | | /** true正常 false异常 */ |
| | | CheckResult: boolean |
| | | ItemCode: string |
| | | ItemName: string |
| | | } |
| | |
| | | 7: 'G' |
| | | } |
| | | |
| | | export const DEVICE_STATUS_NAME_MAP = { |
| | | 1: '正常', |
| | | 2: '异常' |
| | | } |
| | | // plc 轮询间隔 |
| | | export const PLC_POLLING_DURATION = 6_000 |
| | | // 设备信息 轮询间隔 |
| | | export const DEVICE_INFO_POLLING_DURATION = 15_000 |
| | | // 问题诊断 |
| | | export const PROBLEMS_POLLING_DURATION = 30_000 |
| | |
| | | <g> |
| | | <path |
| | | style="opacity: 0.988" |
| | | fill="#d71d05" |
| | | d="M 87.5,-0.5 C 95.1667,-0.5 102.833,-0.5 110.5,-0.5C 146.888,7.72465 167.388,30.3913 172,67.5C 172.5,101.165 172.667,134.832 172.5,168.5C 123.833,168.5 75.1667,168.5 26.5,168.5C 26.3333,132.498 26.5,96.4985 27,60.5C 33.9986,27.0017 54.1653,6.66837 87.5,-0.5 Z M 101.5,40.5 C 103.678,52.6006 105.011,64.9339 105.5,77.5C 116.858,77.4139 128.191,77.9139 139.5,79C 123.945,100.223 108.778,121.723 94,143.5C 93.167,128.176 92.667,112.842 92.5,97.5C 80.4954,97.6665 68.4954,97.4999 56.5,97C 71.8692,78.4097 86.8692,59.5764 101.5,40.5 Z" |
| | | /> |
| | | </g> |
| | | <g> |
| | | <path |
| | | style="opacity: 0.995" |
| | | fill="#d81d05" |
| | | d="M 199.5,183.5 C 199.5,186.833 199.5,190.167 199.5,193.5C 197.167,195.167 195.167,197.167 193.5,199.5C 130.833,199.5 68.1667,199.5 5.5,199.5C 3.83333,197.167 1.83333,195.167 -0.5,193.5C -0.5,190.167 -0.5,186.833 -0.5,183.5C 1.67098,181.5 4.00432,179.666 6.5,178C 68.5,177.333 130.5,177.333 192.5,178C 194.996,179.666 197.329,181.5 199.5,183.5 Z" |
| | | /> |
| | | </g> |
| | |
| | | import { getDeviceList } from '@/api' |
| | | import { useRequest } from 'vue-hooks-plus' |
| | | import type { Devices } from '@/api/device' |
| | | import { DEVICE_INFO_POLLING_DURATION } from '@/common/constants' |
| | | |
| | | export const useDevicesStore = defineStore('device', () => { |
| | | const deviceInfo = computed(() => { |
| | |
| | | cancel: cancelDevicePolling |
| | | } = useRequest(getDeviceList, { |
| | | manual: true, |
| | | pollingInterval: 6000, |
| | | pollingInterval: DEVICE_INFO_POLLING_DURATION, |
| | | pollingWhenHidden: false |
| | | }) |
| | | |
| | |
| | | import { useRequest } from 'vue-hooks-plus' |
| | | import { useTasksStore } from '@/stores/tasks' |
| | | import type { PLCResponse } from '@/api/plc' |
| | | import { PLC_POLLING_DURATION } from '@/common/constants' |
| | | |
| | | // 全局 watcher ref 防止多次调用 usePLCStore 时重复注册侦听器 |
| | | const unwatch = ref() |
| | |
| | | } as ProductProgressParams), |
| | | { |
| | | manual: true, |
| | | pollingInterval: 6000, |
| | | pollingInterval: PLC_POLLING_DURATION, |
| | | pollingWhenHidden: false |
| | | } |
| | | ) |
| | |
| | | <template> |
| | | <div class="dashboard-title"> |
| | | <div class="title-text">智能工作台 — {{ deviceStore?.deviceInfo?.currentDeviceID ?? '' }}</div> |
| | | <div class="title-text"> |
| | | 智能工作台 — {{ deviceStore?.deviceInfo?.currentDeviceID ?? '' }} |
| | | <el-icon size="32" color="#0db7f5" style="margin-left: 20px; cursor: pointer" @click="openDevicesModal"> |
| | | <IconSlider></IconSlider> |
| | | </el-icon> |
| | | </div> |
| | | <div class="title-status"> |
| | | <div class="connection-info" @click="openSelectDeviceModal"> |
| | | <el-icon size="30" color="red"> |
| | | <div class="connection-info" @click="openProblemsModal"> |
| | | <el-icon size="30" :color="problemsIconStatus ? '#00ff00' : '#ff0000'"> |
| | | <AlertLightIcon></AlertLightIcon> |
| | | </el-icon> |
| | | </div> |
| | | <div class="cloud-connection-status"> |
| | | <el-icon size="45" color="#ff0000"> |
| | | <IconCloudOff></IconCloudOff> |
| | | <el-icon v-if="cloudConnectionIconStatus" size="45" color="#00ff00"> |
| | | <IconCloudDone></IconCloudDone> |
| | | </el-icon> |
| | | |
| | | <el-icon size="45" color="#00ff00"> |
| | | <IconCloudDone></IconCloudDone> |
| | | <el-icon v-else size="45" color="#ff0000"> |
| | | <IconCloudOff></IconCloudOff> |
| | | </el-icon> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <TroubleTrackerModal v-model="showModal"></TroubleTrackerModal> |
| | | <TroubleTrackerModal v-model="showProblemsModal" :problems="problemList"></TroubleTrackerModal> |
| | | </template> |
| | | <script setup lang="ts"> |
| | | import AlertLightIcon from '@/components/icons/AlertLightIcon.vue' |
| | | import { ref } from 'vue' |
| | | import { computed, onUnmounted, ref } from 'vue' |
| | | import { useDevicesStore } from '@/stores/devices' |
| | | import TroubleTrackerModal from '@/views/dashboard/components/TroubleTrackerModal.vue' |
| | | import IconCloudDone from '~icons/material-symbols-light/cloud-done-outline' |
| | | import IconCloudOff from '~icons/material-symbols-light/cloud-off-outline' |
| | | import IconSlider from '~icons/bx/slider' |
| | | import { useRequest } from 'vue-hooks-plus' |
| | | import { apiGetProblemList } from '@/api' |
| | | import { PROBLEMS_POLLING_DURATION } from '@/common/constants' |
| | | |
| | | const showModal = ref(false) |
| | | |
| | | function openSelectDeviceModal() { |
| | | showModal.value = true |
| | | // 是否显示问题诊断modal |
| | | const showProblemsModal = ref(false) |
| | | /** |
| | | * 打开问题诊断modal |
| | | */ |
| | | function openProblemsModal() { |
| | | showProblemsModal.value = true |
| | | } |
| | | |
| | | // 是否显示设备切换modal |
| | | const showDevicesModal = ref(false) |
| | | /** |
| | | * 打开设备切换modal |
| | | */ |
| | | function openDevicesModal() { |
| | | showDevicesModal.value = true |
| | | } |
| | | |
| | | // 获取当前设备名 |
| | | const deviceStore = useDevicesStore() |
| | | |
| | | // 问题诊断列表 |
| | | const problemList = computed(() => { |
| | | return problemsRes?.value?.data ?? [] |
| | | }) |
| | | // 问题诊断icon状态, 问题列表中有一条异常即为红灯 否则是绿灯 true绿灯 |
| | | const problemsIconStatus = computed(() => { |
| | | if (!problemList.value || !problemList.value?.length) { |
| | | // 默认绿灯, 拿到一次数据后才以接口为准 |
| | | return true |
| | | } |
| | | return !problemList.value.some((ele) => !ele.CheckResult) |
| | | }) |
| | | // 云端连接icon状态, 问题列表中有一条代表云端链接的, 异常即为红色云icon 否则是绿色 true绿云 |
| | | const cloudConnectionIconStatus = computed(() => { |
| | | if (!problemList.value || !problemList.value?.length) { |
| | | // 默认绿灯, 拿到一次数据后才以接口为准 |
| | | return true |
| | | } |
| | | // 没数据就当是链接正常 |
| | | const cloudConnection = problemList.value.find((ele) => ele.ItemCode === 'cloud') |
| | | return cloudConnection ? cloudConnection?.CheckResult : true |
| | | }) |
| | | |
| | | /** |
| | | * 轮询问题诊断 |
| | | */ |
| | | const { |
| | | data: problemsRes, |
| | | run: startProblemsPolling, |
| | | cancel: cancelProblemsPolling |
| | | } = useRequest(apiGetProblemList, { |
| | | manual: true, |
| | | pollingInterval: PROBLEMS_POLLING_DURATION, |
| | | pollingWhenHidden: false |
| | | }) |
| | | startProblemsPolling() |
| | | onUnmounted(() => { |
| | | cancelProblemsPolling() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | <div class="device-t">本机设备编码</div> |
| | | <div class="device-b"> |
| | | <div class="device-info"> |
| | | {{ deviceInfo?.currentDeviceID }} |
| | | {{ deviceInfo?.systemDeviceID }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <template #title>问题诊断 </template> |
| | | <div class="modal-content"> |
| | | <el-scrollbar always class="scroller"> |
| | | <div v-if="fakeTroubles?.length" class="trouble"> |
| | | <div v-for="(item, index) in fakeTroubles" :key="index" class="trouble-item"> |
| | | <div v-if="problems?.length" class="trouble"> |
| | | <div v-for="(item, index) in problems" :key="index" class="trouble-item"> |
| | | <div class="trouble-content"> |
| | | <div class="trouble-icon"> |
| | | <el-icon v-if="item.status === 2" size="30" color="#ff0000"><WarnTriangleFilled /></el-icon> |
| | | <el-icon v-if="item.status === 1" size="30" color="#00ff00"><CircleCheckFilled /></el-icon> |
| | | <el-icon v-if="item.CheckResult" size="30" color="#00ff00"><CircleCheckFilled /></el-icon> |
| | | <el-icon v-if="!item.CheckResult" size="30" color="#ff0000"><WarnTriangleFilled /></el-icon> |
| | | </div> |
| | | <div class="trouble-text">{{ item.content }}</div> |
| | | <div class="trouble-text">{{ item.ItemName }}</div> |
| | | </div> |
| | | <div class="trouble-status" :class="{ green: item.status === 1, red: item.status === 2 }"> |
| | | {{ DEVICE_STATUS_NAME_MAP[item.status] }} |
| | | <div class="trouble-status" :class="{ green: item.CheckResult, red: !item.CheckResult }"> |
| | | {{ item.CheckResult ? '正常' : '异常' }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | <script setup lang="ts"> |
| | | import { useVModel } from '@vueuse/core' |
| | | import { ref } from 'vue' |
| | | import { CircleCheckFilled, WarnTriangleFilled } from '@element-plus/icons-vue' |
| | | import { DEVICE_STATUS_NAME_MAP } from '@/common/constants' |
| | | import type { Problem } from '@/api/problem' |
| | | import { toRefs } from 'vue' |
| | | export interface TroubleTrackerModalProps { |
| | | modelValue: boolean |
| | | problems?: Problem[] |
| | | } |
| | | const props = withDefaults(defineProps<TroubleTrackerModalProps>(), { |
| | | modelValue: false |
| | | modelValue: false, |
| | | problems: undefined |
| | | }) |
| | | const { problems } = toRefs(props) |
| | | const emit = defineEmits<{ |
| | | 'update:modelValue': [show: boolean] |
| | | }>() |
| | |
| | | function closeModal() { |
| | | modelData.value = false |
| | | } |
| | | |
| | | const fakeTroubles = ref<{ content: string; status: 1 | 2 }[]>([{ content: '云端网络连接', status: 1 }]) |
| | | // TODO: 等接口 |
| | | fakeTroubles.value.push( |
| | | ...Array(100) |
| | | .fill(0) |
| | | .map(() => { |
| | | return { |
| | | content: '云端网络连接', |
| | | status: Math.ceil(Math.random() + 1) |
| | | } as { content: string; status: 1 | 2 } |
| | | }) |
| | | ) |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | .modal-content { |