haoxuan
2023-11-02 3a4e0e2208a48c43639cdd4a7ef883a11e747fbf
Merge branch 'dev' of http://192.168.5.5:10010/r/web/bulletin-board-style1 into wn
1个文件已添加
13个文件已修改
411 ■■■■■ 已修改文件
package-lock.json 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/device.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/plc.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/devices.ts 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/plc.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/tasks.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/DashboardTitle.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/DeviceNumberInfo.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/DeviceStatusInfo.vue 154 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/ProcessingInfo.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/TaskControlModal.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/components/TroubleTrackerModal.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dashboard/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | 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">