Quellcode durchsuchen

瑞恒统计看板

lipenghui vor 3 Wochen
Ursprung
Commit
19230be648

+ 6 - 0
src/api/pms/stat/index.ts

@@ -5,6 +5,9 @@ export const IotStatApi = {
   getMainDay: async (params: any) => {
     return await request.get({ url: `/rq/stat/main/day` })
   },
+  getOrderSeven: async (params: any) => {
+    return await request.get({ url: `/rq/stat/rh/order` })
+  },
   getMainWeek: async (params: any) => {
     return await request.get({ url: `/rq/stat/main/week` })
   },
@@ -92,6 +95,9 @@ export const IotStatApi = {
   getSafeCount: async (params: any) => {
     return await request.get({ url: `/rq/stat/home/safe` })
   },
+  getMaterial: async (params: any) => {
+    return await request.get({ url: `/pms/iot-outbound/materials/top` })
+  },
   getDeptStatistics: async (params: any) => {
       return await request.get({ url: `/rq/iot-opeation-fill/getCount`, params })
   },

+ 1 - 0
src/utils/dict.ts

@@ -267,6 +267,7 @@ export enum DICT_TYPE {
   IOT_DATA_BRIDGE_TYPE_ENUM = 'iot_data_bridge_type_enum', // 桥梁类型
 
   // ========== PMS模块  ==========
+  PMS_INSPECT_WRITE = "inspect_wirte_normal",
   PMS_BOM_NODE_EXT_ATTR = 'BOM_NODE_EXT_ATTR', // BOM节点扩展属性 维护 or 保养
   PMS_MAIN_WORK_ORDER_TYPE = 'pms_main_work_order_type', // 保养工单类型
   PMS_MAIN_WORK_ORDER_RESULT = 'pms_main_work_order_result', // 保养工单状态

+ 1 - 0
src/views/Home/Index.vue

@@ -873,6 +873,7 @@ const initQxChart = () => {
   if (!qxRef.value) return
 
   const chartQxData = mockData()
+  debugger
 
   qxInstance = echarts.init(qxRef.value)
 

+ 1 - 1
src/views/pms/inspect/order/WriteOrder.vue

@@ -60,7 +60,7 @@
                     clearable
                   >
                     <el-option
-                      v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+                      v-for="dict in getBoolDictOptions(DICT_TYPE.PMS_INSPECT_WRITE)"
                       :key="dict.value"
                       :label="dict.label"
                       :value="dict.value"

+ 1 - 1
src/views/pms/stat/inspect.vue

@@ -808,7 +808,7 @@ const initChart = async () => {
 
   // 获取数据
   const { months, faults, repairs } = await fetchChartData()
-
+  debugger
   // ECharts配置
   const option = {
     tooltip: {

+ 713 - 0
src/views/pms/stat/rhkb.vue

@@ -0,0 +1,713 @@
+<template>
+  <!-- 第二行:图表行 -->
+  <el-row :gutter="16" class="mb-4">
+    <el-col :span="6">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center">
+            <span class="text-base font-medium text-gray-600">设备状态统计</span>
+          </div>
+        </template>
+        <div ref="statusChartRef" class="h-[290px]"></div>
+      </el-card>
+    </el-col>
+    <el-col :span="8">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center">
+            <span class="text-base font-medium text-gray-600">设备类别TOP5数量</span>
+          </div>
+        </template>
+        <div ref="topContainer" class="h-[290px]"></div>
+      </el-card>
+    </el-col>
+    <el-col :span="10">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium text-gray-600">当日运维成本</span>
+          </div>
+        </template>
+        <div ref="chartContainer" class="h-[290px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- 第三行:消息统计行 -->
+  <el-row :gutter="16" class="mb-1">
+    <el-col :span="6">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center">
+            <span class="text-base font-medium text-gray-600">近7天物料消耗TOP5</span>
+          </div>
+        </template>
+        <div ref="materialChartRef" class="h-[320px]"></div>
+      </el-card>
+    </el-col>
+    <el-col :span="18">
+      <el-card class="chart-card" shadow="never">
+        <template #header>
+          <div class="flex items-center justify-between">
+            <span class="text-base font-medium text-gray-600">工单数量情况</span>
+          </div>
+        </template>
+        <div ref="qxRef" class="h-[320px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- TODO 第四行:地图 -->
+</template>
+
+<script setup lang="ts" name="Index">
+import * as echarts from 'echarts/core'
+import { BarChart, GaugeChart, LineChart, PieChart } from 'echarts/charts' // 显式导入柱状图模块
+import {
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent
+} from 'echarts/components'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import { useElementSize } from '@vueuse/core'
+import {
+  IotStatisticsDeviceMessageSummaryRespVO,
+  IotStatisticsSummaryRespVO
+} from '@/api/iot/statistics'
+import { formatDate } from '@/utils/formatTime'
+import { IotStatApi } from '@/api/pms/stat'
+
+// TODO @super:参考下 /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue,拆一拆组件
+
+/** IoT 首页 */
+defineOptions({ name: 'IotRhStat' })
+
+// TODO @super:使用下 Echart 组件,参考 yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue 等
+echarts.use([
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+  TitleComponent,
+  ToolboxComponent,
+  GridComponent,
+  LineChart,
+  UniversalTransition,
+  GaugeChart,
+  BarChart
+])
+
+const dateRange = ref<[Date, Date] | null>(null)
+
+const queryParams = reactive({
+  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+  endTime: Date.now() // 设置默认结束时间为当前时间
+})
+const backendData = ref([])
+const statusChartRef = ref() // 设备数量统计的图表
+const materialChartRef = ref() // 设备数量统计的图表
+// 基础统计数据
+// TODO @super:初始为 -1,然后界面展示先是加载中?试试用 cursor 改哈
+const statsData = ref<IotStatisticsSummaryRespVO>({
+  productCategoryCount: 0,
+  productCount: 0,
+  deviceCount: 0,
+  deviceMessageCount: 0,
+  productCategoryTodayCount: 0,
+  productTodayCount: 0,
+  deviceTodayCount: 0,
+  deviceMessageTodayCount: 0,
+  deviceOnlineCount: 0,
+  deviceOfflineCount: 0,
+  deviceInactiveCount: 0,
+  productCategoryDeviceCounts: {}
+})
+
+const device = ref({
+  total: undefined,
+  today: undefined
+})
+const maintain = ref({
+  total: undefined,
+  today: undefined
+})
+const work = ref({
+  total: undefined,
+  today: undefined
+})
+const inspect = ref({
+  total: undefined,
+  today: undefined
+})
+
+const status = ref({
+  finished: 0,
+  todo: 0
+})
+const todayStatus = ref({
+  finished: 0,
+  todo: 0
+})
+const typeData = ref({})
+const materialData = ref({})
+const orderSevenData = ref({})
+const safe = ref()
+/** 获取统计数据 */
+const getStats = async () => {
+  // 获取基础统计数据
+  IotStatApi.getDeviceCount().then((res) => {
+    device.value = res
+  })
+  IotStatApi.getMaintainCount().then((res) => {
+    maintain.value = res
+  })
+  IotStatApi.getMainWorkCount().then((res) => {
+    work.value = res
+  })
+  IotStatApi.getInspectCount().then((res) => {
+    inspect.value = res
+  })
+  IotStatApi.getMaintenanceStatus().then((res) => {
+    status.value = res
+    initCharts()
+  })
+  IotStatApi.getMaintenanceTodayStatus().then((res) => {
+    todayStatus.value = res
+    initCharts()
+  })
+  IotStatApi.getDeviceStatusCount().then((res) => {
+    typeData.value = res
+    initCharts()
+  })
+  IotStatApi.getSafeCount().then((res) => {
+    safe.value = res
+  })
+  IotStatApi.getMaterial().then((res) => {
+    materialData.value = res
+    initCharts();
+  })
+  IotStatApi.getOrderSeven().then((res) => {
+    debugger
+    orderSevenData.value = res
+    initQxChart();
+  })
+}
+
+/** 初始化图表 */
+const initCharts = () => {
+  // 设备数量统计
+  echarts.init(statusChartRef.value).setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'horizontal',  // 水平排列图例项
+      bottom: '0%',         // 放置在底部
+      icon: 'circle'
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['50%', '44%'],
+        label: {
+          show: false,
+          position: 'outside'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: typeData.value
+      }
+    ]
+  })
+
+  echarts.init(materialChartRef.value).setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      // top: '5%',
+      // right: '10%',
+      // align: 'left',
+      // orient: 'vertical',
+      // icon: 'circle'
+      orient: 'horizontal',  // 水平排列图例项
+      bottom: '0%',         // 放置在底部
+      icon: 'circle'
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['50%', '44%'],
+        label: {
+          show: false,
+          position: 'outside'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 15,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: materialData.value
+      }
+    ]
+  })
+}
+
+/** 初始化消息统计图表 */
+const chartContainer = ref(null)
+let chartInstance = null
+
+// 生成过去12个月份的标签 (格式: YYYY-MM)
+const generateMonthLabels = () => {
+  const months = []
+  const date = new Date()
+  for (let i = 11; i >= 0; i--) {
+    const tempDate = new Date(date.getFullYear(), date.getMonth() - i, 1)
+    const year = tempDate.getFullYear()
+    const month = String(tempDate.getMonth() + 1).padStart(2, '0')
+    months.push(`${year}-${month}`)
+  }
+  return months
+}
+
+// 模拟数据获取
+const fetchChartData = async () => {
+  // 模拟异步请求
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve({
+        months: ['空压机','增压机','提纯撬'],
+        repairs: [10, 30, 90 ]
+      })
+    }, 300)
+  })
+}
+
+// 初始化图表配置
+const initChart = async () => {
+  if (!chartContainer.value) return
+
+  // 获取数据
+  const { months, faults, repairs } = await fetchChartData()
+  // const months = ['空压机','增压机','提纯撬']
+  // ECharts配置
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      },
+      formatter: (params) => {
+        return `${params[0].axisValue}<br/>
+                ${params[0].marker} ${params[0].seriesName}: ${params[0].value}`
+      }
+    },
+    legend: {
+      data: ['当日运维成本'],
+      top: 1
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '1%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: months,
+      axisLabel: {
+        rotate: 45,
+        margin: 15
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: {
+        formatter: (value) => Math.floor(value).toString()
+      }
+    },
+    series: [
+      {
+        name: '当日运维成本',
+        type: 'bar',
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#f69606' },
+          ])
+        },
+        emphasis: {
+          focus: 'series'
+        },
+        data: repairs
+      }
+    ]
+  }
+
+  // 初始化图表
+  chartInstance = echarts.init(chartContainer.value)
+  chartInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleResize)
+  handleResize()
+}
+
+// 自适应调整
+const handleResize = () => {
+  chartInstance?.resize()
+}
+
+const topContainer = ref(null)
+let topInstance = null
+// 响应式容器尺寸
+const { width, height } = useElementSize(topContainer)
+// 处理数据(排序+限制5条)
+const processedData = () => {
+  const data = IotStatApi.getDeviceTypeCount()
+  backendData.value = data
+  return [...backendData.value].sort((a, b) => a.value - b.value)
+}
+
+const fetchTop = () => {
+  IotStatApi.getDeviceTypeCount().then((res) => {
+    backendData.value = res
+  })
+}
+// 初始化图表配置
+const getTopOption = () => {
+  // backendData.value = data
+  const data = backendData.value.sort((a, b) => a.value - b.value)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' },
+      formatter: (params) => {
+        const item = params[0]
+        return `${item.name}<br/>${item.marker} ${item.value.toLocaleString()}`
+      }
+    },
+    grid: {
+      height: '200px',
+      left: '6%',
+      right: '6%',
+      bottom: '18%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'value',
+      axisLabel: {
+        formatter: (value) => {
+          if (value >= 10000) return `${(value / 10000).toFixed(1)}万`
+          return value.toLocaleString()
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      data: data.map((item) => item.category),
+      axisTick: { show: false },
+      axisLabel: {}
+    },
+    series: [
+      {
+        type: 'bar',
+        data: data.map((item) => item.value),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: '#83bff6' },
+            { offset: 0.7, color: '#188df0' },
+            { offset: 1, color: '#188df0' }
+          ]),
+          borderRadius: [0, 8, 8, 0]
+        },
+        label: {
+          show: true,
+          position: 'right',
+          formatter: '{@value}',
+          color: '#333',
+          fontWeight: 'bold'
+        },
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+}
+
+// 初始化图表
+const initTopChart = async () => {
+  await IotStatApi.getDeviceTypeCount().then((res) => {
+    backendData.value = res
+  })
+  if (!topContainer.value) return
+  topInstance = echarts.init(topContainer.value)
+  updateTopChart()
+}
+
+// 更新图表
+const updateTopChart = () => {
+  if (!topInstance) return
+  topInstance.setOption(getTopOption())
+}
+
+// 自适应调整
+watch([width, height], () => {
+  topInstance?.resize()
+})
+
+// 监听数据变化
+watch(
+  backendData,
+  () => {
+    updateTopChart()
+  },
+  { deep: true }
+)
+
+const activeDom = ref(null)
+let activeInstance = null
+
+const activeData = ref([])
+const initActiveChart = async () => {
+  if (!activeDom.value) return
+  activeData.value = await IotStatApi.getDeptCount()
+  activeInstance = echarts.init(activeDom.value)
+
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: { type: 'shadow' }, //:ml-citation{ref="1,7" data="citationList"}
+      formatter: (params) => `
+        ${params[0].name}<br/>
+        ${params[0].marker} 总人数: ${params[0].value}<br/>
+        ${params[1].marker} 活跃人数: ${params[1].value}
+      `
+    },
+    legend: {
+      data: ['总人数', '活跃人数'],
+      top: 30
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true //:ml-citation{ref="2,7" data="citationList"}
+    },
+    xAxis: {
+      type: 'category',
+      data: activeData.value.map((item) => item.department),
+      axisLabel: {
+        interval: 0,
+        rotate: 0 //:ml-citation{ref="5" data="citationList"}
+      }
+    },
+    yAxis: {
+      type: 'value',
+      name: '人数',
+      splitLine: {
+        show: true,
+        lineStyle: { type: 'dashed' }
+      }
+    },
+    series: [
+      {
+        name: '总人数',
+        type: 'bar',
+        data: activeData.value.map((item) => item.total),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#5470c6' },
+            { offset: 1, color: '#83bff6' }
+          ])
+        },
+        barWidth: 30
+      },
+      {
+        name: '活跃人数',
+        type: 'bar',
+        data: activeData.value.map((item) => item.active),
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#91cc75' },
+            { offset: 1, color: '#e6f4d2' }
+          ])
+        },
+        barWidth: 30
+      }
+    ]
+  }
+
+  activeInstance.setOption(option)
+}
+
+const qxRef = ref(null)
+let qxInstance = null
+
+// 生成近12个月份 (包含当年和去年)
+const generateMonths = () => {
+  const months = []
+  const date = new Date()
+  date.setMonth(date.getMonth() + 1, 1) // 从下个月开始倒推
+
+  for (let i = 0; i < 12; i++) {
+    date.setMonth(date.getMonth() - 1)
+    months.push(`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`)
+  }
+  return months.reverse()
+}
+
+const initQxChart = () => {
+  if (!qxRef.value) return
+debugger
+  qxInstance = echarts.init(qxRef.value)
+  debugger
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+    legend: {
+      data: orderSevenData.value.series.map((item) => item.name),
+      top: 30
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: orderSevenData.value.xAxis,
+      axisLabel: {
+        formatter: (value) => value.split('-').join('/') // 显示为 2023/01
+      }
+    },
+    // yAxis: {
+    //   type: 'value',
+    //   axisLabel: {
+    //     formatter: '{value}'
+    //   }
+    // },
+    yAxis: [
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: '{value}'
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: '{value}'
+        },
+        position: 'right', // 右侧 Y 轴
+        splitLine: {
+          show: false // 隐藏右侧 Y 轴的分割线
+        }
+      }
+    ],
+
+    // series: orderSevenData.value.series.map((item, index) => ({
+    //   name: item.name,
+    //   type: 'line',
+    //   smooth: true,
+    //   symbol: 'circle',
+    //   symbolSize: 8,
+    //   itemStyle: {
+    //     color: ['#5470c6', '#f1d209', '#e14f0f','#09a134'][index] // 蓝、绿、黄
+    //   },
+    //   areaStyle: {
+    //     color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+    //       { offset: 0, color: 'rgba(84,112,198,0.4)' },
+    //       { offset: 1, color: 'rgba(84,112,198,0.1)' }
+    //     ])
+    //   },
+    //   data: item.data
+    // }))
+    series: orderSevenData.value.series.map((item, index) => {
+      // 假设前两条曲线使用左侧 Y 轴,后两条曲线使用右侧 Y 轴
+      const yAxisIndex = index < 2 ? 0 : 1;
+      return {
+        name: item.name,
+        type: 'line',
+        smooth: true,
+        symbol: 'circle',
+        symbolSize: 8,
+        itemStyle: {
+          color: ['#5470c6', '#f1d209', '#e14f0f', '#91cc75'][index]
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(84,112,198,0.4)' },
+            { offset: 1, color: 'rgba(84,112,198,0.1)' }
+          ])
+        },
+        data: item.data,
+        yAxisIndex: yAxisIndex // 指定使用的 Y 轴
+      }
+    })
+  }
+
+  qxInstance.setOption(option)
+}
+
+// 响应式调整
+const resizeQxChart = () => qxInstance?.resize()
+
+/** 初始化 */
+onMounted(() => {
+  getStats()
+  initChart()
+  initTopChart()
+  initActiveChart()
+  initQxChart()
+  window.addEventListener('resize', resizeQxChart)
+  // fetchTop()
+  window.addEventListener('resize', () => topInstance?.resize())
+})
+onBeforeUnmount(() => {
+  chartInstance?.dispose()
+  window.removeEventListener('resize', () => chartInstance?.resize())
+  topInstance?.dispose()
+  window.removeEventListener('resize', handleResize)
+  qxInstance?.dispose()
+  window.removeEventListener('resize', resizeQxChart)
+})
+</script>
+
+<style lang="scss" scoped></style>