yangfeng
2023-12-12 1519870c0e18171ced014a840e86a459dc6b00f1
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,62 +30,156 @@
              '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">
@@ -97,20 +191,15 @@
$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;
@@ -128,8 +217,13 @@
  .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;
@@ -149,7 +243,51 @@
    }
  }
  .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>