lipenghui 3 luni în urmă
părinte
comite
eeaee53d9e

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

@@ -17,4 +17,43 @@ export const IotStatApi = {
   getMainStatus: async (params: any) => {
     return await request.get({ url: `/rq/stat/main/status` })
   },
+  getInspectDay: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/day` })
+  },
+  getInspectWeek: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/week` })
+  },
+  getInspectMonth: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/month` })
+  },
+  getInspectTotal: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/total` })
+  },
+  getInspectStatus: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/status` })
+  },
+  getInspectTodayStatus: async (params: any) => {
+    return await request.get({ url: `/rq/stat/inspect/today/status` })
+  },
+  getMaintenanceDay: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/day` })
+  },
+  getMaintenanceWeek: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/week` })
+  },
+  getMaintenanceMonth: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/month` })
+  },
+  getMaintenanceTotal: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/total` })
+  },
+  getMaintenanceStatus: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/status` })
+  },
+  getMaintenanceTodayStatus: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/today/status` })
+  },
+  getMaintenanceType: async (params: any) => {
+    return await request.get({ url: `/rq/stat/maintenance/type` })
+  },
 }

+ 699 - 0
src/views/pms/stat/inspect.vue

@@ -0,0 +1,699 @@
+<template>
+  <!-- 第一行:统计卡片行 -->
+  <el-row :gutter="16" class="mb-4">
+    <el-col :span="6">
+    <el-card class="stat-card" shadow="never">
+      <div class="flex flex-col">
+        <div class="flex justify-between items-center text-gray-400">
+          <span>昨日工单数量</span>
+          <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+        </div>
+        <el-divider />
+        <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+          <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+          >未完成</span
+          >
+        </div>
+        <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ day.total }}
+            </span>
+          <span class="text-3xl font-bold text-gray-700">
+              {{ day.todo }}
+            </span>
+        </div>
+        <!--          <el-divider class="my-2" />-->
+        <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+        <!--            <span>今日新增</span>-->
+        <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+        <!--          </div>-->
+      </div>
+    </el-card>
+    </el-col>
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>近一周工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ week.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ week.todo }}
+            </span>
+          </div>
+          <!--          <el-divider class="my-2" />-->
+          <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+          <!--            <span>今日新增</span>-->
+          <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+          <!--          </div>-->
+        </div>
+      </el-card>
+    </el-col>
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>近一月工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ month.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ month.todo }}
+            </span>
+          </div>
+          <!--          <el-divider class="my-2" />-->
+          <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+          <!--            <span>今日新增</span>-->
+          <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+          <!--          </div>-->
+        </div>
+      </el-card>
+    </el-col>
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ total.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ total.todo }}
+            </span>
+          </div>
+          <!--          <el-divider class="my-2" />-->
+          <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+          <!--            <span>今日新增</span>-->
+          <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+          <!--          </div>-->
+        </div>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- 第二行:图表行 -->
+  <el-row :gutter="16" class="mb-4">
+    <el-col :span="12">
+      <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>
+        <el-row class="h-[220px]">
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="writeTodayChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">待执行</span>
+            </div>
+          </el-col>
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="finishedTodayChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">已执行</span>
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+    </el-col>
+    <el-col :span="12">
+      <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>
+        <el-row class="h-[220px]">
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="writeChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">待执行</span>
+            </div>
+          </el-col>
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="finishedChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">已执行</span>
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- 第三行:消息统计行 -->
+  <el-row>
+    <el-col :span="24">
+      <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-[300px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- TODO 第四行:地图 -->
+</template>
+
+<script setup lang="ts" name="Index">
+import * as echarts from 'echarts/core'
+import { BarChart } from 'echarts/charts'; // 显式导入柱状图模块
+
+import {
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent
+} from 'echarts/components'
+import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+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: 'IoTHome' })
+
+// 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 timeRange = ref('7d') // 修改默认选择为近一周
+const dateRange = ref<[Date, Date] | null>(null)
+
+const queryParams = reactive({
+  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+  endTime: Date.now() // 设置默认结束时间为当前时间
+})
+
+// const deviceCountChartRef = ref() // 设备数量统计的图表
+const reportingChartRef = ref() // 在线设备统计的图表
+const dealFinishedChartRef = ref() // 离线设备统计的图表
+const transOrderChartRef = ref() // 待激活设备统计的图表
+const orderFinishChartRef = ref()
+const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
+const writeChartRef = ref() // 上下行消息量统计的图表
+const finishedChartRef = ref() // 上下行消息量统计的图表
+const writeTodayChartRef = ref() // 上下行消息量统计的图表
+const finishedTodayChartRef = 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 day = ref({
+  total: undefined,
+  todo: undefined
+})
+const week = ref({
+  total: undefined,
+  todo: undefined
+})
+const month = ref({
+  total: undefined,
+  todo: undefined
+})
+const total = ref({
+  total: undefined,
+  todo: undefined
+})
+
+const status = ref({
+    finished: 0,
+    todo: 0
+})
+const todayStatus = ref({
+  finished: 0,
+  todo: 0
+})
+// 消息统计数据
+const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
+  upstreamCounts: {},
+  downstreamCounts: {}
+})
+
+/** 处理快捷时间范围选择 */
+const handleTimeRangeChange = (timeRange: string) => {
+  const now = Date.now()
+  let startTime: number
+
+  // TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
+  switch (timeRange) {
+    case '1h':
+      startTime = now - 60 * 60 * 1000
+      break
+    case '24h':
+      startTime = now - 24 * 60 * 60 * 1000
+      break
+    case '7d':
+      startTime = now - 7 * 24 * 60 * 60 * 1000
+      break
+    default:
+      return
+  }
+
+  // 清空日期选择器
+  dateRange.value = null
+
+  // 更新查询参数
+  queryParams.startTime = startTime
+  queryParams.endTime = now
+
+  // 重新获取数据
+  getStats()
+}
+
+/** 处理自定义日期范围选择 */
+const handleDateRangeChange = (value: [Date, Date] | null) => {
+  if (value) {
+    // 清空快捷选项
+    timeRange.value = ''
+
+    // 更新查询参数
+    queryParams.startTime = value[0].getTime()
+    queryParams.endTime = value[1].getTime()
+
+    // 重新获取数据
+    getStats()
+  }
+}
+
+/** 获取统计数据 */
+const getStats = async () => {
+  // 获取基础统计数据
+  IotStatApi.getInspectDay().then((res) => {
+    day.value = res
+  })
+  IotStatApi.getInspectWeek().then((res) => {
+    week.value = res
+  })
+  IotStatApi.getInspectMonth().then((res) => {
+    month.value = res
+  })
+  IotStatApi.getInspectTotal().then((res) => {
+    total.value = res
+  })
+  IotStatApi.getInspectStatus().then((res) => {
+    status.value = res
+    initCharts()
+  })
+  IotStatApi.getInspectTodayStatus().then((res) => {
+    todayStatus.value = res
+    debugger
+    initCharts()
+  })
+  // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
+
+  //
+  // // 获取消息统计数据
+  // messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
+
+  // 初始化图表
+}
+
+/** 初始化图表 */
+const initCharts = () => {
+  //待执行
+  initGaugeChart(
+    writeTodayChartRef.value,
+    todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
+    '#05b'
+  )
+  //已执行
+  initGaugeChart(
+    finishedTodayChartRef.value,
+    todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
+    '#f50'
+  )
+  // 待执行
+  initGaugeChart(
+    writeChartRef.value,
+    status.value.todo === undefined ? 0 : status.value.todo,
+    '#05b'
+  )
+  //已执行
+  initGaugeChart(
+    finishedChartRef.value,
+    status.value.finished === undefined ? 0 : status.value.finished,
+    '#f50'
+  )
+  // 消息量统计
+  //initMessageChart()
+}
+
+/** 初始化仪表盘图表 */
+const initGaugeChart = (el: any, value: number, color: string) => {
+  echarts.init(el).setOption({
+    series: [
+      {
+        type: 'gauge',
+        startAngle: 360,
+        endAngle: 0,
+        min: 0,
+        max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
+        progress: {
+          show: true,
+          width: 12,
+          itemStyle: {
+            color: color
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            width: 12,
+            color: [[1, '#E5E7EB']]
+          }
+        },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+        pointer: { show: false },
+        anchor: { show: false },
+        title: { show: false },
+        detail: {
+          valueAnimation: true,
+          fontSize: 24,
+          fontWeight: 'bold',
+          fontFamily: 'Inter, sans-serif',
+          color: color,
+          offsetCenter: [0, '0'],
+          formatter: (value: number) => {
+            return `${value} `
+          }
+        },
+        data: [{ value: value }]
+      }
+    ]
+  })
+}
+
+/** 初始化消息统计图表 */
+const initMessageChart = () => {
+  // 获取所有时间戳并排序
+  // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
+  const timestamps = Array.from(
+    new Set([
+      ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
+      ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
+    ])
+  ).sort((a, b) => a - b) // 确保时间戳从小到大排序
+
+  // 准备数据
+  const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
+  const upData = timestamps.map((ts) => {
+    const item = messageStats.value.upstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+  const downData = timestamps.map((ts) => {
+    const item = messageStats.value.downstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+
+  // 配置图表
+  echarts.init(deviceMessageCountChartRef.value).setOption({
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#E5E7EB',
+      textStyle: {
+        color: '#374151'
+      }
+    },
+    legend: {
+      data: ['上行消息量', '下行消息量'],
+      textStyle: {
+        color: '#374151',
+        fontWeight: 500
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xdata,
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      },
+      splitLine: {
+        lineStyle: {
+          color: '#F3F4F6'
+        }
+      }
+    },
+    series: [
+      {
+        name: '上行消息量',
+        type: 'line',
+        smooth: true, // 添加平滑曲线
+        data: upData,
+        itemStyle: {
+          color: '#3B82F6'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
+            { offset: 1, color: 'rgba(59, 130, 246, 0)' }
+          ])
+        }
+      },
+      {
+        name: '下行消息量',
+        type: 'line',
+        smooth: true, // 添加平滑曲线
+        data: downData,
+        itemStyle: {
+          color: '#10B981'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
+            { offset: 1, color: 'rgba(16, 185, 129, 0)' }
+          ])
+        }
+      }
+    ]
+  })
+}
+
+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: generateMonthLabels(),
+        faults: [20,30,100,40,20,50,70,80,60,90,100,100],
+        repairs: [10,30,90,30,10,20,60,50,22,34,70,85],
+      })
+    }, 300)
+  })
+}
+
+// 初始化图表配置
+const initChart = async () => {
+  if (!chartContainer.value) return
+
+  // 获取数据
+  const { months, faults, repairs } = await fetchChartData()
+
+  // 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: 25
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      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: '#2196df' },
+            { offset: 1, color: '#2196df' }
+          ])
+        },
+        emphasis: {
+          focus: 'series'
+        },
+        data: repairs
+      }
+    ]
+  }
+
+  // 初始化图表
+  chartInstance = echarts.init(chartContainer.value)
+  chartInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleResize)
+  handleResize()
+}
+
+// 自适应调整
+const handleResize = () => {
+  chartInstance?.resize()
+}
+/** 初始化 */
+onMounted(() => {
+  getStats()
+  initChart()
+})
+onUnmounted(() => {
+  chartInstance?.dispose()
+  window.removeEventListener('resize', handleResize)
+})
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 43 - 17
src/views/pms/stat/maintain.vue

@@ -5,25 +5,17 @@
       <el-card class="stat-card" shadow="never">
         <div class="flex flex-col">
           <div class="flex justify-between items-center text-gray-400">
-            <span>昨日工单数量</span>
+            <span>MTTR</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-              >故障上报</span
-            >
-            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
-              >维修工单</span
-            >
-          </div>
-          <div class="flex justify-between items-center mb-1">
-            <span class="text-3xl font-bold text-gray-700">
-              {{ day.failureDay }}
-            </span>
-            <span class="text-3xl font-bold text-gray-700">
-              {{ day.maintainDay }}
-            </span>
+            <span class="text-gray-500 text-base font-medium">平均解决时间</span>
+<!--            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />-->
           </div>
+          <span class="text-3xl font-bold text-gray-700">
+            {{ statsData.productCategoryCount }}
+          </span>
           <!--          <el-divider class="my-2" />-->
           <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
           <!--            <span>今日新增</span>-->
@@ -32,11 +24,43 @@
         </div>
       </el-card>
     </el-col>
+<!--    <el-col :span="6">-->
+<!--      <el-card class="stat-card" shadow="never">-->
+<!--        <div class="flex flex-col">-->
+<!--          <div class="flex justify-between items-center text-gray-400">-->
+<!--            <span>昨日工单数量</span>-->
+<!--          </div>-->
+<!--          <el-divider />-->
+<!--          <div class="flex justify-between items-center mb-1">-->
+<!--            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"-->
+<!--            >故障上报</span-->
+<!--            >-->
+<!--            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"-->
+<!--            >维修工单</span-->
+<!--            >-->
+<!--          </div>-->
+<!--          <div class="flex justify-between items-center mb-1">-->
+<!--            <span class="text-3xl font-bold text-gray-700">-->
+<!--              {{ day.failureDay }}-->
+<!--            </span>-->
+<!--            <span class="text-3xl font-bold text-gray-700">-->
+<!--              {{ day.maintainDay }}-->
+<!--            </span>-->
+<!--          </div>-->
+<!--          &lt;!&ndash;          <el-divider class="my-2" />&ndash;&gt;-->
+<!--          &lt;!&ndash;          <div class="flex justify-between items-center text-gray-400 text-sm">&ndash;&gt;-->
+<!--          &lt;!&ndash;            <span>今日新增</span>&ndash;&gt;-->
+<!--          &lt;!&ndash;            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>&ndash;&gt;-->
+<!--          &lt;!&ndash;          </div>&ndash;&gt;-->
+<!--        </div>-->
+<!--      </el-card>-->
+<!--    </el-col>-->
     <el-col :span="6">
       <el-card class="stat-card" shadow="never">
         <div class="flex flex-col">
           <div class="flex justify-between items-center text-gray-400">
             <span>近一周工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
@@ -68,6 +92,7 @@
         <div class="flex flex-col">
           <div class="flex justify-between items-center text-gray-400">
             <span>近一月工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
@@ -99,6 +124,7 @@
         <div class="flex flex-col">
           <div class="flex justify-between items-center text-gray-400">
             <span>工单总数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
           </div>
           <el-divider />
           <div class="flex justify-between items-center mb-1">
@@ -666,8 +692,8 @@ const fetchChartData = async () => {
     setTimeout(() => {
       resolve({
         months: generateMonthLabels(),
-        faults: [100,100,1001,100,100,100,100,1001,100,100,100,100],
-        repairs: [100,100,1001,100,100,100,100,1001,100,100,100,100],
+        faults: [20,30,100,40,20,50,70,80,60,90,100,100],
+        repairs: [10,30,90,30,10,20,60,50,22,34,70,85],
       })
     }, 300)
   })

+ 750 - 0
src/views/pms/stat/maintenance.vue

@@ -0,0 +1,750 @@
+<template>
+  <!-- 第一行:统计卡片行 -->
+  <el-row :gutter="16" class="mb-4">
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>昨日工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ day.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ day.todo }}
+            </span>
+          </div>
+          <!--          <el-divider class="my-2" />-->
+          <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+          <!--            <span>今日新增</span>-->
+          <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+          <!--          </div>-->
+        </div>
+      </el-card>
+    </el-col>
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>近一周工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ week.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ week.todo }}
+            </span>
+          </div>
+        </div>
+      </el-card>
+    </el-col>
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>近一月工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ month.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ month.todo }}
+            </span>
+          </div>
+        </div>
+      </el-card>
+    </el-col>
+    <el-col :span="6">
+      <el-card class="stat-card" shadow="never">
+        <div class="flex flex-col">
+          <div class="flex justify-between items-center text-gray-400">
+            <span>工单数量</span>
+            <Icon icon="ep:menu" class="text-[32px] text-blue-400" />
+          </div>
+          <el-divider />
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >总数量</span
+            >
+            <span class="text-gray-500 text-base font-medium" style="font-size: 14px"
+            >未完成</span
+            >
+          </div>
+          <div class="flex justify-between items-center mb-1">
+            <span class="text-3xl font-bold text-gray-700">
+              {{ total.total }}
+            </span>
+            <span class="text-3xl font-bold text-gray-700">
+              {{ total.todo }}
+            </span>
+          </div>
+          <!--          <el-divider class="my-2" />-->
+          <!--          <div class="flex justify-between items-center text-gray-400 text-sm">-->
+          <!--            <span>今日新增</span>-->
+          <!--            <span class="text-green-500">+{{ statsData.productCategoryTodayCount }}</span>-->
+          <!--          </div>-->
+        </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">
+            <span class="text-base font-medium text-gray-600">保养工单状态统计</span>
+          </div>
+        </template>
+        <el-row class="h-[220px]">
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="writeChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">待执行</span>
+            </div>
+          </el-col>
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="finishedChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">已执行</span>
+            </div>
+          </el-col>
+        </el-row>
+      </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">工单类型统计</span>
+          </div>
+        </template>
+        <div ref="typeChartRef" class="h-[220px]"></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">今日工单状态统计</span>
+          </div>
+        </template>
+        <el-row class="h-[220px]">
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="writeTodayChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">待执行</span>
+            </div>
+          </el-col>
+          <el-col :span="12" class="flex flex-col items-center">
+            <div ref="finishedTodayChartRef" class="h-[160px] w-full"></div>
+            <div class="text-center mt-2">
+              <span class="text-sm text-gray-600">已执行</span>
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- 第三行:消息统计行 -->
+  <el-row>
+    <el-col :span="24">
+      <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-[300px]"></div>
+      </el-card>
+    </el-col>
+  </el-row>
+
+  <!-- TODO 第四行:地图 -->
+</template>
+
+<script setup lang="ts" name="Index">
+import * as echarts from 'echarts/core'
+import { BarChart } from 'echarts/charts'; // 显式导入柱状图模块
+
+import {
+  GridComponent,
+  LegendComponent,
+  TitleComponent,
+  ToolboxComponent,
+  TooltipComponent
+} from 'echarts/components'
+import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+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: 'IoTHome' })
+
+// 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 timeRange = ref('7d') // 修改默认选择为近一周
+const dateRange = ref<[Date, Date] | null>(null)
+
+const queryParams = reactive({
+  startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 设置默认开始时间为 7 天前
+  endTime: Date.now() // 设置默认结束时间为当前时间
+})
+
+const typeChartRef = ref() // 设备数量统计的图表
+const reportingChartRef = ref() // 在线设备统计的图表
+const dealFinishedChartRef = ref() // 离线设备统计的图表
+const transOrderChartRef = ref() // 待激活设备统计的图表
+const orderFinishChartRef = ref()
+const deviceMessageCountChartRef = ref() // 上下行消息量统计的图表
+const writeChartRef = ref() // 上下行消息量统计的图表
+const finishedChartRef = ref() // 上下行消息量统计的图表
+const writeTodayChartRef = ref() // 上下行消息量统计的图表
+const finishedTodayChartRef = 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 day = ref({
+  total: undefined,
+  todo: undefined
+})
+const week = ref({
+  total: undefined,
+  todo: undefined
+})
+const month = ref({
+  total: undefined,
+  todo: undefined
+})
+const total = ref({
+  total: undefined,
+  todo: undefined
+})
+
+const status = ref({
+  finished: 0,
+  todo: 0
+})
+const todayStatus = ref({
+  finished: 0,
+  todo: 0
+})
+const typeData = ref({})
+// 消息统计数据
+const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
+  upstreamCounts: {},
+  downstreamCounts: {}
+})
+
+/** 处理快捷时间范围选择 */
+const handleTimeRangeChange = (timeRange: string) => {
+  const now = Date.now()
+  let startTime: number
+
+  // TODO @super:这个的计算,看看能不能结合 dayjs 简化。因为 1h、24h、7d 感觉是比较标准的。如果没有,抽到 utils/formatTime.ts 作为一个工具方法
+  switch (timeRange) {
+    case '1h':
+      startTime = now - 60 * 60 * 1000
+      break
+    case '24h':
+      startTime = now - 24 * 60 * 60 * 1000
+      break
+    case '7d':
+      startTime = now - 7 * 24 * 60 * 60 * 1000
+      break
+    default:
+      return
+  }
+
+  // 清空日期选择器
+  dateRange.value = null
+
+  // 更新查询参数
+  queryParams.startTime = startTime
+  queryParams.endTime = now
+
+  // 重新获取数据
+  getStats()
+}
+
+/** 处理自定义日期范围选择 */
+const handleDateRangeChange = (value: [Date, Date] | null) => {
+  if (value) {
+    // 清空快捷选项
+    timeRange.value = ''
+
+    // 更新查询参数
+    queryParams.startTime = value[0].getTime()
+    queryParams.endTime = value[1].getTime()
+
+    // 重新获取数据
+    getStats()
+  }
+}
+
+/** 获取统计数据 */
+const getStats = async () => {
+  // 获取基础统计数据
+  IotStatApi.getMaintenanceDay().then((res) => {
+    day.value = res
+  })
+  IotStatApi.getMaintenanceWeek().then((res) => {
+    week.value = res
+  })
+  IotStatApi.getMaintenanceMonth().then((res) => {
+    month.value = res
+  })
+  IotStatApi.getMaintenanceTotal().then((res) => {
+    total.value = res
+  })
+  IotStatApi.getMaintenanceStatus().then((res) => {
+    debugger
+    status.value = res
+    initCharts()
+  })
+  IotStatApi.getMaintenanceTodayStatus().then((res) => {
+    todayStatus.value = res
+    debugger
+    initCharts()
+  })
+  IotStatApi.getMaintenanceType().then((res) => {
+    typeData.value = res
+    initCharts()
+  })
+  // IotStatApi.getInspectTodayStatus().then((res) => {
+  //   todayStatus.value = res
+  //   debugger
+  //   initCharts()
+  // })
+  // statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
+
+  //
+  // // 获取消息统计数据
+  // messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
+
+  // 初始化图表
+}
+
+/** 初始化图表 */
+const initCharts = () => {
+  // 设备数量统计
+  echarts.init(typeChartRef.value).setOption({
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      top: '5%',
+      right: '10%',
+      align: 'left',
+      orient: 'vertical',
+      icon: 'circle'
+    },
+    series: [
+      {
+        name: '',
+        type: 'pie',
+        radius: ['50%', '80%'],
+        avoidLabelOverlap: false,
+        center: ['30%', '50%'],
+        label: {
+          show: false,
+          position: 'outside'
+        },
+        emphasis: {
+          label: {
+            show: true,
+            fontSize: 20,
+            fontWeight: 'bold'
+          }
+        },
+        labelLine: {
+          show: false
+        },
+        data: Object.entries(typeData.value).map(([name, value]) => ({
+          name,
+          value
+        }))
+      }
+    ]
+  })
+  //待执行
+  initGaugeChart(
+    writeTodayChartRef.value,
+    todayStatus.value.todo === undefined ? 0 : todayStatus.value.todo,
+    '#05b'
+  )
+  //已执行
+  initGaugeChart(
+    finishedTodayChartRef.value,
+    todayStatus.value.finished === undefined ? 0 : todayStatus.value.finished,
+    '#f50'
+  )
+  // 待执行
+  initGaugeChart(
+    writeChartRef.value,
+    status.value.todo === undefined ? 0 : status.value.todo,
+    '#05b'
+  )
+  //已执行
+  initGaugeChart(
+    finishedChartRef.value,
+    status.value.finished === undefined ? 0 : status.value.finished,
+    '#f50'
+  )
+  // 消息量统计
+  //initMessageChart()
+}
+
+/** 初始化仪表盘图表 */
+const initGaugeChart = (el: any, value: number, color: string) => {
+  echarts.init(el).setOption({
+    series: [
+      {
+        type: 'gauge',
+        startAngle: 360,
+        endAngle: 0,
+        min: 0,
+        max: statsData.value.deviceCount || 100, // 使用设备总数作为最大值
+        progress: {
+          show: true,
+          width: 12,
+          itemStyle: {
+            color: color
+          }
+        },
+        axisLine: {
+          lineStyle: {
+            width: 12,
+            color: [[1, '#E5E7EB']]
+          }
+        },
+        axisTick: { show: false },
+        splitLine: { show: false },
+        axisLabel: { show: false },
+        pointer: { show: false },
+        anchor: { show: false },
+        title: { show: false },
+        detail: {
+          valueAnimation: true,
+          fontSize: 24,
+          fontWeight: 'bold',
+          fontFamily: 'Inter, sans-serif',
+          color: color,
+          offsetCenter: [0, '0'],
+          formatter: (value: number) => {
+            return `${value} `
+          }
+        },
+        data: [{ value: value }]
+      }
+    ]
+  })
+}
+
+/** 初始化消息统计图表 */
+const initMessageChart = () => {
+  // 获取所有时间戳并排序
+  // TODO @super:一些 idea 里的红色报错,要去处理掉噢。
+  const timestamps = Array.from(
+    new Set([
+      ...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
+      ...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
+    ])
+  ).sort((a, b) => a - b) // 确保时间戳从小到大排序
+
+  // 准备数据
+  const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
+  const upData = timestamps.map((ts) => {
+    const item = messageStats.value.upstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+  const downData = timestamps.map((ts) => {
+    const item = messageStats.value.downstreamCounts.find(
+      (count) => Number(Object.keys(count)[0]) === ts
+    )
+    return item ? Object.values(item)[0] : 0
+  })
+
+  // 配置图表
+  echarts.init(deviceMessageCountChartRef.value).setOption({
+    tooltip: {
+      trigger: 'axis',
+      backgroundColor: 'rgba(255, 255, 255, 0.9)',
+      borderColor: '#E5E7EB',
+      textStyle: {
+        color: '#374151'
+      }
+    },
+    legend: {
+      data: ['上行消息量', '下行消息量'],
+      textStyle: {
+        color: '#374151',
+        fontWeight: 500
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: xdata,
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        lineStyle: {
+          color: '#E5E7EB'
+        }
+      },
+      axisLabel: {
+        color: '#6B7280'
+      },
+      splitLine: {
+        lineStyle: {
+          color: '#F3F4F6'
+        }
+      }
+    },
+    series: [
+      {
+        name: '上行消息量',
+        type: 'line',
+        smooth: true, // 添加平滑曲线
+        data: upData,
+        itemStyle: {
+          color: '#3B82F6'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
+            { offset: 1, color: 'rgba(59, 130, 246, 0)' }
+          ])
+        }
+      },
+      {
+        name: '下行消息量',
+        type: 'line',
+        smooth: true, // 添加平滑曲线
+        data: downData,
+        itemStyle: {
+          color: '#10B981'
+        },
+        lineStyle: {
+          width: 2
+        },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
+            { offset: 1, color: 'rgba(16, 185, 129, 0)' }
+          ])
+        }
+      }
+    ]
+  })
+}
+
+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: generateMonthLabels(),
+        faults: [20,30,100,40,20,50,70,80,60,90,100,100],
+        repairs: [10,30,90,30,10,20,60,50,22,34,70,85],
+      })
+    }, 300)
+  })
+}
+
+// 初始化图表配置
+const initChart = async () => {
+  if (!chartContainer.value) return
+
+  // 获取数据
+  const { months, faults, repairs } = await fetchChartData()
+
+  // 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: 25
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      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: '#2196df' },
+            { offset: 1, color: '#2196df' }
+          ])
+        },
+        emphasis: {
+          focus: 'series'
+        },
+        data: repairs
+      }
+    ]
+  }
+
+  // 初始化图表
+  chartInstance = echarts.init(chartContainer.value)
+  chartInstance.setOption(option)
+
+  // 窗口缩放监听
+  window.addEventListener('resize', handleResize)
+  handleResize()
+}
+
+// 自适应调整
+const handleResize = () => {
+  chartInstance?.resize()
+}
+/** 初始化 */
+onMounted(() => {
+  getStats()
+  initChart()
+})
+onUnmounted(() => {
+  chartInstance?.dispose()
+  window.removeEventListener('resize', handleResize)
+})
+</script>
+
+<style lang="scss" scoped>
+</style>