| | |
| | | <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" |
| | |
| | | </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"> |
| | |
| | | '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> |
| | | <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-b">运行时间:1天1小时23分12秒</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" 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' |
| | | import type { TasksGroupByChannel } from '@/api/task' |
| | | import { isNumber } from 'lodash-es' |
| | | import { useInterval } from '@vueuse/core' |
| | | |
| | | 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}分` |
| | | } |
| | | |
| | | /** |
| | | * 获取某任务所在通道的运行中的任务 |
| | | * @param channelMap |
| | | * @param channelNumber |
| | | */ |
| | | function getChannelRunningTask(channelMap?: TasksGroupByChannel, channelNumber?: number) { |
| | | if (!channelMap || !isNumber(channelNumber)) { |
| | | return |
| | | } |
| | | |
| | | const channel = channelMap[channelNumber] |
| | | if (channel) { |
| | | const taskList = channel?.Tasks ?? [] |
| | | |
| | | return taskList.find((ele) => ele.Procedure.Status === 2) |
| | | } |
| | | } |
| | | |
| | | // 工序运行时间 |
| | | const taskStore = useTasksStore() |
| | | const { activeTask, channels } = storeToRefs(taskStore) |
| | | const { counter, reset } = useInterval(1000, { controls: true }) |
| | | const runningTime = computed(() => { |
| | | if (counter.value > 1000) { |
| | | // 拿一个 counter 定时触发工序运行时间的重新计算和渲染, 防止溢出需要重置 |
| | | reset() |
| | | } |
| | | // 如果当前选中的是已完成的任务, 使用当前选中任务来展示 |
| | | if (activeTask?.value?.Procedure?.Status === 3) { |
| | | return getTaskRunningTime(activeTask.value.Procedure?.realStartTime, activeTask.value.Procedure?.realEndTime) |
| | | } |
| | | // 如果当前选中的是运行中或者未开始的任务, 使用当前选中任务所在通道的正处于运行中的任务来展示 |
| | | const runningTask = getChannelRunningTask(channels?.value, activeTask?.value?.Channel) |
| | | return getTaskRunningTime(runningTask?.Procedure?.realStartTime, runningTask?.Procedure?.realEndTime) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | $status-dark-font: #efefef; |
| | | $status-off: red; |
| | | .device-status-info { |
| | | width: calc(50% - 5px); |
| | | width: 50%; |
| | | height: 150px; |
| | | line-height: 40px; |
| | | background: $status-done; |
| | | margin-top: 10px; |
| | | border-radius: 4px; |
| | | color: #fff; |
| | | border: 1px solid $status-border; |
| | | box-sizing: border-box; |
| | | overflow: hidden; |
| | | float: left; |
| | | text-align: center; |
| | | font-size: 15px; |
| | | |
| | | .duan { |
| | | color: $status-off; |
| | | font-size: 26px; |
| | |
| | | .color-one, |
| | | .color-two { |
| | | height: 100%; |
| | | width: 100%; |
| | | margin-right: 10px; |
| | | width: calc(100% - 10px); |
| | | margin-right: 20px; |
| | | background: $status-done; |
| | | border: 1px solid $status-border; |
| | | box-sizing: border-box; |
| | | border-radius: 4px; |
| | | padding-top: 10px; |
| | | .device-m { |
| | | width: 100%; |
| | | display: inline-block; |
| | |
| | | } |
| | | } |
| | | .color-two { |
| | | margin-left: 10px; |
| | | margin-right: 0px; |
| | | position: relative; |
| | | .device-info { |
| | | position: absolute; |
| | | top: 5px; |
| | | right: 5px; |
| | | padding: 0px 6px; |
| | | font-size: 12px; |
| | | line-height: 20px; |
| | | color: #fff; |
| | | border-radius: 8px; |
| | | &.master { |
| | | background: $status-running; |
| | | } |
| | | &.slave { |
| | | background-color: #3399ff; |
| | | } |
| | | } |
| | | .title-bng { |
| | | width: calc(90% - 1px); |
| | | margin: 10px auto 0; |
| | | height: 29px; |
| | | line-height: 29px; |
| | | font-size: 14px; |
| | | background: url('../../../assets/images/nodeNumber.png') no-repeat center center / cover; |
| | | img { |
| | | height: 16px; |
| | | display: inline-block; |
| | | float: left; |
| | | margin: 8px 5px 0 5px; |
| | | } |
| | | span { |
| | | height: 32px; |
| | | display: inline-block; |
| | | text-align: center; |
| | | display: inline-block; |
| | | float: left; |
| | | font-weight: bold; |
| | | } |
| | | i { |
| | | float: right; |
| | | margin-right: 15px; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |