352 lines
9.3 KiB
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> |