ether-admin/src/views/energy/EnergyStatistics.vue

352 lines
9.3 KiB
Vue

<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue'
import { Button, Select, Space, message, Row, Col, Card, Statistic, Table, DatePicker } from 'ant-design-vue'
import type { ColumnsType } from 'ant-design-vue/es/table'
import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue'
import { getConsumptionByType, getUnitConsumption } from '@/api/energy'
import { getProjectSelectorList } from '@/api/project'
// 能源类型映射
const energyTypeMap: Record<string, { color: string; text: string }> = {
ELECTRICITY: { color: '#faad14', text: '电力' },
WATER: { color: '#1890ff', text: '水' },
GAS: { color: '#fa541c', text: '燃气' },
CENTRAL_HEATING: { color: '#f5222d', text: '集中供热' },
CENTRAL_COOLING: { color: '#13c2c2', text: '集中供冷' }
}
// 项目选择选项
const projectOptions = ref<{ value: string; label: string }[]>([])
// 查询参数
const queryParams = reactive({
projectId: '',
month: ''
})
// 数据状态
const loading = ref(false)
const byTypeData = ref<{ energyType: string; consumption: number; amount: number }[]>([])
const unitData = ref<{ indicatorName: string; value: number; unit: string }[]>([])
// 总计
const totalConsumption = ref(0)
const totalAmount = ref(0)
// 表格列定义(分项统计)
const byTypeColumns: ColumnsType = [
{ title: '能源类型', dataIndex: 'energyType', key: 'energyType', width: 150 },
{ title: '消耗量(kWh)', dataIndex: 'consumption', key: 'consumption', width: 150 },
{ title: '费用(元)', dataIndex: 'amount', key: 'amount', width: 150 },
{
title: '占比',
key: 'percentage',
width: 150,
customRender: ({ record }: { record: { consumption: number } }) => {
const pct = totalConsumption.value > 0
? ((record.consumption / totalConsumption.value) * 100).toFixed(1)
: '0.0'
return `${pct}%`
}
}
]
// 单方能耗列定义
const unitColumns: ColumnsType = [
{ title: '指标名称', dataIndex: 'indicatorName', key: 'indicatorName', width: 200 },
{ title: '指标值', dataIndex: 'value', key: 'value', width: 150 },
{ title: '单位', dataIndex: 'unit', key: 'unit', width: 100 }
]
// 获取项目列表
const fetchProjects = async () => {
try {
const res = await getProjectSelectorList()
projectOptions.value = (res.data.data || []).map((item: any) => ({
value: item.id,
label: item.name
}))
} catch {
message.error('获取项目列表失败')
}
}
// 获取月度格式化
const getCurrentMonth = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
return `${year}-${month}`
}
// 获取分项统计数据
const fetchByTypeData = async () => {
if (!queryParams.projectId || !queryParams.month) return
loading.value = true
try {
const res = await getConsumptionByType(queryParams.projectId, queryParams.month)
byTypeData.value = res.data.data || []
// 计算总计
totalConsumption.value = byTypeData.value.reduce((sum, item) => sum + item.consumption, 0)
totalAmount.value = byTypeData.value.reduce((sum, item) => sum + item.amount, 0)
} catch {
message.error('获取分项统计数据失败')
} finally {
loading.value = false
}
}
// 获取单方能耗数据
const fetchUnitData = async () => {
if (!queryParams.projectId || !queryParams.month) return
try {
const res = await getUnitConsumption(queryParams.projectId, queryParams.month)
unitData.value = res.data.data || []
} catch {
message.error('获取单方能耗数据失败')
}
}
// 搜索
const handleSearch = () => {
if (!queryParams.projectId) {
message.warning('请先选择项目')
return
}
if (!queryParams.month) {
message.warning('请选择月份')
return
}
fetchByTypeData()
fetchUnitData()
}
// 重置
const handleReset = () => {
queryParams.projectId = ''
queryParams.month = ''
byTypeData.value = []
unitData.value = []
totalConsumption.value = 0
totalAmount.value = 0
}
// 获取能源类型标签
const getEnergyTypeTag = (type: string) => {
return energyTypeMap[type] || { color: 'default', text: type }
}
onMounted(() => {
fetchProjects()
queryParams.month = getCurrentMonth()
})
</script>
<template>
<div class="page-container">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">能耗统计</h2>
</div>
<!-- 筛选区 -->
<div class="filter-bar">
<Space>
<Select
v-model:value="queryParams.projectId"
placeholder="请选择项目"
style="width: 240px"
allow-clear
:options="projectOptions"
/>
<DatePicker
v-model:value="queryParams.month"
picker="month"
placeholder="选择月份"
style="width: 140px"
:format="(value: any) => value ? value.format('YYYY-MM') : ''"
/>
<Button type="primary" @click="handleSearch">
<SearchOutlined /> 查询
</Button>
<Button @click="handleReset">
<ReloadOutlined /> 重置
</Button>
</Space>
</div>
<!-- 统计卡片 -->
<Row :gutter="16" style="margin-bottom: 24px">
<Col :span="6">
<Card>
<Statistic
title="月度总消耗"
:value="totalConsumption"
suffix="kWh"
:precision="2"
/>
</Card>
</Col>
<Col :span="6">
<Card>
<Statistic
title="月度总费用"
:value="totalAmount"
prefix="¥"
:precision="2"
/>
</Card>
</Col>
<Col :span="6">
<Card>
<Statistic
title="计量点数量"
:value="byTypeData.length"
/>
</Card>
</Col>
<Col :span="6">
<Card>
<Statistic
title="统计月份"
:value="queryParams.month || '-'"
/>
</Card>
</Col>
</Row>
<!-- 分项统计饼图区域 -->
<Row :gutter="24" style="margin-bottom: 24px">
<Col :span="24">
<Card title="分项能耗构成">
<!-- 简单饼图实现 -->
<div class="pie-container">
<div class="pie-chart">
<div
v-for="(item, index) in byTypeData"
:key="item.energyType"
class="pie-segment"
:style="{
'--percentage': `${(item.consumption / totalConsumption) * 100 || 0}%`,
'--color': getEnergyTypeTag(item.energyType).color,
'--index': index
}"
>
<div class="pie-label">
<span class="pie-color" :style="{ background: getEnergyTypeTag(item.energyType).color }"></span>
<span class="pie-text">{{ getEnergyTypeTag(item.energyType).text }}</span>
<span class="pie-value">{{ ((item.consumption / totalConsumption) * 100 || 0).toFixed(1) }}%</span>
</div>
</div>
</div>
</div>
</Card>
</Col>
</Row>
<Row :gutter="24">
<!-- 分项能耗表格 -->
<Col :span="12">
<Card title="分项能耗明细">
<a-table
:columns="byTypeColumns"
:data-source="byTypeData"
:loading="loading"
:row-key="(record: any) => record.energyType"
:pagination="false"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'energyType'">
<span :style="{ color: getEnergyTypeTag(record.energyType).color }">
{{ getEnergyTypeTag(record.energyType).text }}
</span>
</template>
</template>
</a-table>
<a-empty v-if="!queryParams.projectId || byTypeData.length === 0" description="请先选择项目和月份进行查询" />
</Card>
</Col>
<!-- 单方能耗指标 -->
<Col :span="12">
<Card title="单方能耗指标">
<a-table
:columns="unitColumns"
:data-source="unitData"
:loading="loading"
:row-key="(record: any) => record.indicatorName"
:pagination="false"
/>
<a-empty v-if="!queryParams.projectId || unitData.length === 0" description="请先选择项目和月份进行查询" />
</Card>
</Col>
</Row>
</div>
</template>
<style scoped>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.page-title {
margin: 0;
font-size: 20px;
font-weight: 500;
color: #262626;
}
.filter-bar {
margin-bottom: 24px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
/* 简单饼图样式 */
.pie-container {
padding: 20px 0;
}
.pie-chart {
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
}
.pie-segment {
display: flex;
align-items: center;
padding: 8px 16px;
border-left: 4px solid var(--color);
background: #fafafa;
border-radius: 4px;
min-width: 200px;
}
.pie-label {
display: flex;
align-items: center;
gap: 8px;
}
.pie-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
.pie-text {
font-weight: 500;
color: #262626;
}
.pie-value {
color: #8c8c8c;
margin-left: 8px;
}
</style>