|
@@ -0,0 +1,342 @@
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, onMounted, computed } from 'vue'
|
|
|
|
|
+import { ElMessage, ElCard, ElRow, ElCol, ElStatistic, ElTable, ElTableColumn } from 'element-plus'
|
|
|
|
|
+import * as echarts from 'echarts'
|
|
|
|
|
+import { Zap, Building2, Hash, TrendingUp, Activity, BarChart3 } from 'lucide-vue-next'
|
|
|
|
|
+import { clientGet } from '@/utils/request.ts'
|
|
|
|
|
+
|
|
|
|
|
+interface BaseResponse {
|
|
|
|
|
+ code: number
|
|
|
|
|
+ msg: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface ElectricDataResponse extends BaseResponse {
|
|
|
|
|
+ data: {
|
|
|
|
|
+ dailyElectricity: number
|
|
|
|
|
+ totalElectricity: number
|
|
|
|
|
+ electricNumber: string
|
|
|
|
|
+ companyName: string
|
|
|
|
|
+ }[]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const electricData = ref<ElectricDataResponse['data']>([])
|
|
|
|
|
+const loading = ref(true)
|
|
|
|
|
+const chartInstance = ref<echarts.ECharts>()
|
|
|
|
|
+
|
|
|
|
|
+// 模拟API调用 - 替换为你的实际API
|
|
|
|
|
+const getData = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await clientGet<null, ElectricDataResponse>("/infrared/infraredReadingMeter/dailyAndTotalMap")
|
|
|
|
|
+
|
|
|
|
|
+ if (res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log(res.data)
|
|
|
|
|
+ electricData.value = res.data
|
|
|
|
|
+ initChart()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error('获取数据失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 计算统计数据
|
|
|
|
|
+const statistics = computed(() => {
|
|
|
|
|
+ const data = electricData.value
|
|
|
|
|
+ return {
|
|
|
|
|
+ totalCompanies: data.length,
|
|
|
|
|
+ totalDailyElectricity: data.reduce((sum, item) => sum + item.dailyElectricity, 0),
|
|
|
|
|
+ totalElectricitySum: data.reduce((sum, item) => sum + item.totalElectricity, 0),
|
|
|
|
|
+ avgDailyElectricity: data.length > 0 ? data.reduce((sum, item) => sum + item.dailyElectricity, 0) / data.length : 0
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 初始化图表
|
|
|
|
|
+const initChart = () => {
|
|
|
|
|
+ const chartDom = document.getElementById('electricChart')
|
|
|
|
|
+ if (!chartDom) return
|
|
|
|
|
+
|
|
|
|
|
+ chartInstance.value = echarts.init(chartDom)
|
|
|
|
|
+
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ text: '各公司用电量对比',
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ textStyle: {
|
|
|
|
|
+ color: '#333',
|
|
|
|
|
+ fontSize: 16,
|
|
|
|
|
+ fontWeight: 'bold'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item',
|
|
|
|
|
+ formatter: (params: any) => {
|
|
|
|
|
+ const dataIndex = params.dataIndex
|
|
|
|
|
+ const companyData = electricData.value[dataIndex]
|
|
|
|
|
+ // 处理公司名为空的情况
|
|
|
|
|
+ const companyName = companyData.companyName || '未知公司'
|
|
|
|
|
+ return `
|
|
|
|
|
+ <div style="padding: 12px; border-radius: 6px;">
|
|
|
|
|
+ <div style="font-weight: bold; margin-bottom: 8px; color: #333;">${companyName}</div>
|
|
|
|
|
+ <div style="margin-bottom: 4px;"><span style="color: #409EFF;">电表号:</span> ${companyData.electricNumber}</div>
|
|
|
|
|
+ <div style="margin-bottom: 4px;"><span style="color: #67C23A;">当日用电量:</span> ${companyData.dailyElectricity} kWh</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ `
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 让图表尽量占满
|
|
|
|
|
+ grid: {
|
|
|
|
|
+ top: 60,
|
|
|
|
|
+ left: '5%',
|
|
|
|
|
+ right: '5%',
|
|
|
|
|
+ bottom: '5%',
|
|
|
|
|
+ containLabel: true
|
|
|
|
|
+ },
|
|
|
|
|
+ // 用电量数值在 x 轴
|
|
|
|
|
+ xAxis: {
|
|
|
|
|
+ type: 'value',
|
|
|
|
|
+ name: '用电量 (kWh)',
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ formatter: '{value}'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // 公司名在 y 轴
|
|
|
|
|
+ yAxis: {
|
|
|
|
|
+ type: 'category',
|
|
|
|
|
+ data: electricData.value.map(item => item.companyName || '未知公司'),
|
|
|
|
|
+ axisLabel: {
|
|
|
|
|
+ fontSize: 12,
|
|
|
|
|
+ formatter: (value: string) => {
|
|
|
|
|
+ const maxLength = 8 // 每行最多 8 个字
|
|
|
|
|
+ if (value.length > maxLength) {
|
|
|
|
|
+ return value.match(new RegExp('.{1,' + maxLength + '}', 'g'))!.join('\n')
|
|
|
|
|
+ }
|
|
|
|
|
+ return value
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ series: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '当日用电量',
|
|
|
|
|
+ type: 'bar',
|
|
|
|
|
+ data: electricData.value.map(item => item.dailyElectricity),
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
|
|
|
|
+ { offset: 0, color: '#409EFF' },
|
|
|
|
|
+ { offset: 1, color: '#79bbff' }
|
|
|
|
|
+ ])
|
|
|
|
|
+ },
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
|
|
|
|
|
+ { offset: 0, color: '#337ecc' },
|
|
|
|
|
+ { offset: 1, color: '#409EFF' }
|
|
|
|
|
+ ])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ chartInstance.value.setOption(option)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getData()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="electricity-dashboard p-6 bg-gray-50 min-h-screen">
|
|
|
|
|
+ <!-- 页面标题 -->
|
|
|
|
|
+ <div class="mb-6">
|
|
|
|
|
+ <h1 class="text-3xl font-bold text-gray-800 flex items-center gap-3">
|
|
|
|
|
+ <Zap class="text-yellow-500" :size="32" />
|
|
|
|
|
+ 电表数据统计
|
|
|
|
|
+ </h1>
|
|
|
|
|
+ <p class="text-gray-600 mt-2">实时监控各公司用电情况</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 统计卡片 -->
|
|
|
|
|
+ <ElRow :gutter="20" class="mb-6">
|
|
|
|
|
+ <ElCol :xs="24" :sm="12" :md="6">
|
|
|
|
|
+ <ElCard class="stat-card">
|
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <ElStatistic
|
|
|
|
|
+ title="监控公司数量"
|
|
|
|
|
+ :value="statistics.totalCompanies"
|
|
|
|
|
+ suffix="家"
|
|
|
|
|
+ class="text-blue-600"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <Building2 class="text-blue-500" :size="40" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </ElCard>
|
|
|
|
|
+ </ElCol>
|
|
|
|
|
+
|
|
|
|
|
+ <ElCol :xs="24" :sm="12" :md="6">
|
|
|
|
|
+ <ElCard class="stat-card">
|
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <ElStatistic
|
|
|
|
|
+ title="今日总用电量"
|
|
|
|
|
+ :value="statistics.totalDailyElectricity"
|
|
|
|
|
+ :precision="1"
|
|
|
|
|
+ suffix="kWh"
|
|
|
|
|
+ class="text-green-600"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <Activity class="text-green-500" :size="40" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </ElCard>
|
|
|
|
|
+ </ElCol>
|
|
|
|
|
+
|
|
|
|
|
+ <ElCol :xs="24" :sm="12" :md="6">
|
|
|
|
|
+ <ElCard class="stat-card">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <ElStatistic
|
|
|
|
|
+ title="累计总用电量"
|
|
|
|
|
+ :value="statistics.totalElectricitySum"
|
|
|
|
|
+ :precision="1"
|
|
|
|
|
+ suffix="kWh"
|
|
|
|
|
+ class="text-purple-600"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <TrendingUp class="text-purple-500" :size="40" />
|
|
|
|
|
+ </ElCard>
|
|
|
|
|
+ </ElCol>
|
|
|
|
|
+
|
|
|
|
|
+ <ElCol :xs="24" :sm="12" :md="6">
|
|
|
|
|
+ <ElCard class="stat-card">
|
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <ElStatistic
|
|
|
|
|
+ title="日均用电量"
|
|
|
|
|
+ :value="statistics.avgDailyElectricity"
|
|
|
|
|
+ :precision="1"
|
|
|
|
|
+ suffix="kWh"
|
|
|
|
|
+ class="text-orange-600"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <BarChart3 class="text-orange-500" :size="40" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </ElCard>
|
|
|
|
|
+ </ElCol>
|
|
|
|
|
+ </ElRow>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图表区域 -->
|
|
|
|
|
+ <ElRow :gutter="20" class="mb-6">
|
|
|
|
|
+ <ElCol :span="24">
|
|
|
|
|
+ <ElCard>
|
|
|
|
|
+ <div id="electricChart" style="width: 100%; height: 400px;"></div>
|
|
|
|
|
+ </ElCard>
|
|
|
|
|
+ </ElCol>
|
|
|
|
|
+ </ElRow>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 数据表格 -->
|
|
|
|
|
+ <ElCard>
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
|
|
+ <Hash class="text-gray-600" :size="20" />
|
|
|
|
|
+ <span class="font-semibold">详细数据列表</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <ElTable
|
|
|
|
|
+ :data="electricData"
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ stripe
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ class="custom-table"
|
|
|
|
|
+ >
|
|
|
|
|
+ <ElTableColumn prop="companyName" label="公司名称" min-width="150">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
|
|
+ <Building2 class="text-blue-500" :size="16" />
|
|
|
|
|
+ <!-- 处理公司名为空的情况 -->
|
|
|
|
|
+ <span class="font-medium">{{ row.companyName || '未知公司' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ElTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ElTableColumn prop="electricNumber" label="电表号" min-width="120">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
|
|
+ <Hash class="text-gray-500" :size="16" />
|
|
|
|
|
+ <span class="font-mono text-sm">{{ row.electricNumber }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ElTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ElTableColumn prop="dailyElectricity" label="当日用电量 (kWh)" min-width="140" sortable>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
|
|
+ <Activity class="text-green-500" :size="16" />
|
|
|
|
|
+ <span class="font-semibold text-green-600">{{ row.dailyElectricity.toFixed(1) }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ElTableColumn>
|
|
|
|
|
+
|
|
|
|
|
+ <ElTableColumn prop="totalElectricity" label="总用电量 (kWh)" min-width="140" sortable>
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
|
|
+ <TrendingUp class="text-purple-500" :size="16" />
|
|
|
|
|
+ <span class="font-semibold text-purple-600">{{ row.totalElectricity.toLocaleString() }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </ElTableColumn>
|
|
|
|
|
+ </ElTable>
|
|
|
|
|
+ </ElCard>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.electricity-dashboard {
|
|
|
|
|
+ font-family: 'Inter', sans-serif;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-card {
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-card:hover {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.custom-table {
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-card__body) {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-statistic__content) {
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-statistic__title) {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table th) {
|
|
|
|
|
+ background-color: #f8fafc;
|
|
|
|
|
+ color: #374151;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+:deep(.el-table td) {
|
|
|
|
|
+ padding: 16px 0;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|