package-lock.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
package.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/device.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/plc.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/stores/devices.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/stores/plc.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/stores/tasks.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/components/DashboardTitle.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/components/DeviceNumberInfo.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/components/DeviceStatusInfo.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/components/ProcessingInfo.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/components/TaskControlModal.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/components/TroubleTrackerModal.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/dashboard/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
package-lock.json
@@ -22,6 +22,7 @@ "xstate": "^4.38.3" }, "devDependencies": { "@iconify-json/material-symbols-light": "^1.1.0", "@iconify-json/mdi": "^1.1.55", "@rushstack/eslint-patch": "^1.3.3", "@tsconfig/node18": "^18.2.2", @@ -810,6 +811,15 @@ "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/material-symbols-light": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/@iconify-json/material-symbols-light/-/material-symbols-light-1.1.0.tgz", "integrity": "sha512-4ijRVXNjdspFh52lmK2pdZyadqEoYDOUAJ2GsptFXFqeVHcdzKB0XiIp235XJ+xVZbjScY1SzwrsozFx8kOvDA==", "dev": true, "dependencies": { "@iconify/types": "*" } }, "node_modules/@iconify-json/mdi": { "version": "1.1.55", @@ -5453,6 +5463,15 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@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", "integrity": "sha512-4ijRVXNjdspFh52lmK2pdZyadqEoYDOUAJ2GsptFXFqeVHcdzKB0XiIp235XJ+xVZbjScY1SzwrsozFx8kOvDA==", "dev": true, "requires": { "@iconify/types": "*" } }, "@iconify-json/mdi": { "version": "1.1.55", "resolved": "https://registry.npmmirror.com/@iconify-json/mdi/-/mdi-1.1.55.tgz", package.json
@@ -27,6 +27,7 @@ "xstate": "^4.38.3" }, "devDependencies": { "@iconify-json/material-symbols-light": "^1.1.0", "@iconify-json/mdi": "^1.1.55", "@rushstack/eslint-patch": "^1.3.3", "@tsconfig/node18": "^18.2.2", src/api/device.ts
@@ -2,7 +2,8 @@ systemDeviceID: string currentDeviceID: string systemDeviceStatus: number clusterStatus: string /** 集群状态 master主节点 slave从节点 空串代表不在集群中 */ clusterStatus: 'master' | 'slave' | '' clusterNodeQuantity: number systemDeviceRunSince: number deviceIDList: string[] src/api/plc.ts
@@ -2,7 +2,6 @@ finishNumber: number totalNumber: number /** 1断开 2生产中 3待机*/ // plcStatus: 1 | 2 | 3, plcStatus: 1 plcStatus: 1 | 2 | 3 plcNotConnected: String } src/stores/devices.ts
@@ -1,23 +1,35 @@ import { ref, computed } from 'vue' import { computed, onUnmounted } from 'vue' import { defineStore } from 'pinia' import type { Devices } from '@/api/device' import { getDeviceList } from '@/api' import { useRequest } from 'vue-hooks-plus' import type { Devices } from '@/api/device' export const useDevicesStore = defineStore('counter', () => { const devices = ref<Devices>() const deviceIDList = computed(() => devices?.value?.deviceIDList ?? []) export const useDevicesStore = defineStore('device', () => { const deviceInfo = computed(() => { return deviceRes?.value?.data as Devices }) function getDevicesInfo() { getDeviceList().then( (res) => { devices.value = res?.data }, (err) => { console.error(err) devices.value = undefined } ) /** * 如果任务状态是进行中, 则轮询 plc 取进度 */ const { data: deviceRes, run: startDevicePolling, cancel: cancelDevicePolling } = useRequest(getDeviceList, { manual: true, pollingInterval: 6000, pollingWhenHidden: false }) function startPollingDevice() { cancelDevicePolling() startDevicePolling() } return { devices, deviceIDList, getDevicesInfo } onUnmounted(() => { cancelDevicePolling() }) return { deviceInfo, startPollingDevice } }) src/stores/plc.ts
New file @@ -0,0 +1,62 @@ import { computed, watch, onUnmounted, ref } from 'vue' import { defineStore } from 'pinia' import { getProductProgress } from '@/api' import type { ProductProgressParams } from '@/api' import { useRequest } from 'vue-hooks-plus' import { useTasksStore } from '@/stores/tasks' import type { PLCResponse } from '@/api/plc' // 全局 watcher ref 防止多次调用 usePLCStore 时重复注册侦听器 const unwatch = ref() export const usePLCStore = defineStore('plc', () => { const taskStore = useTasksStore() const plcInfo = computed(() => { return plcRes?.value?.data as PLCResponse }) /** * 如果任务状态是进行中, 则轮询 plc 取进度 */ const { data: plcRes, run: startPLCPolling, cancel: cancelPLCPolling } = useRequest( () => getProductProgress({ channel: taskStore.activeTask?.Channel, procedureId: taskStore.activeTask?.Procedure.ID } as ProductProgressParams), { manual: true, pollingInterval: 6000, pollingWhenHidden: false } ) if (!unwatch.value) { /** * 如果切换到其他通道的任务,则重新轮询plc */ unwatch.value = watch( () => taskStore.activeTask?.Channel, () => { cancelPLCPolling() startPLCPolling() } ) } function startPollingPLC() { cancelPLCPolling() startPLCPolling() } onUnmounted(() => { cancelPLCPolling() }) return { plcInfo, startPollingPLC } }) src/stores/tasks.ts
@@ -28,6 +28,11 @@ return getTaskList(params) .then((res) => { channels.value = res.data // 首次获取通道数据时自动选中第一个任务 if (!activeTask?.value) { selectFirstTask(channels.value) } }) .catch((err) => { console.error(err) @@ -35,6 +40,19 @@ }) } function selectFirstTask(channels: TasksGroupByChannel) { const firstNotEmptyChannel = Object.entries(channels).find((ele) => { const taskList = (ele[1] as TasksResponse)?.Tasks return !!taskList.length }) if (firstNotEmptyChannel) { const channelNumber = +firstNotEmptyChannel[0] activeTask.value = channels[channelNumber].Tasks[0] } } /** * 数据加载完成后自动选中一个任务 */ src/views/dashboard/components/DashboardTitle.vue
@@ -7,7 +7,15 @@ <AlertLightIcon></AlertLightIcon> </el-icon> </div> <div class="connection-status"></div> <div class="cloud-connection-status"> <el-icon size="45" color="#ff0000"> <IconCloudOff></IconCloudOff> </el-icon> <el-icon size="45" color="#00ff00"> <IconCloudDone></IconCloudDone> </el-icon> </div> </div> </div> @@ -18,12 +26,8 @@ import { ref } from 'vue' import { useDevicesStore } from '@/stores/devices' import TroubleTrackerModal from '@/views/dashboard/components/TroubleTrackerModal.vue' export interface DashBoardTitleProps { // deviceInfo: Devices } const props = defineProps<DashBoardTitleProps>() import IconCloudDone from '~icons/material-symbols-light/cloud-done-outline' import IconCloudOff from '~icons/material-symbols-light/cloud-off-outline' const showModal = ref(false) @@ -32,8 +36,6 @@ } const deviceStore = useDevicesStore() deviceStore.getDevicesInfo() </script> <style scoped lang="scss"> @@ -48,11 +50,14 @@ font-weight: 700; } .title-status { display: flex; align-items: center; position: absolute; top: 16px; right: 40px; } .connection-info { margin-right: 10px; cursor: pointer; } </style> src/views/dashboard/components/DeviceNumberInfo.vue
@@ -4,31 +4,26 @@ <div class="device-t">本机设备编码</div> <div class="device-b"> <div class="device-info"> {{ deviceCurrent }} {{ deviceInfo?.currentDeviceID }} </div> </div> </div> <div class="device-r"> <div class="device-t">云端设备编码</div> <div class="device-b"> <div v-for="(item, index) in deviceList" :key="index" class="device-info"> {{ item.value }} <div v-for="(item, index) in deviceInfo?.deviceIDList" :key="index" class="device-info"> {{ item }} </div> </div> </div> </div> </template> <script setup lang="ts"> import { toRefs } from 'vue' const deviceList = [ { value: '123454' }, { value: '54321' } ] const deviceCurrent = '456789' import { useDevicesStore } from '@/stores/devices' import { storeToRefs } from 'pinia' const deviceStore = useDevicesStore() const { deviceInfo } = storeToRefs(deviceStore) </script> <style scoped lang="scss"> src/views/dashboard/components/DeviceStatusInfo.vue
@@ -1,11 +1,11 @@ <template> <div class="device-status-info"> <div v-if="type == 1" class="color-one"> <div v-if="type === 1" class="color-one"> 设备状态 <!-- 1断开2生产3待机 --> <span v-if="device.plcStatus" class="device-m"> <span v-if="plc?.plcStatus" class="device-m"> <el-popover v-if="device.plcStatus == 1 && device.plcNotConnected" v-if="plc?.plcStatus === 1 && plc?.plcNotConnected" :width="180" placement="top-end" trigger="click" @@ -16,10 +16,10 @@ </el-icon> 断开 </template> {{ device.plcNotConnected }} {{ plc?.plcNotConnected }} </el-popover> <span v-else> <el-icon v-if="device.plcStatus == 1 && !device.plcNotConnected" class="duan"> <el-icon v-if="plc?.plcStatus == 1 && !plc?.plcNotConnected" class="duan"> <Link /> </el-icon> <el-icon v-else class="lian"> @@ -30,66 +30,125 @@ 'status-off': device.plcStatus != 2, }" --> <span class="status-running"> {{ device.plcStatus == 1 ? '断开' : device.plcStatus == 2 ? '生产中' : '待机' }} {{ plc?.plcStatus === 1 ? '断开' : plc?.plcStatus === 2 ? '生产中' : '待机' }} </span> </span> </span> <div class="device-b">运行时间:1天1小时23分12秒</div> <!-- TODO: 应该改成 plc里取,缺接口 --> <div class="device-b">工序运行时间:{{ runningTime }}</div> </div> <div v-if="type == 2" class="color-two"> 集群状态 <!-- 1断开2生产3待机 --> <span v-if="device.plcStatus" class="device-m"> <el-popover v-if="device.plcStatus == 1 && device.plcNotConnected" :width="180" placement="top-end" trigger="click" > <template #reference> <el-icon class="duan"> <CircleCloseFilled /> <span class="device-m"> <span> <template v-if="device?.clusterStatus"> <el-icon class="lian"> <SuccessFilled /> </el-icon> 断开 <span class="status-running"> 在线 </span> </template> {{ device.plcNotConnected }} </el-popover> <span v-else> <el-icon v-if="device.plcStatus == 1 && !device.plcNotConnected" class="duan"> <CircleCloseFilled /> </el-icon> <el-icon v-else class="lian"> <SuccessFilled /> </el-icon> <!-- :class="{ 'status-running': device.plcStatus == 2, 'status-off': device.plcStatus != 2, }" --> <span class="status-running"> {{ device.plcStatus == 1 ? '断开' : device.plcStatus == 2 ? '在线' : '待机' }} </span> <template v-else> <span>不在集群中</span> </template> </span> </span> <div class="device-info">主节点</div> <div class="device-info" :class="{ master: device?.clusterStatus === 'master', slave: device?.clusterStatus === 'slave' }" > {{ nodeTypeText }} </div> <div class="device-b title-bng"> <img src="~@/assets/images/leaf.png" /> <span>节点数</span> <i>120</i> <img src="~@/assets/images/leaf.png" alt="" /> <span>节点数</span> <i>{{ device?.clusterNodeQuantity ?? 0 }}</i> </div> </div> </div> </template> <script setup lang="ts"> import { toRefs } from 'vue' import { Link, CircleCloseFilled, SuccessFilled } from '@element-plus/icons-vue' import { computed, toRefs } from 'vue' import { Link, SuccessFilled } from '@element-plus/icons-vue' import type { Devices } from '@/api/device' import type { PLCResponse } from '@/api/plc' import { useTasksStore } from '@/stores/tasks' import { storeToRefs } from 'pinia' export interface DeviceStatusInfoProps { device: PLCResponse type?: Number plc?: PLCResponse device?: Devices /** 1: 设备状态 2:集群状态*/ type?: 1 | 2 } const props = defineProps<DeviceStatusInfoProps>() const { device, type } = toRefs(props) const props = withDefaults(defineProps<DeviceStatusInfoProps>(), { type: 1, device: undefined, plc: undefined }) const { type, plc, device } = toRefs(props) // 集群节点文本 const nodeTypeText = computed(() => { switch (device?.value?.clusterStatus) { case 'master': return '主节点' case 'slave': return '从节点' case '': default: return '' } }) /** * 工序运行时间 * 根据接口返回 realStartTime realEndTime 判断如何展示 * 如果 realEndTime 为 0 则 用当前时间 - realStartTime * 如果 存在 realEndTime, 则 realEndTime - realStartTime * 注意 realStartTime realEndTime 是接口返回的10位时间戳 * @param realStartTime * @param realEndTime * @return */ function getTaskRunningTime(realStartTime?: number, realEndTime?: number) { if (realStartTime && realEndTime) { return calcRunningDuration(realStartTime * 1000, realEndTime * 1000) } else if (!realEndTime && realStartTime) { const now = Math.floor(new Date().getTime() / 1000) * 1000 return calcRunningDuration(realStartTime * 1000, now) } else { return '--' } } /** * 计算时间段, 注意参数要求时间戳为 JS Date 的13位时间戳 * @param startTime * @param endTime * @return */ function calcRunningDuration(startTime: number, endTime: number) { let timeDuration = endTime - startTime if (timeDuration < 0) { return '0天0时0分' } let seconds = Math.floor(timeDuration / 1000) let minutes = Math.floor(seconds / 60) let days = Math.floor(timeDuration / 1000 / 60 / 60 / 24) let hours = Math.floor(minutes / 60) - days * 24 let m = minutes - days * 24 * 60 - hours * 60 return `${days}天${hours}时${m}分` } // 工序运行时间 const taskStore = useTasksStore() const { activeTask } = storeToRefs(taskStore) const runningTime = computed(() => { return getTaskRunningTime(activeTask?.value?.Procedure?.realStartTime, activeTask?.value?.Procedure?.realEndTime) }) </script> <style scoped lang="scss"> @@ -162,9 +221,14 @@ padding: 0px 6px; font-size: 12px; line-height: 20px; background: $status-running; color: #fff; border-radius: 8px; &.master { background: $status-running; } &.slave { background-color: #3399ff; } } .title-bng { width: calc(90% - 1px); src/views/dashboard/components/ProcessingInfo.vue
@@ -62,6 +62,7 @@ import type { ProductProgressParams } from '@/api' import { isNumber } from 'lodash-es' import { CHANNEL_NAME_MAP } from '@/common/constants' import { usePLCStore } from '@/stores/plc' const props = defineProps<{ task?: Task @@ -94,6 +95,8 @@ return result > 100 ? 100 : result } const plcStore = usePLCStore() /** * 计算完成进度, 任务状态未生产固定为 0% 已完成固定为 100% 生产中则从plc获取 */ @@ -107,47 +110,10 @@ } if (task?.value?.Procedure?.Status === 2) { return calculateProgress(plcResponse?.value?.data as Statistics) return calculateProgress(plcStore.plcInfo as Statistics) } return 0 }) /** * 如果任务状态是进行中, 则轮询 plc 取进度 */ const { data: plcResponse, run: startPLCPolling, cancel: cancelPLCPolling } = useRequest( () => getProductProgress({ channel: task?.value?.Channel, procedureId: task?.value?.Procedure.ID } as ProductProgressParams), { manual: true, pollingInterval: 6000, pollingWhenHidden: false } ) /** * 任务状态是生产中则轮询plc取目标数和完成数计算完成进度 */ watch( () => task?.value, () => { cancelPLCPolling() if (task?.value?.Procedure?.Status === 2) { startPLCPolling() } } ) onUnmounted(() => { cancelPLCPolling() }) /** src/views/dashboard/components/TaskControlModal.vue
@@ -238,7 +238,7 @@ const safeProduce = ref('') watch(modelData, () => { if (modelData.value) { safeProduce.value = channels?.value?.[task.value.Channel]?.Prompt?.safeProduce ?? '' safeProduce.value = channels?.value?.[task?.value?.Channel ?? 0]?.Prompt?.safeProduce ?? '' } }) src/views/dashboard/components/TroubleTrackerModal.vue
@@ -24,12 +24,12 @@ </div> </template> <script setup lang="ts"> import { useVModel } from '@vueuse/core/index' 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' export interface TroubleTrackerModalProps { modelValue: false modelValue: boolean } const props = withDefaults(defineProps<TroubleTrackerModalProps>(), { modelValue: false src/views/dashboard/index.vue
@@ -41,8 +41,8 @@ </div> </template> <template #rightBlock2> <DeviceStatusInfo :device="device" :type="1"></DeviceStatusInfo> <DeviceStatusInfo :device="device" :type="2"></DeviceStatusInfo> <DeviceStatusInfo :plc="plcStore.plcInfo" :type="1"></DeviceStatusInfo> <DeviceStatusInfo :device="deviceStore.deviceInfo" :type="2"></DeviceStatusInfo> <DeviceNumberInfo></DeviceNumberInfo> </template> <template #rightBlock3> @@ -53,7 +53,7 @@ </DashboardLayout> </template> <script setup lang="ts"> import { computed, ref } from 'vue' import { computed, ref, watch, onUnmounted } from 'vue' import ChannelCollapse from '@/views/dashboard/components/ChannelCollapse.vue' import type { Worker, Order, Task } from '@/api/task' import type { PLCResponse } from '@/api/plc' @@ -75,6 +75,8 @@ import TaskControl from '@/views/dashboard/components/TaskControl.vue' import SubTitle from '@/views/dashboard/components/SubTitle.vue' import DashboardTitle from '@/views/dashboard/components/DashboardTitle.vue' import { usePLCStore } from '@/stores/plc' import { useDevicesStore } from '@/stores/devices' defineOptions({ name: 'DashboardView' @@ -131,6 +133,13 @@ function reloadAllData(task: Task) { tasksStore.reload(task.Channel) } const plcStore = usePLCStore() // 启动plc 轮询 plcStore.startPollingPLC() const deviceStore = useDevicesStore() deviceStore.startPollingDevice() </script> <style scoped lang="scss">