|
@@ -1,435 +1,718 @@
|
|
|
<template>
|
|
|
<div class="app-container">
|
|
|
- <el-tabs v-model="activeName" @tab-click="tabClick">
|
|
|
+ <!-- Tab切换 -->
|
|
|
+ <el-tabs v-model="activeName" @tab-click="handleTabClick">
|
|
|
<el-tab-pane label="区域用能" name="areaConsume">
|
|
|
</el-tab-pane>
|
|
|
<el-tab-pane label="设施用能" name="facsConsume">
|
|
|
</el-tab-pane>
|
|
|
</el-tabs>
|
|
|
+
|
|
|
<el-row :gutter="20">
|
|
|
+ <!-- 左侧树形选择器 -->
|
|
|
<el-col :span="4" :xs="24">
|
|
|
<div class="head-container">
|
|
|
- <el-input v-model="areaName" placeholder="请输入区域名称" clearable size="small" prefix-icon="el-icon-search"
|
|
|
- style="margin-bottom: 20px"
|
|
|
+ <el-input
|
|
|
+ v-model="filterText"
|
|
|
+ placeholder="请输入区域名称"
|
|
|
+ clearable
|
|
|
+ size="small"
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
+ style="margin-bottom: 20px"
|
|
|
/>
|
|
|
</div>
|
|
|
- <div class="head-container" style="height: 100vh; overflow: hidden; position: relative;">
|
|
|
- <el-tree :data="objOptions" :props="defaultProps" :expand-on-click-node="false"
|
|
|
- :filter-node-method="filterNode" ref="tree" node-key="id" default-expand-all highlight-current
|
|
|
- @node-click="handleNodeClick" style="height: calc(100vh - 50px); overflow-y: auto;"
|
|
|
+ <div class="head-container tree-container">
|
|
|
+ <el-tree
|
|
|
+ :data="treeOptions"
|
|
|
+ :props="defaultProps"
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ :filter-node-method="filterNode"
|
|
|
+ ref="tree"
|
|
|
+ node-key="id"
|
|
|
+ default-expand-all
|
|
|
+ highlight-current
|
|
|
+ @node-click="handleNodeClick"
|
|
|
/>
|
|
|
</div>
|
|
|
</el-col>
|
|
|
+
|
|
|
+ <!-- 右侧内容区域 -->
|
|
|
<el-col :span="20" :xs="24">
|
|
|
- <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="68px">
|
|
|
- <!-- 开始时间选择器 -->
|
|
|
+ <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
|
|
|
+ <!-- 时间范围选择 -->
|
|
|
<el-form-item label="开始时间" prop="startRecTime">
|
|
|
<el-date-picker
|
|
|
- v-model="queryParams.startRecTime"
|
|
|
- type="datetime"
|
|
|
- value-format="yyyy-MM-dd HH:00:00"
|
|
|
- :picker-options="startPickerOptions"
|
|
|
- placeholder="请选择开始时间"
|
|
|
- @change="handleTimeChange('startRecTime')"
|
|
|
- >
|
|
|
- </el-date-picker>
|
|
|
+ v-model="queryParams.startRecTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ :picker-options="startPickerOptions"
|
|
|
+ placeholder="请选择开始时间"
|
|
|
+ @change="handleTimeChange('startRecTime')"
|
|
|
+ />
|
|
|
</el-form-item>
|
|
|
|
|
|
- <!-- 结束时间选择器 -->
|
|
|
<el-form-item label="结束时间" prop="endRecTime">
|
|
|
<el-date-picker
|
|
|
- v-model="queryParams.endRecTime"
|
|
|
- type="datetime"
|
|
|
- value-format="yyyy-MM-dd HH:00:00"
|
|
|
- :picker-options="endPickerOptions"
|
|
|
- placeholder="请选择结束时间"
|
|
|
- @change="handleTimeChange('endRecTime')"
|
|
|
- >
|
|
|
- </el-date-picker>
|
|
|
+ v-model="queryParams.endRecTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ :picker-options="endPickerOptions"
|
|
|
+ placeholder="请选择结束时间"
|
|
|
+ @change="handleTimeChange('endRecTime')"
|
|
|
+ />
|
|
|
</el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-radio-group v-model="tabPosition">
|
|
|
+
|
|
|
+ <!-- 时间维度选择 -->
|
|
|
+ <el-form-item label="统计维度">
|
|
|
+ <el-radio-group v-model="queryParams.timeDimension" @change="handleTimeDimensionChange">
|
|
|
<el-radio-button label="day">日</el-radio-button>
|
|
|
<el-radio-button label="month">月</el-radio-button>
|
|
|
<el-radio-button label="year">年</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
+
|
|
|
+ <!-- 操作按钮 -->
|
|
|
<el-form-item>
|
|
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|
|
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
|
|
</el-form-item>
|
|
|
-
|
|
|
- <el-table v-loading="loading" :data="consumeList">
|
|
|
- <el-table-column label="对象名称" align="center" prop="deviceName"/>
|
|
|
- <el-table-column label="日期" align="center" prop="date" width="180">
|
|
|
- <template slot-scope="scope">
|
|
|
- <span>{{ parseTime(scope.row.date, '{y}-{m}-{d}') }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="时间" align="center" prop="time">
|
|
|
- <template slot-scope="scope">
|
|
|
- <span>{{ scope.row.time }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="用电量(kW·h)" align="center" prop="elecQuantity"/>
|
|
|
- <el-table-column label="用电花费(元)" align="center" prop="useElecCost"/>
|
|
|
- </el-table>
|
|
|
-
|
|
|
- <pagination
|
|
|
- v-show="total>0"
|
|
|
- :total="total"
|
|
|
- :page.sync="queryParams.pageNum"
|
|
|
- :limit.sync="queryParams.pageSize"
|
|
|
- @pagination="getConsumeList"
|
|
|
- />
|
|
|
-
|
|
|
</el-form>
|
|
|
|
|
|
+ <!-- 统计汇总卡片 -->
|
|
|
+ <el-row :gutter="20" style="margin-bottom: 20px">
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card class="summary-card">
|
|
|
+ <div slot="header">
|
|
|
+ <span>总用电量</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-value">
|
|
|
+ {{ summary.elecQuantity || 0 }} <span class="unit">kWh</span>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card class="summary-card">
|
|
|
+ <div slot="header">
|
|
|
+ <span>总电费</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-value">
|
|
|
+ {{ summary.elecCost || 0 }} <span class="unit">元</span>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card class="summary-card">
|
|
|
+ <div slot="header">
|
|
|
+ <span>平均单价</span>
|
|
|
+ </div>
|
|
|
+ <div class="summary-value">
|
|
|
+ {{ calculateAveragePrice() }} <span class="unit">元/kWh</span>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 数据表格 -->
|
|
|
+ <el-table v-loading="loading" :data="consumptionList">
|
|
|
+ <el-table-column label="对象名称" align="center" prop="objName" />
|
|
|
+
|
|
|
+ <el-table-column label="统计时间" align="center" width="180">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span v-if="queryParams.timeDimension === 'day'">
|
|
|
+ {{ formatDateForDisplay(scope.row.statisticDate) }}
|
|
|
+ </span>
|
|
|
+ <span v-else-if="queryParams.timeDimension === 'month'">
|
|
|
+ {{ scope.row.statisticMonth }}
|
|
|
+ </span>
|
|
|
+ <span v-else-if="queryParams.timeDimension === 'year'">
|
|
|
+ {{ scope.row.statisticYear }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="用电量(kWh)" align="center" prop="elecQuantity">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span>{{ formatNumber(scope.row.elecQuantity) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="用电费用(元)" align="center" prop="elecCost">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span>{{ formatNumber(scope.row.elecCost, 2) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="单价(元/kWh)" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span>{{ calculateUnitPrice(scope.row) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <pagination
|
|
|
+ v-show="total > 0"
|
|
|
+ :total="total"
|
|
|
+ :page.sync="queryParams.pageNum"
|
|
|
+ :limit.sync="queryParams.pageSize"
|
|
|
+ @pagination="getConsumptionList"
|
|
|
+ />
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-
|
|
|
import { getFacsCategoryTree } from '@/api/basecfg/emsfacs'
|
|
|
import { areaTreeSelect } from '@/api/basecfg/area'
|
|
|
-import Treeselect from '@riophae/vue-treeselect'
|
|
|
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
|
|
|
-import { listAreaMeter, listFacsMeter } from '@/api/device/elecMeterH'
|
|
|
-import {DateTool} from "@/utils/DateTool";
|
|
|
+import {
|
|
|
+ getAreaConsumptionList,
|
|
|
+ getFacsConsumptionList,
|
|
|
+ getAreaConsumptionSummary,
|
|
|
+ getFacsConsumptionSummary,
|
|
|
+ exportAreaConsumption,
|
|
|
+ exportFacsConsumption,
|
|
|
+ formatQueryParams
|
|
|
+} from '@/api/device/energyConsumption'
|
|
|
|
|
|
export default {
|
|
|
- name: 'consume',
|
|
|
- components: { Treeselect },
|
|
|
+ name: 'EnergyConsumption',
|
|
|
data() {
|
|
|
return {
|
|
|
- // 遮罩层
|
|
|
+ // 界面控制
|
|
|
loading: true,
|
|
|
activeName: 'areaConsume',
|
|
|
- // 表单参数
|
|
|
- areaOptions: [],
|
|
|
- objOptions: [],
|
|
|
- areaName: undefined,
|
|
|
+ filterText: '',
|
|
|
+
|
|
|
+ // 树形选择器
|
|
|
+ treeOptions: [],
|
|
|
defaultProps: {
|
|
|
children: 'children',
|
|
|
label: 'label'
|
|
|
},
|
|
|
- // 总条数
|
|
|
+
|
|
|
+ // 数据
|
|
|
+ consumptionList: [],
|
|
|
+ summary: {},
|
|
|
total: 0,
|
|
|
- tabPosition: 'month',
|
|
|
- consumeList: [],
|
|
|
+
|
|
|
// 查询参数
|
|
|
queryParams: {
|
|
|
pageNum: 1,
|
|
|
pageSize: 10,
|
|
|
- areaCode: -1,
|
|
|
- objType: '1',
|
|
|
+ areaCode: null,
|
|
|
objCode: null,
|
|
|
+ objType: 1,
|
|
|
facsCategory: 'Z',
|
|
|
- startRecTime: this.getFirstDayOfMonth(), // 本月1号
|
|
|
- endRecTime: this.getTodayEndTime(), // 当天结束时间
|
|
|
+ timeDimension: 'month',
|
|
|
+ startRecTime: this.getDefaultStartTime(),
|
|
|
+ endRecTime: this.getDefaultEndTime(),
|
|
|
+ orderFlag: 'desc'
|
|
|
},
|
|
|
+
|
|
|
// 时间选择器配置
|
|
|
startPickerOptions: {
|
|
|
disabledDate: (time) => {
|
|
|
- // 禁用未来时间和超过90天前的时间
|
|
|
- const ninetyDaysAgo = new Date();
|
|
|
- ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
|
|
|
- return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime();
|
|
|
- },
|
|
|
- selectableRange: this.generateHourRanges()
|
|
|
+ const ninetyDaysAgo = new Date()
|
|
|
+ ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90)
|
|
|
+ return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime()
|
|
|
+ }
|
|
|
},
|
|
|
endPickerOptions: {
|
|
|
disabledDate: (time) => {
|
|
|
if (this.queryParams.startRecTime) {
|
|
|
- // 结束时间不能早于开始时间,不能晚于今天,且不能超过开始时间90天后
|
|
|
- const startDate = new Date(this.queryParams.startRecTime);
|
|
|
- const endDateLimit = new Date(startDate);
|
|
|
- endDateLimit.setDate(endDateLimit.getDate() + 90);
|
|
|
- endDateLimit.setHours(23, 59, 59);
|
|
|
+ const startDate = new Date(this.queryParams.startRecTime)
|
|
|
+ const endDateLimit = new Date(startDate)
|
|
|
+ endDateLimit.setDate(endDateLimit.getDate() + 90)
|
|
|
|
|
|
return time.getTime() < startDate.getTime() ||
|
|
|
time.getTime() > endDateLimit.getTime() ||
|
|
|
- time.getTime() > Date.now() - 8.64e7;
|
|
|
+ time.getTime() > Date.now() - 8.64e7
|
|
|
}
|
|
|
- // 禁用未来时间和超过90天前的时间
|
|
|
- const ninetyDaysAgo = new Date();
|
|
|
- ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
|
|
|
- return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime();
|
|
|
- },
|
|
|
- selectableRange: this.generateHourRanges()
|
|
|
+ const ninetyDaysAgo = new Date()
|
|
|
+ ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90)
|
|
|
+ return time.getTime() > Date.now() - 8.64e7 || time.getTime() < ninetyDaysAgo.getTime()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- created() {
|
|
|
- // 初始化时间格式
|
|
|
- if (this.queryParams.startRecTime) {
|
|
|
- this.queryParams.startRecTime = this.formatDateTime(this.queryParams.startRecTime);
|
|
|
- }
|
|
|
- if (this.queryParams.endRecTime) {
|
|
|
- this.queryParams.endRecTime = this.formatDateTime(this.queryParams.endRecTime);
|
|
|
- }
|
|
|
- this.getAreaList()
|
|
|
- this.getConsumeList()
|
|
|
- },
|
|
|
+
|
|
|
watch: {
|
|
|
- // 根据名称筛选区域树
|
|
|
- tabPosition(val) {
|
|
|
- if (!val) {
|
|
|
- return;
|
|
|
- }
|
|
|
- if (val === 'day') {
|
|
|
- this.queryParams.startRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_00_00_00);
|
|
|
- this.queryParams.endRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_23_59_59);
|
|
|
- }
|
|
|
- if (val === 'month') {
|
|
|
- this.queryParams.startRecTime = DateTool.thisMonth(DateTool.DateFormat.YYYY_MM_01_00_00_00);
|
|
|
- this.queryParams.endRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_23_59_59);
|
|
|
- }
|
|
|
- if (val === 'year') {
|
|
|
- this.queryParams.startRecTime = DateTool.thisYear(DateTool.DateFormat.YYYY_01_01_00_00_00);
|
|
|
- this.queryParams.endRecTime = DateTool.now(DateTool.DateFormat.YYYY_MM_DD_23_59_59);
|
|
|
- }
|
|
|
+ filterText(val) {
|
|
|
+ this.$refs.tree.filter(val)
|
|
|
}
|
|
|
},
|
|
|
+
|
|
|
+ created() {
|
|
|
+ this.initializeData()
|
|
|
+ },
|
|
|
+
|
|
|
methods: {
|
|
|
- tabClick() {
|
|
|
- this.clear()
|
|
|
+ // 初始化数据
|
|
|
+ async initializeData() {
|
|
|
+ await this.loadTreeData()
|
|
|
+ await this.getConsumptionList()
|
|
|
+ await this.getConsumptionSummary()
|
|
|
+ },
|
|
|
+
|
|
|
+ // Tab切换处理
|
|
|
+ async handleTabClick(tab) {
|
|
|
+ this.clearData()
|
|
|
+
|
|
|
if (this.activeName === 'areaConsume') {
|
|
|
this.queryParams.objType = 1
|
|
|
- this.getAreaList()
|
|
|
- this.getConsumeList() // 初始化区域用能数据
|
|
|
+ await this.loadAreaTreeData()
|
|
|
} else if (this.activeName === 'facsConsume') {
|
|
|
this.queryParams.objType = 2
|
|
|
- this.getFacsList()
|
|
|
- this.getConsumeList() // 初始化设施用能数据
|
|
|
+ await this.loadFacsTreeData()
|
|
|
}
|
|
|
+
|
|
|
+ await this.getConsumptionList()
|
|
|
+ await this.getConsumptionSummary()
|
|
|
},
|
|
|
|
|
|
- // 表单重置
|
|
|
- clear() {
|
|
|
- this.queryParams = {
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
- areaCode: -1,
|
|
|
- objCode: null,
|
|
|
- facsCategory: 'Z',
|
|
|
- startRecTime: this.getFirstDayOfMonth(), // 本月1号
|
|
|
- endRecTime: this.getTodayEndTime(), // 当天结束时间
|
|
|
+ // 加载树形数据
|
|
|
+ async loadTreeData() {
|
|
|
+ if (this.activeName === 'areaConsume') {
|
|
|
+ await this.loadAreaTreeData()
|
|
|
+ } else {
|
|
|
+ await this.loadFacsTreeData()
|
|
|
}
|
|
|
- this.total = 0
|
|
|
- this.consumeList = []
|
|
|
},
|
|
|
- /** 查询能源区域树列表 */
|
|
|
- getAreaList() {
|
|
|
- areaTreeSelect(0, 3).then(response => {
|
|
|
- this.objOptions = [{
|
|
|
+
|
|
|
+ // 加载区域树数据
|
|
|
+ async loadAreaTreeData() {
|
|
|
+ try {
|
|
|
+ const response = await areaTreeSelect(0, 3)
|
|
|
+ this.treeOptions = [{
|
|
|
id: '-1',
|
|
|
- label: '全部',
|
|
|
- children: []
|
|
|
- }].concat(response.data)
|
|
|
- })
|
|
|
+ label: '全部区域',
|
|
|
+ children: response.data || []
|
|
|
+ }]
|
|
|
+ } catch (error) {
|
|
|
+ this.$modal.msgError('加载区域数据失败')
|
|
|
+ }
|
|
|
},
|
|
|
- /** 查询能源设施树列表 */
|
|
|
- getFacsList() {
|
|
|
- getFacsCategoryTree().then(response => {
|
|
|
- this.objOptions = this.flattenTreeData(response.data)
|
|
|
- })
|
|
|
+
|
|
|
+ // 加载设施树数据
|
|
|
+ async loadFacsTreeData() {
|
|
|
+ try {
|
|
|
+ const response = await getFacsCategoryTree()
|
|
|
+ this.treeOptions = this.flattenFacsTreeData(response.data || [])
|
|
|
+ } catch (error) {
|
|
|
+ this.$modal.msgError('加载设施数据失败')
|
|
|
+ }
|
|
|
},
|
|
|
- // 核心处理函数:压缩层级并扁平化树形结构
|
|
|
- flattenTreeData(regions) {
|
|
|
+
|
|
|
+ // 处理设施树数据
|
|
|
+ flattenFacsTreeData(regions) {
|
|
|
if (!Array.isArray(regions)) return []
|
|
|
|
|
|
return regions.map(region => {
|
|
|
- // 防御性处理children
|
|
|
const children = region.children || []
|
|
|
- if (!Array.isArray(children)) return null
|
|
|
-
|
|
|
- // 查找Z用能设施节点
|
|
|
const zFacility = children.find(child => child.id === 'Z')
|
|
|
|
|
|
- // 仅当存在Z节点时处理该区域
|
|
|
if (zFacility) {
|
|
|
return {
|
|
|
...region,
|
|
|
- // 直接使用Z节点的子节点作为区域的子节点
|
|
|
children: (zFacility.children || []).map(child => ({ ...child }))
|
|
|
}
|
|
|
}
|
|
|
return null
|
|
|
}).filter(Boolean)
|
|
|
},
|
|
|
- getConsumeList() {
|
|
|
- if (this.activeName === 'areaConsume') {
|
|
|
- this.getAreaConsumeList();
|
|
|
- } else if (this.activeName === 'facsConsume') {
|
|
|
- this.getFacsConsumeList();
|
|
|
- }
|
|
|
- },
|
|
|
- getAreaConsumeList() {
|
|
|
+
|
|
|
+ // 获取用能数据列表
|
|
|
+ async getConsumptionList() {
|
|
|
this.loading = true
|
|
|
- listAreaMeter(this.queryParams).then(response => {
|
|
|
- this.consumeList = response.rows
|
|
|
- this.total = response.total
|
|
|
+
|
|
|
+ try {
|
|
|
+ const query = formatQueryParams(this.queryParams)
|
|
|
+ let response
|
|
|
+
|
|
|
+ if (this.activeName === 'areaConsume') {
|
|
|
+ response = await getAreaConsumptionList(query)
|
|
|
+ } else {
|
|
|
+ response = await getFacsConsumptionList(query)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.consumptionList = response.rows || []
|
|
|
+ this.total = response.total || 0
|
|
|
+ } catch (error) {
|
|
|
+ this.$modal.msgError('获取用能数据失败')
|
|
|
+ this.consumptionList = []
|
|
|
+ this.total = 0
|
|
|
+ } finally {
|
|
|
this.loading = false
|
|
|
- })
|
|
|
+ }
|
|
|
},
|
|
|
- // 获取设施用能数据
|
|
|
- getFacsConsumeList() {
|
|
|
- this.loading = true
|
|
|
- listFacsMeter(this.queryParams).then(response => {
|
|
|
- this.consumeList = response.rows;
|
|
|
- this.total = response.total;
|
|
|
- this.loading = false;
|
|
|
- })
|
|
|
+
|
|
|
+ // 获取用能汇总数据
|
|
|
+ async getConsumptionSummary() {
|
|
|
+ try {
|
|
|
+ // 为汇总接口构建查询参数
|
|
|
+ const summaryQuery = { ...this.queryParams }
|
|
|
+
|
|
|
+ // 在设施用能模式下,如果选择了具体设施,汇总接口也需要传递objCode
|
|
|
+ // 这样可以显示该设施在时间维度上的汇总值,而不是整个区域的汇总值
|
|
|
+ const query = formatQueryParams(summaryQuery)
|
|
|
+ let response
|
|
|
+
|
|
|
+ if (this.activeName === 'areaConsume') {
|
|
|
+ response = await getAreaConsumptionSummary(query)
|
|
|
+ } else {
|
|
|
+ response = await getFacsConsumptionSummary(query)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.summary = response.data || {}
|
|
|
+ } catch (error) {
|
|
|
+ this.$modal.msgError('获取汇总数据失败')
|
|
|
+ this.summary = {}
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修复:节点点击处理 - 根据设施树结构处理点击逻辑
|
|
|
+ handleNodeClick(data, node) {
|
|
|
+ // 在设施用能模式下
|
|
|
+ if (this.activeName === 'facsConsume') {
|
|
|
+ // 判断是否为顶级区域节点(如:常泰高速服务区(北区))
|
|
|
+ const isTopLevelAreaNode = !node.parent || node.level === 1
|
|
|
+
|
|
|
+ if (isTopLevelAreaNode) {
|
|
|
+ // 点击顶级区域节点:设置区域代码,清空设施代码
|
|
|
+ // 这样会返回该区域下所有设施的汇总数据
|
|
|
+ this.queryParams.areaCode = data.id
|
|
|
+ this.queryParams.objCode = null
|
|
|
+ } else {
|
|
|
+ // 点击具体设施节点:设置区域代码和设施代码
|
|
|
+ // 区域代码取顶级节点的id
|
|
|
+ this.queryParams.areaCode = this.getTopLevelAreaId(node)
|
|
|
+ this.queryParams.objCode = data.id
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 区域用能模式保持原有逻辑
|
|
|
+ this.queryParams.areaCode = this.getTopLevelId(node)
|
|
|
+ this.queryParams.objCode = data.id === '-1' ? null : data.id
|
|
|
+ }
|
|
|
+
|
|
|
+ this.queryParams.pageNum = 1
|
|
|
+ this.getConsumptionList()
|
|
|
+ this.getConsumptionSummary()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取顶级区域节点ID(专门用于设施树)
|
|
|
+ getTopLevelAreaId(node) {
|
|
|
+ // 向上遍历找到顶级区域节点(level=1的节点)
|
|
|
+ let currentNode = node
|
|
|
+ while (currentNode.parent && currentNode.level > 1) {
|
|
|
+ currentNode = currentNode.parent
|
|
|
+ }
|
|
|
+ return currentNode.data.id
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取顶级节点ID(原有方法,用于区域用能)
|
|
|
+ getTopLevelId(node) {
|
|
|
+ let currentNode = node
|
|
|
+ while (currentNode.parent && currentNode.parent.data.id !== 0 && currentNode.parent.data.id !== '-1') {
|
|
|
+ currentNode = currentNode.parent
|
|
|
+ }
|
|
|
+ return currentNode.data.id === '-1' ? null : currentNode.data.id
|
|
|
+ },
|
|
|
+
|
|
|
+ // 时间维度变化处理
|
|
|
+ handleTimeDimensionChange() {
|
|
|
+ this.adjustTimeRange()
|
|
|
+ this.queryParams.pageNum = 1
|
|
|
+ this.getConsumptionList()
|
|
|
+ this.getConsumptionSummary()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 调整时间范围
|
|
|
+ adjustTimeRange() {
|
|
|
+ const now = new Date()
|
|
|
+
|
|
|
+ switch (this.queryParams.timeDimension) {
|
|
|
+ case 'day':
|
|
|
+ // 当天
|
|
|
+ this.queryParams.startRecTime = this.formatDateTime(this.getTodayStart())
|
|
|
+ this.queryParams.endRecTime = this.formatDateTime(now)
|
|
|
+ break
|
|
|
+ case 'month':
|
|
|
+ // 本月
|
|
|
+ this.queryParams.startRecTime = this.formatDateTime(this.getMonthStart())
|
|
|
+ this.queryParams.endRecTime = this.formatDateTime(now)
|
|
|
+ break
|
|
|
+ case 'year':
|
|
|
+ // 本年
|
|
|
+ this.queryParams.startRecTime = this.formatDateTime(this.getYearStart())
|
|
|
+ this.queryParams.endRecTime = this.formatDateTime(now)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 时间变化处理
|
|
|
+ handleTimeChange(field) {
|
|
|
+ if (field === 'startRecTime' && this.queryParams.endRecTime) {
|
|
|
+ const startDate = new Date(this.queryParams.startRecTime)
|
|
|
+ const endDate = new Date(this.queryParams.endRecTime)
|
|
|
+
|
|
|
+ if (endDate < startDate) {
|
|
|
+ this.queryParams.endRecTime = this.formatDateTime(startDate)
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
- /** 搜索按钮操作 */
|
|
|
+ // 搜索处理
|
|
|
handleQuery() {
|
|
|
this.queryParams.pageNum = 1
|
|
|
- this.getConsumeList()
|
|
|
+ this.getConsumptionList()
|
|
|
+ this.getConsumptionSummary()
|
|
|
},
|
|
|
- /** 重置按钮操作 */
|
|
|
+
|
|
|
+ // 重置处理
|
|
|
resetQuery() {
|
|
|
this.resetForm('queryForm')
|
|
|
+ this.queryParams = {
|
|
|
+ ...this.queryParams,
|
|
|
+ areaCode: null,
|
|
|
+ objCode: null,
|
|
|
+ startRecTime: this.getDefaultStartTime(),
|
|
|
+ endRecTime: this.getDefaultEndTime(),
|
|
|
+ pageNum: 1
|
|
|
+ }
|
|
|
this.handleQuery()
|
|
|
},
|
|
|
- /** 导出按钮操作 */
|
|
|
- handleExport() {
|
|
|
- if (this.activeName === 'areaConsume') {
|
|
|
- this.download('ems/elecMeterH/exportAreaMeter', {
|
|
|
- ...this.queryParams
|
|
|
- }, `区域用能_${new Date().getTime()}.xlsx`)
|
|
|
- } else if (this.activeName === 'facsConsume') {
|
|
|
- this.download('ems/elecMeterH/exportFacsMeter', {
|
|
|
- ...this.queryParams
|
|
|
- }, `设施用能_${new Date().getTime()}.xlsx`)
|
|
|
+
|
|
|
+ // 导出处理
|
|
|
+ async handleExport() {
|
|
|
+ try {
|
|
|
+ this.$modal.loading('正在导出数据,请稍候...')
|
|
|
+ const query = formatQueryParams(this.queryParams)
|
|
|
+
|
|
|
+ let response
|
|
|
+ let filename = ''
|
|
|
+
|
|
|
+ if (this.activeName === 'areaConsume') {
|
|
|
+ response = await exportAreaConsumption(query)
|
|
|
+ filename = `区域用能报表_${this.getCurrentTimeStr()}.xlsx`
|
|
|
+ } else {
|
|
|
+ response = await exportFacsConsumption(query)
|
|
|
+ filename = `设施用能报表_${this.getCurrentTimeStr()}.xlsx`
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理文件下载
|
|
|
+ this.downloadExcelFile(response, filename)
|
|
|
+
|
|
|
+ this.$modal.closeLoading()
|
|
|
+ this.$modal.msgSuccess('导出成功')
|
|
|
+ } catch (error) {
|
|
|
+ this.$modal.closeLoading()
|
|
|
+ this.$modal.msgError('导出失败:' + (error.message || '未知错误'))
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 下载Excel文件
|
|
|
+ downloadExcelFile(data, filename) {
|
|
|
+ try {
|
|
|
+ let blob
|
|
|
+
|
|
|
+ // 如果返回的是Blob对象,直接使用
|
|
|
+ if (data instanceof Blob) {
|
|
|
+ blob = data
|
|
|
+ }
|
|
|
+ // 如果返回的是ArrayBuffer,转换为Blob
|
|
|
+ else if (data instanceof ArrayBuffer) {
|
|
|
+ blob = new Blob([data], {
|
|
|
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 如果是其他格式,尝试转换
|
|
|
+ else {
|
|
|
+ blob = new Blob([data], {
|
|
|
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建下载链接
|
|
|
+ const url = window.URL.createObjectURL(blob)
|
|
|
+ const link = document.createElement('a')
|
|
|
+ link.style.display = 'none'
|
|
|
+ link.href = url
|
|
|
+ link.download = filename
|
|
|
+
|
|
|
+ // 添加到页面并点击下载
|
|
|
+ document.body.appendChild(link)
|
|
|
+ link.click()
|
|
|
+
|
|
|
+ // 清理
|
|
|
+ document.body.removeChild(link)
|
|
|
+ window.URL.revokeObjectURL(url)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('下载文件失败:', error)
|
|
|
+ throw new Error('文件下载失败')
|
|
|
}
|
|
|
},
|
|
|
- // 筛选节点
|
|
|
+
|
|
|
+ // 获取当前时间字符串(用于文件名)
|
|
|
+ getCurrentTimeStr() {
|
|
|
+ const now = new Date()
|
|
|
+ const year = now.getFullYear()
|
|
|
+ const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(now.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(now.getHours()).padStart(2, '0')
|
|
|
+ const minutes = String(now.getMinutes()).padStart(2, '0')
|
|
|
+ const seconds = String(now.getSeconds()).padStart(2, '0')
|
|
|
+
|
|
|
+ return `${year}${month}${day}_${hours}${minutes}${seconds}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // 树过滤
|
|
|
filterNode(value, data) {
|
|
|
if (!value) return true
|
|
|
return data.label.indexOf(value) !== -1
|
|
|
},
|
|
|
- // 节点点击事件处理
|
|
|
- handleNodeClick(data, node) {
|
|
|
- this.queryParams.areaCode = this.getTopLevelId(node)
|
|
|
- this.queryParams.objCode = data.id
|
|
|
+
|
|
|
+ // 清空数据
|
|
|
+ clearData() {
|
|
|
+ this.consumptionList = []
|
|
|
+ this.summary = {}
|
|
|
+ this.total = 0
|
|
|
+ this.queryParams.areaCode = null
|
|
|
+ this.queryParams.objCode = null
|
|
|
this.queryParams.pageNum = 1
|
|
|
+ },
|
|
|
|
|
|
- if (this.activeName === 'areaConsume') {
|
|
|
- this.getAreaConsumeList()
|
|
|
- } else if (this.activeName === 'facsConsume') {
|
|
|
- this.getFacsConsumeList()
|
|
|
+ // 计算平均单价
|
|
|
+ calculateAveragePrice() {
|
|
|
+ if (!this.summary.elecQuantity || !this.summary.elecCost) {
|
|
|
+ return '0.00'
|
|
|
}
|
|
|
+
|
|
|
+ const price = this.summary.elecCost / this.summary.elecQuantity
|
|
|
+ return this.formatNumber(price, 3)
|
|
|
},
|
|
|
- // 追溯顶级节点ID的辅助函数
|
|
|
- getTopLevelId(node) {
|
|
|
- let currentNode = node;
|
|
|
|
|
|
- while (currentNode.parent && currentNode.parent.id !== 0) {
|
|
|
- currentNode = currentNode.parent;
|
|
|
+ // 计算单价
|
|
|
+ calculateUnitPrice(row) {
|
|
|
+ if (!row.elecQuantity || !row.elecCost) {
|
|
|
+ return '0.00'
|
|
|
}
|
|
|
- return currentNode.data.id;
|
|
|
+
|
|
|
+ const price = row.elecCost / row.elecQuantity
|
|
|
+ return this.formatNumber(price, 3)
|
|
|
},
|
|
|
- // 获取本月1号 00:00:00
|
|
|
- getFirstDayOfMonth() {
|
|
|
- const date = new Date();
|
|
|
- date.setDate(1);
|
|
|
- date.setHours(0, 0, 0, 0);
|
|
|
- return this.formatDateTime(date);
|
|
|
+
|
|
|
+ // 修复:专门用于显示的日期格式化方法
|
|
|
+ formatDateForDisplay(dateStr) {
|
|
|
+ if (!dateStr) return ''
|
|
|
+
|
|
|
+ // 如果是完整的日期时间字符串,提取日期部分
|
|
|
+ if (dateStr.includes(' ')) {
|
|
|
+ return dateStr.split(' ')[0]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果已经是日期格式,直接返回
|
|
|
+ if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
|
|
+ return dateStr
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试解析并格式化
|
|
|
+ try {
|
|
|
+ const date = new Date(dateStr)
|
|
|
+ if (isNaN(date.getTime())) return dateStr
|
|
|
+
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ return `${year}-${month}-${day}`
|
|
|
+ } catch (error) {
|
|
|
+ return dateStr
|
|
|
+ }
|
|
|
},
|
|
|
|
|
|
- // 获取当天 23:59:59
|
|
|
- getTodayEndTime() {
|
|
|
- const date = new Date();
|
|
|
- date.setHours(23, 59, 59, 999);
|
|
|
- return this.formatDateTime(date);
|
|
|
+ // 工具方法
|
|
|
+ getDefaultStartTime() {
|
|
|
+ const date = new Date()
|
|
|
+ date.setDate(1)
|
|
|
+ date.setHours(0, 0, 0, 0)
|
|
|
+ return this.formatDateTime(date)
|
|
|
},
|
|
|
|
|
|
- // 格式化日期时间为 yyyy-MM-dd HH:mm:ss
|
|
|
- formatDateTime(date) {
|
|
|
- if (!date) return '';
|
|
|
- if (typeof date === 'string') {
|
|
|
- date = new Date(date);
|
|
|
- }
|
|
|
+ getDefaultEndTime() {
|
|
|
+ return this.formatDateTime(new Date())
|
|
|
+ },
|
|
|
|
|
|
- const year = date.getFullYear();
|
|
|
- const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
- const day = String(date.getDate()).padStart(2, '0');
|
|
|
- const hours = String(date.getHours()).padStart(2, '0');
|
|
|
- const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
- const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
+ getTodayStart() {
|
|
|
+ const date = new Date()
|
|
|
+ date.setHours(0, 0, 0, 0)
|
|
|
+ return date
|
|
|
+ },
|
|
|
|
|
|
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
+ getMonthStart() {
|
|
|
+ const date = new Date()
|
|
|
+ date.setDate(1)
|
|
|
+ date.setHours(0, 0, 0, 0)
|
|
|
+ return date
|
|
|
},
|
|
|
|
|
|
- // 时间选择处理(增强版)
|
|
|
- handleTimeChange(field) {
|
|
|
- this.tabPosition = ""
|
|
|
- if (this.queryParams[field]) {
|
|
|
- // 格式化时间
|
|
|
- this.queryParams[field] = this.formatDateTime(this.queryParams[field]);
|
|
|
-
|
|
|
- // 自动调整另一个时间选择器的范围
|
|
|
- if (field === 'startRecTime' && this.queryParams.endRecTime) {
|
|
|
- // 结束时间不能早于开始时间
|
|
|
- const startDate = new Date(this.queryParams.startRecTime);
|
|
|
- const endDate = new Date(this.queryParams.endRecTime);
|
|
|
-
|
|
|
- if (endDate < startDate) {
|
|
|
- endDate.setTime(startDate.getTime());
|
|
|
- this.queryParams.endRecTime = this.formatDateTime(endDate);
|
|
|
- }
|
|
|
+ getYearStart() {
|
|
|
+ const date = new Date()
|
|
|
+ date.setMonth(0, 1)
|
|
|
+ date.setHours(0, 0, 0, 0)
|
|
|
+ return date
|
|
|
+ },
|
|
|
|
|
|
- // 结束时间不能超过开始时间90天后
|
|
|
- const endDateLimit = new Date(startDate);
|
|
|
- endDateLimit.setDate(endDateLimit.getDate() + 90);
|
|
|
- endDateLimit.setHours(23, 59, 59);
|
|
|
+ formatDateTime(date) {
|
|
|
+ if (!date) return ''
|
|
|
|
|
|
- if (endDate > endDateLimit) {
|
|
|
- this.queryParams.endRecTime = this.formatDateTime(endDateLimit);
|
|
|
- }
|
|
|
+ const year = date.getFullYear()
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0')
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0')
|
|
|
+ const seconds = String(date.getSeconds()).padStart(2, '0')
|
|
|
|
|
|
- this.$nextTick(() => {
|
|
|
- this.$refs.queryForm.validateField('endRecTime');
|
|
|
- });
|
|
|
- } else if (field === 'endRecTime' && this.queryParams.startRecTime) {
|
|
|
- // 开始时间不能晚于结束时间
|
|
|
- const startDate = new Date(this.queryParams.startRecTime);
|
|
|
- const endDate = new Date(this.queryParams.endRecTime);
|
|
|
-
|
|
|
- if (startDate > endDate) {
|
|
|
- startDate.setTime(endDate.getTime());
|
|
|
- this.queryParams.startRecTime = this.formatDateTime(startDate);
|
|
|
- }
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
|
|
+ },
|
|
|
|
|
|
- this.$nextTick(() => {
|
|
|
- this.$refs.queryForm.validateField('startRecTime');
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
+ formatDate(date, format) {
|
|
|
+ if (!date) return ''
|
|
|
+ return this.parseTime(date, format)
|
|
|
},
|
|
|
|
|
|
- // 生成整点时间范围(优化版)
|
|
|
- generateHourRanges() {
|
|
|
- const ranges = [];
|
|
|
- for (let i = 0; i < 24; i++) {
|
|
|
- const start = `${String(i).padStart(2, '0')}:00:00`;
|
|
|
- const end = `${String(i).padStart(2, '0')}:59:59`;
|
|
|
- ranges.push(`${start} - ${end}`);
|
|
|
+ formatNumber(num, decimals = 2) {
|
|
|
+ if (num === null || num === undefined || isNaN(num)) {
|
|
|
+ return '0.' + '0'.repeat(decimals)
|
|
|
}
|
|
|
- return ranges;
|
|
|
+ return Number(num).toFixed(decimals)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
+
|
|
|
<style scoped>
|
|
|
+.tree-container {
|
|
|
+ height: calc(100vh - 200px);
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.summary-card {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
|
|
|
+.summary-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #409EFF;
|
|
|
+}
|
|
|
+
|
|
|
+.unit {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: normal;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
|
|
|
-/* 自定义样式:父节点文字变灰,提示不可点击 */
|
|
|
.el-tree .el-tree-node.is-leaf > .el-tree-node__content {
|
|
|
color: #333;
|
|
|
cursor: pointer;
|