瀏覽代碼

瑞都统计看板

lipenghui 3 周之前
父節點
當前提交
29af8a2f45
共有 2 個文件被更改,包括 617 次插入1 次删除
  1. 1 1
      src/components/SummaryCard/index.vue
  2. 616 0
      src/views/pms/stat/rdkb.vue

+ 1 - 1
src/components/SummaryCard/index.vue

@@ -14,7 +14,7 @@
         </el-tooltip>
       </div>
       <div class="flex flex-row items-baseline gap-2">
-        <div class="text-7">
+        <div class="text-4">
           <CountTo :prefix="prefix" :end-val="value" :decimals="decimals" />
         </div>
         <span

+ 616 - 0
src/views/pms/stat/rdkb.vue

@@ -0,0 +1,616 @@
+<template>
+  <div class="flex flex-col">
+    <el-row :gutter="16" class="summary">
+      <!-- 原有的统计卡片部分保持不变 -->
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="summary?.userCount || 0"
+          icon="fa-solid:project-diagram"
+          icon-bg-color="text-blue-500"
+          icon-color="bg-blue-100"
+          title="设备数"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="summary?.userCount || 0"
+          icon="fa-solid:list"
+          icon-bg-color="text-blue-500"
+          icon-color="bg-blue-100"
+          title="维修工单"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="summary?.rechargeUserCount || 0"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-purple-500"
+          icon-color="bg-purple-100"
+          title="未填写运行记录"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="fenToYuan(summary?.rechargePrice || 0)"
+          icon="fa-solid:award"
+          icon-bg-color="text-purple-500"
+          icon-color="bg-purple-100"
+          title="已填写运行记录"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="fenToYuan(summary?.expensePrice || 0)"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-green-500"
+          icon-color="bg-green-100"
+          title="未执行保养工单"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="fenToYuan(summary?.expensePrice || 0)"
+          icon="fa-solid:award"
+          icon-bg-color="text-green-500"
+          icon-color="bg-green-100"
+          title="已执行保养工单"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="fenToYuan(summary?.expensePrice || 0)"
+          icon="fa-solid:times-circle"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          title="待填写巡检工单"
+        />
+      </el-col>
+      <el-col v-loading="loading" :sm="3" :xs="12">
+        <SummaryCard
+          :value="fenToYuan(summary?.expensePrice || 0)"
+          icon="fa-solid:award"
+          icon-bg-color="text-yellow-500"
+          icon-color="bg-yellow-100"
+          title="已填写巡检工单"
+        />
+      </el-col>
+      <!-- 其他统计卡片... -->
+    </el-row>
+    <el-row :gutter="16" class="mb-4">
+      <!-- 设备状态统计和工单数量情况图表部分保持不变 -->
+      <el-col :span="7">
+        <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="17">
+        <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-[290px]"></div>
+        </el-card>
+      </el-col>
+    </el-row>
+    <el-row :gutter="16" class="mb-4">
+      <!-- 备件更换情况部分保持不变 -->
+      <el-col :span="8">
+        <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 class="flex justify-between mb-2">
+            <el-card class="stat-card " >
+              <div class="flex flex-row justify-evenly">
+                <div>
+                  <Icon icon="fa-solid:award" size="30" color="blue"/>
+                </div>
+                <div class="flex flex-col items-center">
+                  <span class="text-sm text-gray-600">物料消耗数量</span>
+                  <span class="text-lg font-bold">{{ totalMaterialCount }}</span>
+                </div>
+              </div>
+            </el-card>
+            <el-card class="stat-card">
+              <div class="flex flex-row justify-evenly">
+                <div>
+                  <Icon icon="fa-solid:yen-sign" size="30" color="orange"/>
+                </div>
+                <div class="flex flex-col items-center">
+                  <span class="text-sm text-gray-600">物料消耗费用</span>
+                  <span class="text-lg font-bold">{{ totalMaterialCost }}</span>
+                </div>
+              </div>
+            </el-card>
+          </div>
+          <div ref="sparePartRef" class="h-[330px]"></div>
+        </el-card>
+      </el-col>
+      <el-col :span="16">
+        <div class="flex flex-col justify-between">
+          <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 class="flex mr-3">
+              <div ref="maintenanceChartRef" class="h-[157px] w-2/5"></div>
+              <div class="w-3/5 p-4 flex flex-col mt-5">
+                <div class="flex justify-between">
+                  <div class="flex flex-col items-center">
+                    <Icon icon="fa-solid:list" size="20" color="gray"/>
+                    <p>总工单数</p>
+                    <span>{{ totalMaintenanceOrders }}</span>
+                  </div>
+                  <div class="flex flex-col items-center">
+                    <Icon icon="fa-solid:check-circle" size="20" color="green"/>
+                    <p>已执行工单数</p>
+                    <span>{{ completedMaintenanceOrders }}</span>
+                  </div>
+                  <div class="flex flex-col items-center">
+                    <Icon icon="fa-solid:hourglass-half" size="20" color="orange"/>
+                    <p>待执行工单数 </p>
+                    <span>{{ pendingMaintenanceOrders }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-card>
+          <el-card class="chart-card mt-2" shadow="never">
+            <template #header>
+              <div class="flex items-center justify-between">
+                <span class="text-base font-medium text-gray-600">巡检情况</span>
+              </div>
+            </template>
+            <div class="flex mr-3">
+              <div ref="maintenanceChartRef1" class="h-[157px] w-2/5"></div>
+              <div class="w-3/5 p-4 flex flex-col mt-5">
+                <div class="flex justify-between">
+                  <div class="flex flex-col items-center">
+                    <Icon icon="fa-solid:list" size="20" color="gray"/>
+                    <p>总工单数</p>
+                    <span>{{ totalMaintenanceOrders }}</span>
+                  </div>
+                  <div class="flex flex-col items-center">
+                    <Icon icon="fa-solid:check-circle" size="20" color="green"/>
+                    <p>已执行工单数</p>
+                    <span>{{ completedMaintenanceOrders }}</span>
+                  </div>
+                  <div class="flex flex-col items-center">
+                    <Icon icon="fa-solid:hourglass-half" size="20" color="orange"/>
+                    <p>待执行工单数</p>
+                    <span>{{ pendingMaintenanceOrders }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </el-card>
+        </div>
+      </el-col>
+      <el-col :span="24">
+        <el-card class="chart-card mt-1" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-base font-medium text-gray-600">维保日历</span>
+            </div>
+          </template>
+          <el-calendar v-model="currentDate">
+            <template #dateCell="{ data }">
+              <div class="calendar-cell">
+                <span>{{ data.day.split('-').pop() }}</span>
+                <div :ref="el => calendarPieRefs[data.day] = el" class="calendar-pie"></div>
+              </div>
+            </template>
+          </el-calendar>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script lang="ts" setup>
+import { MemberSummaryRespVO } from '@/api/mall/statistics/member'
+import SummaryCard from '@/components/SummaryCard/index.vue'
+import { fenToYuan } from '@/utils'
+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'
+import { ref, onMounted, computed, watch, nextTick } from 'vue';
+import { ElCalendar } from 'element-plus';
+
+/** 会员统计 */
+defineOptions({ name: 'IotRdStat' })
+
+echarts.use([
+  TooltipComponent,
+  LegendComponent,
+  PieChart,
+  CanvasRenderer,
+  LabelLayout,
+  TitleComponent,
+  ToolboxComponent,
+  GridComponent,
+  LineChart,
+  UniversalTransition,
+  GaugeChart,
+  BarChart
+])
+
+const loading = ref(true) // 加载中
+const summary = ref<MemberSummaryRespVO>() // 会员统计数据
+const statusChartRef = ref() // 设备数量统计的图表
+const qxRef = ref(null)
+let qxInstance = null
+const sparePartRef = ref(null)
+let sparePartInstance = null
+const maintenanceChartRef = ref(null)
+let maintenanceChartInstance = null
+const inspectionChartRef = ref(null)
+let inspectionChartInstance = null
+const maintenanceChartRef1 = ref(null)
+let maintenanceChartInstance1 = null
+
+const typeData = ref({})
+const orderSevenData = ref({})
+const sparePartData = ref({
+  xAxis: ['扳手', '水杯', '皮带', '螺丝'],
+  series: [
+    {
+      name: '数量',
+      type: 'bar',
+      data: [10, 20, 15, 25],
+      yAxisIndex: 0
+    },
+    {
+      name: '金额',
+      type: 'line',
+      data: [100, 200, 150, 250],
+      yAxisIndex: 1
+    }
+  ]
+})
+
+// 模拟保养工单数据
+const totalMaintenanceOrders = ref(100)
+const completedMaintenanceOrders = ref(80)
+const pendingMaintenanceOrders = ref(20)
+
+// 模拟巡检工单数据
+const totalInspectionOrders = ref(80)
+const completedInspectionOrders = ref(60)
+const pendingInspectionOrders = ref(20)
+
+// 计算物料消耗数量及费用
+const totalMaterialCount = computed(() => {
+  const quantitySeries = sparePartData.value.series.find(item => item.name === '数量');
+  return quantitySeries ? quantitySeries.data.reduce((sum, val) => sum + val, 0) : 0;
+});
+
+const totalMaterialCost = computed(() => {
+  const costSeries = sparePartData.value.series.find(item => item.name === '金额');
+  return costSeries ? costSeries.data.reduce((sum, val) => sum + val, 0) : 0;
+});
+
+// 维保日历相关
+const currentDate = ref(new Date())
+const calendarPieRefs = ref({})
+const maintenanceData = ref({
+  // 示例数据,需要根据实际情况替换
+  '2024-01-01': { maintenance: 5, inspection: 3 },
+  '2024-01-02': { maintenance: 2, inspection: 4 },
+  // ... 其他日期数据
+})
+
+const getStats = () => {
+  IotStatApi.getDeviceStatusCount().then((res) => {
+    typeData.value = res
+    initDeviceStatusCharts()
+  })
+  IotStatApi.getOrderSeven().then((res) => {
+    orderSevenData.value = res
+    initQxChart()
+  })
+  initSparePartChart()
+  initMaintenanceChart()
+  initInspectionChart()
+  nextTick(() => {
+    initCalendarPieCharts()
+  })
+}
+
+const initQxChart = () => {
+  if (!qxRef.value) return
+  qxInstance = echarts.init(qxRef.value)
+  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}'
+        },
+        position: 'left' // 左侧 Y 轴
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: '{value}'
+        },
+        position: 'right', // 右侧 Y 轴
+        splitLine: {
+          show: false // 隐藏右侧 Y 轴的分割线
+        }
+      }
+    ],
+    series: orderSevenData.value.series.map((item, index) => {
+      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 initSparePartChart = () => {
+  if (!sparePartRef.value) return
+  sparePartInstance = echarts.init(sparePartRef.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross',
+        crossStyle: {
+          color: '#999'
+        }
+      }
+    },
+    legend: {
+      data: ['数量', '金额']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: sparePartData.value.xAxis
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '数量',
+        position: 'left',
+        axisLabel: {
+          formatter: '{value}'
+        }
+      },
+      {
+        type: 'value',
+        name: '金额',
+        position: 'right',
+        axisLabel: {
+          formatter: '{value}'
+        },
+        splitLine: {
+          show: false
+        }
+      }
+    ],
+    series: sparePartData.value.series.map((item, index) => ({
+      name: item.name,
+      type: item.type,
+      data: item.data,
+      yAxisIndex: item.yAxisIndex,
+      itemStyle: {
+        color: ['#6084fb', '#5aef13'][index]
+      },
+    }))
+  }
+
+  sparePartInstance.setOption(option)
+}
+
+const initMaintenanceChart = () => {
+  if (!maintenanceChartRef.value) return
+  maintenanceChartInstance = echarts.init(maintenanceChartRef.value)
+  const completionRate = (completedMaintenanceOrders.value / totalMaintenanceOrders.value) * 100
+  const option = {
+    series: [
+      {
+        type: 'pie',
+        radius: ['40%', '70%'],
+        data: [
+          { value: completionRate, name: '完成率' },
+          { value: 100 - completionRate, name: '未完成率' }
+        ]
+      }
+    ]
+  }
+  maintenanceChartInstance.setOption(option)
+}
+
+const initInspectionChart = () => {
+  if (!inspectionChartRef.value) return
+  inspectionChartInstance = echarts.init(inspectionChartRef.value)
+  const completionRate = (completedInspectionOrders.value / totalInspectionOrders.value) * 100
+  const option = {
+    series: [
+      {
+        type: 'pie',
+        radius: ['50%', '80%'],
+        data: [
+          { value: completionRate, name: '完成率' },
+          { value: 100 - completionRate, name: '未完成率' }
+        ]
+      }
+    ]
+  }
+  inspectionChartInstance.setOption(option)
+}
+
+/** 初始化图表 */
+const initDeviceStatusCharts = () => {
+  // 设备数量统计
+  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
+      }
+    ]
+  })
+}
+
+const initCalendarPieCharts = () => {
+  Object.entries(calendarPieRefs.value).forEach(([date, el]) => {
+    if (el) {
+      const { maintenance = 0, inspection = 0 } = maintenanceData.value[date] || {}
+      const total = maintenance + inspection
+      const option = {
+        series: [
+          {
+            type: 'pie',
+            radius: ['30%', '60%'],
+            data: [
+              { value: maintenance, name: '保养' },
+              { value: inspection, name: '巡检' }
+            ]
+          }
+        ]
+      }
+      echarts.init(el).setOption(option)
+    }
+  })
+}
+
+watch(currentDate, () => {
+  nextTick(() => {
+    initCalendarPieCharts()
+  })
+})
+
+/** 初始化 **/
+onMounted(async () => {
+  loading.value = true
+  await Promise.all([getStats()])
+  loading.value = false
+})
+</script>
+<style lang="scss" scoped>
+.summary {
+  .el-col {
+    margin-bottom: 1rem;
+  }
+}
+.stat-card {
+  width: 48%;
+}
+.calendar-cell {
+  position: relative;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+.calendar-pie {
+  width: 100%;
+  height: 80px;
+}
+</style>