From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/iot/home/components/DeviceStateCountCard.vue |  163 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 163 insertions(+), 0 deletions(-)

diff --git a/src/views/iot/home/components/DeviceStateCountCard.vue b/src/views/iot/home/components/DeviceStateCountCard.vue
new file mode 100644
index 0000000..fbda0a9
--- /dev/null
+++ b/src/views/iot/home/components/DeviceStateCountCard.vue
@@ -0,0 +1,163 @@
+<template>
+  <el-card class="chart-card" shadow="never" :loading="loading">
+    <template #header>
+      <div class="flex items-center">
+        <span class="text-base font-medium text-gray-600">璁惧鐘舵�佺粺璁�</span>
+      </div>
+    </template>
+    <div v-if="loading && !hasData" class="h-[240px] flex justify-center items-center">
+      <el-empty description="鍔犺浇涓�..." />
+    </div>
+    <div v-else-if="!hasData" class="h-[240px] flex justify-center items-center">
+      <el-empty description="鏆傛棤鏁版嵁" />
+    </div>
+    <el-row v-else class="h-[240px]">
+      <el-col :span="8" class="flex flex-col items-center">
+        <div ref="deviceOnlineCountChartRef" 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="8" class="flex flex-col items-center">
+        <div ref="deviceOfflineChartRef" 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="8" class="flex flex-col items-center">
+        <div ref="deviceActiveChartRef" 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>
+</template>
+
+<script lang="ts" setup>
+import * as echarts from 'echarts/core'
+import { GaugeChart } from 'echarts/charts'
+import { CanvasRenderer } from 'echarts/renderers'
+import { IotStatisticsSummaryRespVO } from '@/api/iot/statistics'
+import type { PropType } from 'vue'
+
+/** 銆愯澶囩姸鎬併�戠粺璁″崱鐗� */
+defineOptions({ name: 'DeviceStateCountCard' })
+
+const props = defineProps({
+  statsData: {
+    type: Object as PropType<IotStatisticsSummaryRespVO>,
+    required: true
+  },
+  loading: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const deviceOnlineCountChartRef = ref()
+const deviceOfflineChartRef = ref()
+const deviceActiveChartRef = ref()
+
+/** 鏄惁鏈夋暟鎹� */
+const hasData = computed(() => {
+  if (!props.statsData) return false
+  return props.statsData.deviceCount !== -1
+})
+
+/** 鍒濆鍖栦华琛ㄧ洏鍥捐〃 */
+const initGaugeChart = (el: any, value: number, color: string) => {
+  // 纭繚 DOM 鍏冪礌瀛樺湪涓斿凡娓叉煋
+  if (!el) {
+    console.warn('鍥捐〃DOM鍏冪礌涓嶅瓨鍦�')
+    return
+  }
+
+  echarts.use([GaugeChart, CanvasRenderer])
+  try {
+    const chart = echarts.init(el)
+    chart.setOption({
+      series: [
+        {
+          type: 'gauge',
+          startAngle: 360,
+          endAngle: 0,
+          min: 0,
+          max: props.statsData.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 }]
+        }
+      ]
+    })
+    return chart
+  } catch (error) {
+    console.error('鍒濆鍖栧浘琛ㄥけ璐�:', error)
+    return null
+  }
+}
+
+/** 鍒濆鍖栨墍鏈夊浘琛� */
+const initCharts = () => {
+  // 濡傛灉娌℃湁鏁版嵁锛屽垯涓嶅垵濮嬪寲鍥捐〃
+  if (!hasData.value) return
+
+  // 浣跨敤 nextTick 纭繚 DOM 宸叉洿鏂�
+  nextTick(() => {
+    // 鍦ㄧ嚎璁惧缁熻
+    if (deviceOnlineCountChartRef.value) {
+      initGaugeChart(deviceOnlineCountChartRef.value, props.statsData.deviceOnlineCount, '#0d9')
+    }
+    // 绂荤嚎璁惧缁熻
+    if (deviceOfflineChartRef.value) {
+      initGaugeChart(deviceOfflineChartRef.value, props.statsData.deviceOfflineCount, '#f50')
+    }
+    // 寰呮縺娲昏澶囩粺璁�
+    if (deviceActiveChartRef.value) {
+      initGaugeChart(deviceActiveChartRef.value, props.statsData.deviceInactiveCount, '#05b')
+    }
+  })
+}
+
+/** 鐩戝惉鏁版嵁鍙樺寲 */
+watch(
+  () => props.statsData,
+  () => {
+    initCharts()
+  },
+  { deep: true }
+)
+
+/** 缁勪欢鎸傝浇鏃跺垵濮嬪寲鍥捐〃 */
+onMounted(() => {
+  initCharts()
+})
+</script>

--
Gitblit v1.8.0