|
|
@@ -1,987 +1,1715 @@
|
|
|
<template>
|
|
|
- <div class="app-container">
|
|
|
- <el-tabs v-model="activeName" @tab-click="tabClick">
|
|
|
- <el-tab-pane label="总览" name="summery">
|
|
|
- <div class="First">
|
|
|
- <div class="time-range-buttons">
|
|
|
- <el-button :class="{ 'is-active': selectedTimeRange === 'day' }"
|
|
|
- @click="changeTimeRange('day')">今日</el-button>
|
|
|
- <el-button :class="{ 'is-active': selectedTimeRange === 'month' }"
|
|
|
- @click="changeTimeRange('month')">本月</el-button>
|
|
|
- <el-button :class="{ 'is-active': selectedTimeRange === 'year' }"
|
|
|
- @click="changeTimeRange('year')">本年</el-button>
|
|
|
- </div>
|
|
|
- <div class="first-content">
|
|
|
- <div>
|
|
|
- <SubTitle title="能耗统计" />
|
|
|
- <!-- 图表容器 -->
|
|
|
- <div ref="sumBySubCategoryChart" style="height: 380px;" />
|
|
|
+ <div class="power-use-container">
|
|
|
+ <el-tabs v-model="activeName" class="main-tabs" @tab-click="handleTabClick">
|
|
|
+ <!-- 总览 Tab -->
|
|
|
+ <el-tab-pane label="总览" name="summary">
|
|
|
+ <div class="summary-section">
|
|
|
+ <!-- 时间范围切换 -->
|
|
|
+ <div class="time-range-bar">
|
|
|
+ <div class="time-buttons">
|
|
|
+ <el-button
|
|
|
+ v-for="item in timeRangeOptions"
|
|
|
+ :key="item.value"
|
|
|
+ :class="{ 'is-active': selectedTimeRange === item.value }"
|
|
|
+ size="small"
|
|
|
+ @click="changeTimeRange(item.value)"
|
|
|
+ >
|
|
|
+ {{ item.label }}
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
- <div>
|
|
|
- <SubTitle title="能耗总览" />
|
|
|
- <el-table border stripe show-summary :data="tableData" style="width: 100%;margin-top: 20px;">
|
|
|
- <el-table-column prop="name" align="center" label="设施名称">
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="value" align="center" label="能耗(kW·h)">
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
+ <div class="time-display">
|
|
|
+ <i class="el-icon-time"></i>
|
|
|
+ <span>{{ formatDateRangeDisplay() }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- </el-tab-pane>
|
|
|
- <el-tab-pane v-for="item in facsCategoryOptions" :key="item.code" :label="item.name" :name="item.code">
|
|
|
- <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"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="head-container tree-container">
|
|
|
- <el-tree
|
|
|
- :data="areaOptions"
|
|
|
- :props="defaultProps"
|
|
|
- :expand-on-click-node="false"
|
|
|
- :filter-node-method="filterNode"
|
|
|
- ref="tree"
|
|
|
- node-key="id"
|
|
|
- default-expand-all
|
|
|
- highlight-current
|
|
|
- @node-click="handleNodeClick"
|
|
|
- >
|
|
|
- <template #default="{ node, data }">
|
|
|
+ <!-- 统计卡片 -->
|
|
|
+ <div class="stat-cards">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-tooltip
|
|
|
+ :disabled="!formattedOverview.totalQuantity.isShortened"
|
|
|
+ :content="'精确值: ' + formattedOverview.totalQuantity.full + ' kW·h'"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <div class="stat-card primary-card">
|
|
|
+ <div class="card-icon"><i class="el-icon-lightning"></i></div>
|
|
|
+ <div class="card-info">
|
|
|
+ <span class="card-label">总用电量</span>
|
|
|
+ <span class="card-value">
|
|
|
+ {{ formattedOverview.totalQuantity.display }}
|
|
|
+ <span v-if="formattedOverview.totalQuantity.suffix" class="value-suffix">
|
|
|
+ {{ formattedOverview.totalQuantity.suffix }}
|
|
|
+ </span>
|
|
|
+ <small>kW·h</small>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-tooltip
|
|
|
+ :disabled="!formattedOverview.peakQuantity.isShortened"
|
|
|
+ :content="'精确值: ' + formattedOverview.peakQuantity.full + ' kW·h'"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <div class="stat-card warning-card">
|
|
|
+ <div class="card-icon"><i class="el-icon-sunrise"></i></div>
|
|
|
+ <div class="card-info">
|
|
|
+ <span class="card-label">峰时用电</span>
|
|
|
+ <span class="card-value">
|
|
|
+ {{ formattedOverview.peakQuantity.display }}
|
|
|
+ <span v-if="formattedOverview.peakQuantity.suffix" class="value-suffix">
|
|
|
+ {{ formattedOverview.peakQuantity.suffix }}
|
|
|
+ </span>
|
|
|
+ <small>kW·h</small>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-tooltip
|
|
|
+ :disabled="!formattedOverview.valleyQuantity.isShortened"
|
|
|
+ :content="'精确值: ' + formattedOverview.valleyQuantity.full + ' kW·h'"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <div class="stat-card info-card">
|
|
|
+ <div class="card-icon"><i class="el-icon-moon"></i></div>
|
|
|
+ <div class="card-info">
|
|
|
+ <span class="card-label">谷时用电</span>
|
|
|
+ <span class="card-value">
|
|
|
+ {{ formattedOverview.valleyQuantity.display }}
|
|
|
+ <span v-if="formattedOverview.valleyQuantity.suffix" class="value-suffix">
|
|
|
+ {{ formattedOverview.valleyQuantity.suffix }}
|
|
|
+ </span>
|
|
|
+ <small>kW·h</small>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
<el-tooltip
|
|
|
- class="tree-node-tooltip"
|
|
|
- effect="dark"
|
|
|
- :content="data.label"
|
|
|
- placement="right"
|
|
|
- :disabled="!isTextOverflow(data.label)"
|
|
|
+ :disabled="!formattedOverview.totalCost.isShortened"
|
|
|
+ :content="'精确值: ¥' + formattedOverview.totalCost.full"
|
|
|
+ placement="top"
|
|
|
>
|
|
|
- <div class="tree-node">
|
|
|
- <i class="el-icon-office-building node-icon"></i>
|
|
|
- <span class="node-label">{{ data.label }}</span>
|
|
|
+ <div class="stat-card success-card">
|
|
|
+ <div class="card-icon"><i class="el-icon-money"></i></div>
|
|
|
+ <div class="card-info">
|
|
|
+ <span class="card-label">总电费</span>
|
|
|
+ <span class="card-value">
|
|
|
+ ¥{{ formattedOverview.totalCost.display }}
|
|
|
+ <span v-if="formattedOverview.totalCost.suffix" class="value-suffix">
|
|
|
+ {{ formattedOverview.totalCost.suffix }}
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</el-tooltip>
|
|
|
- </template>
|
|
|
- </el-tree>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
</div>
|
|
|
- </el-col>
|
|
|
- <el-col :span="20" :xs="24">
|
|
|
- <div class="container-block">
|
|
|
- <div class="ctl-container">
|
|
|
- <SubTitle :title="`设施汇总电耗【${selectedLabel}】`" />
|
|
|
- <div>
|
|
|
- <el-select v-model="objCode" placeholder="选择设施" clearable @visible-change="handleObjSelectClick"
|
|
|
- @change="getList">
|
|
|
- <el-option v-for="item in objOptions" :label="item.objName" :value="item.objCode"
|
|
|
- :key="item.objCode" />
|
|
|
-
|
|
|
- </el-select>
|
|
|
- <el-date-picker v-model="dateRange" type="datetimerange" @change="getList"
|
|
|
- :picker-options="pickerOptions" value-format="yyyy-MM-dd hh:mm:ss" range-separator="至"
|
|
|
- start-placeholder="开始日期" end-placeholder="结束日期" :clearable="false" align="right">
|
|
|
- </el-date-picker>
|
|
|
+
|
|
|
+ <!-- 图表和表格区域 -->
|
|
|
+ <el-row :gutter="20" class="content-row">
|
|
|
+ <el-col :span="14">
|
|
|
+ <div class="chart-panel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <h3 class="panel-title"><i class="el-icon-pie-chart"></i> 能耗分布</h3>
|
|
|
+ <el-radio-group v-model="overviewChartType" size="mini">
|
|
|
+ <el-radio-button label="pie">饼图</el-radio-button>
|
|
|
+ <el-radio-button label="bar">柱图</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container" v-loading="overviewLoading">
|
|
|
+ <div ref="overviewChartRef" class="chart-canvas" style="height: 340px;"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="10">
|
|
|
+ <div class="table-panel">
|
|
|
+ <div class="panel-header">
|
|
|
+ <h3 class="panel-title"><i class="el-icon-document"></i> 能耗排行</h3>
|
|
|
+ </div>
|
|
|
+ <div class="table-container" v-loading="overviewLoading">
|
|
|
+ <el-table
|
|
|
+ :data="overviewTableData"
|
|
|
+ stripe
|
|
|
+ size="small"
|
|
|
+ max-height="340"
|
|
|
+ show-summary
|
|
|
+ :summary-method="getOverviewSummaries"
|
|
|
+ >
|
|
|
+ <el-table-column type="index" label="排名" width="60" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span :class="getRankClass(scope.$index)">{{ scope.$index + 1 }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="objName" label="设施名称" show-overflow-tooltip/>
|
|
|
+ <el-table-column prop="quantity" label="用电量(kW·h)" width="120" align="right">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span class="quantity-text">{{ formatNumber(scope.row.quantity) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="占比" width="100" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-progress
|
|
|
+ :percentage="getPercentage(scope.row.quantity)"
|
|
|
+ :stroke-width="6"
|
|
|
+ :show-text="false"
|
|
|
+ :color="getProgressColor(getPercentage(scope.row.quantity))"
|
|
|
+ />
|
|
|
+ <span class="percentage-text">{{ getPercentage(scope.row.quantity).toFixed(1) }}%</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <!-- 设施分类 Tabs (动态生成) - 修复:使用独立的 detail-pane 组件或唯一ref -->
|
|
|
+ <el-tab-pane
|
|
|
+ v-for="item in facsCategoryOptions"
|
|
|
+ :key="item.code"
|
|
|
+ :label="item.name"
|
|
|
+ :name="item.code"
|
|
|
+ :lazy="true"
|
|
|
+ >
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <!-- 左侧区域树 -->
|
|
|
+ <el-col :span="5">
|
|
|
+ <div class="tree-panel">
|
|
|
+ <div class="tree-search">
|
|
|
+ <el-input
|
|
|
+ v-model="areaKeyword"
|
|
|
+ placeholder="搜索区域"
|
|
|
+ clearable
|
|
|
+ size="small"
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
+ @input="filterAreaTree"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="tree-content">
|
|
|
+ <el-tree
|
|
|
+ :ref="'areaTree_' + item.code"
|
|
|
+ :data="areaTreeData"
|
|
|
+ :props="treeProps"
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ :filter-node-method="filterNode"
|
|
|
+ node-key="id"
|
|
|
+ default-expand-all
|
|
|
+ highlight-current
|
|
|
+ @node-click="handleAreaNodeClick"
|
|
|
+ >
|
|
|
+ <template #default="{ node, data }">
|
|
|
+ <div class="tree-node-item">
|
|
|
+ <i :class="getTreeNodeIcon(data)" class="node-icon"></i>
|
|
|
+ <span class="node-label">{{ node.label }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-tree>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 表格 -->
|
|
|
- <el-table v-loading="loading" :data="sumByFacsList" style="width: 100%; margin-top: 10px">
|
|
|
- <el-table-column label="设施编码" align="center" prop="objCode">
|
|
|
- <template slot-scope="scope">
|
|
|
- <span>{{ scope.row.objCode }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="设施名称" align="center" prop="objName">
|
|
|
- <template slot-scope="scope">
|
|
|
- <span>{{ scope.row.objName }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="用电量(kW·h)" align="center" prop="elecQuantity">
|
|
|
- <template slot-scope="scope">
|
|
|
- <span>{{ scope.row.quantity }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- <SubTitle title="设施时段电耗" style="margin-top: 20px;" />
|
|
|
- <!--柱状图-->
|
|
|
- <div class="container-block" style="margin-top: 20px;">
|
|
|
- <BaseChart width="100%" height="300px" :option="barChartOptions" />
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <!-- 右侧内容区 -->
|
|
|
+ <el-col :span="19">
|
|
|
+ <div class="detail-content">
|
|
|
+ <!-- 筛选条件 -->
|
|
|
+ <div class="filter-section">
|
|
|
+ <div class="filter-left">
|
|
|
+ <span class="current-area">
|
|
|
+ <i class="el-icon-location-outline"></i> {{ selectedAreaLabel }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="filter-right">
|
|
|
+ <el-select
|
|
|
+ v-model="selectedObjCode"
|
|
|
+ placeholder="全部设施"
|
|
|
+ clearable
|
|
|
+ size="small"
|
|
|
+ style="width: 180px"
|
|
|
+ @change="handleFacsChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="opt in facsOptions"
|
|
|
+ :key="opt.objCode"
|
|
|
+ :label="opt.objName"
|
|
|
+ :value="opt.objCode"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="detailDateRange"
|
|
|
+ type="datetimerange"
|
|
|
+ :picker-options="datePickerOptions"
|
|
|
+ value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ size="small"
|
|
|
+ :clearable="false"
|
|
|
+ style="width: 340px"
|
|
|
+ @change="loadDetailData"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 设施汇总表格 -->
|
|
|
+ <div class="detail-table-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3 class="section-title"><i class="el-icon-s-grid"></i> 设施用电汇总</h3>
|
|
|
+ <div class="section-summary" v-if="facsSummaryList.length > 0">
|
|
|
+ <span class="summary-item">
|
|
|
+ <i class="el-icon-files"></i>
|
|
|
+ 共 <em>{{ facsSummaryList.length }}</em> 个设施
|
|
|
+ </span>
|
|
|
+ <span class="summary-item">
|
|
|
+ <i class="el-icon-lightning"></i>
|
|
|
+ 合计 <em>{{ formatNumber(facsSummaryTotal) }}</em> kW·h
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ v-loading="detailLoading"
|
|
|
+ :data="facsSummaryList"
|
|
|
+ stripe
|
|
|
+ size="small"
|
|
|
+ max-height="220"
|
|
|
+ show-summary
|
|
|
+ :summary-method="getFacsSummaries"
|
|
|
+ >
|
|
|
+ <el-table-column prop="objCode" label="设施编码" width="150" align="center"/>
|
|
|
+ <el-table-column prop="objName" label="设施名称" show-overflow-tooltip>
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span class="facs-name">
|
|
|
+ <i class="el-icon-cpu"></i>
|
|
|
+ {{ scope.row.objName }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="用电量(kW·h)" width="150" align="right">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span class="quantity-value">{{ formatNumber(scope.row.quantity) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="占比" width="140" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <div class="percentage-cell">
|
|
|
+ <el-progress
|
|
|
+ :percentage="getFacsPercentage(scope.row.quantity)"
|
|
|
+ :stroke-width="8"
|
|
|
+ :show-text="false"
|
|
|
+ :color="getProgressColor(getFacsPercentage(scope.row.quantity))"
|
|
|
+ />
|
|
|
+ <span class="percentage-text">{{ getFacsPercentage(scope.row.quantity).toFixed(1) }}%</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 设施时段电耗图表 - 核心修复:使用动态ref -->
|
|
|
+ <div class="detail-chart-section">
|
|
|
+ <div class="section-header">
|
|
|
+ <h3 class="section-title"><i class="el-icon-data-line"></i> 设施时段电耗对比</h3>
|
|
|
+ <div class="chart-controls">
|
|
|
+ <el-radio-group v-model="detailChartType" size="mini" @change="renderDetailChart">
|
|
|
+ <el-radio-button label="bar">
|
|
|
+ <i class="el-icon-s-data"></i> 柱图
|
|
|
+ </el-radio-button>
|
|
|
+ <el-radio-button label="line">
|
|
|
+ <i class="el-icon-data-line"></i> 折线
|
|
|
+ </el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ <el-checkbox
|
|
|
+ v-model="showStacked"
|
|
|
+ size="mini"
|
|
|
+ style="margin-left: 12px;"
|
|
|
+ @change="renderDetailChart"
|
|
|
+ >
|
|
|
+ 堆叠显示
|
|
|
+ </el-checkbox>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="chart-container" v-loading="detailChartLoading">
|
|
|
+ <!-- 关键修复:每个tab使用独立的ref名称 -->
|
|
|
+ <div
|
|
|
+ :ref="'detailChart_' + item.code"
|
|
|
+ class="chart-canvas"
|
|
|
+ style="height: 320px; width: 100%;"
|
|
|
+ ></div>
|
|
|
+ <div v-if="hourlyDataByFacs.length === 0 && !detailChartLoading" class="empty-chart">
|
|
|
+ <i class="el-icon-document-delete"></i>
|
|
|
+ <span>暂无数据</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </el-col>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
</el-tab-pane>
|
|
|
-
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import * as echarts from 'echarts/core';
|
|
|
-import {listByFacs,listFacsMeter} from '@/api/device/elecMeterH'
|
|
|
-import { getPowerData, getPowerMaxLoad} from '@/api/mgr/elecUseH'
|
|
|
-import {getFacsCategorygetByCode} from '@/api/basecfg/emsfacs'
|
|
|
-import {areaTreeByFacsCategory} from '@/api/basecfg/area'
|
|
|
+import * as echarts from 'echarts'
|
|
|
import dayjs from 'dayjs'
|
|
|
-import {DateTool} from '@/utils/DateTool'
|
|
|
-import {dateFormat} from '@/utils';
|
|
|
-import BaseChart from '@/components/BaseChart/index.vue'
|
|
|
-import SubTitle from '@/components/SubTitle'
|
|
|
-import Block from '@/components/Block/block.vue'
|
|
|
-import Treeselect from '@riophae/vue-treeselect'
|
|
|
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
|
|
|
+// API 接口 - 确保路径正确
|
|
|
+import { listByFacs, listFacsMeter } from '@/api/device/elecMeterH'
|
|
|
+import { getFacsCategorygetByCode } from '@/api/basecfg/emsfacs'
|
|
|
+import { areaTreeByFacsCategory } from '@/api/basecfg/area'
|
|
|
|
|
|
export default {
|
|
|
name: 'PowerUse',
|
|
|
- components: {
|
|
|
- Treeselect,
|
|
|
- BaseChart,
|
|
|
- Block,
|
|
|
- SubTitle
|
|
|
- },
|
|
|
- data () {
|
|
|
- const today = dateFormat(new Date(), 'yyyy-MM-dd')
|
|
|
+ data() {
|
|
|
return {
|
|
|
- activeName: 'summery',
|
|
|
- // 遮罩层
|
|
|
- loading: true,
|
|
|
- // 选中数组
|
|
|
- ids: [],
|
|
|
- // 非单个禁用
|
|
|
- single: true,
|
|
|
- // 非多个禁用
|
|
|
- multiple: true,
|
|
|
- // 显示搜索条件
|
|
|
- showSearch: true,
|
|
|
- // 总条数
|
|
|
- total: 0,
|
|
|
- // 用能计量-小时表格数据
|
|
|
- hList: [],
|
|
|
- //查能耗
|
|
|
- sumByFacsList: [],
|
|
|
- // 弹出层标题
|
|
|
- title: '',
|
|
|
- // 是否显示弹出层
|
|
|
- open: false,
|
|
|
- facsCategory: undefined,
|
|
|
- facsSubCategory: undefined,
|
|
|
-
|
|
|
- // 区域名称
|
|
|
- areaName: undefined,
|
|
|
- selectedLabel: '全部',
|
|
|
- defaultProps: {
|
|
|
- children: 'children',
|
|
|
- label: 'label'
|
|
|
+ // ========== 基础状态 ==========
|
|
|
+ activeName: 'summary',
|
|
|
+ selectedTimeRange: 'day',
|
|
|
+ timeRangeOptions: [
|
|
|
+ { label: '今日', value: 'day' },
|
|
|
+ { label: '本月', value: 'month' },
|
|
|
+ { label: '本年', value: 'year' }
|
|
|
+ ],
|
|
|
+
|
|
|
+ // ========== 总览数据 ==========
|
|
|
+ overviewLoading: false,
|
|
|
+ overviewDateRange: [],
|
|
|
+ overviewSummary: {
|
|
|
+ totalQuantity: 0,
|
|
|
+ peakQuantity: 0,
|
|
|
+ valleyQuantity: 0,
|
|
|
+ normalQuantity: 0,
|
|
|
+ totalCost: 0
|
|
|
},
|
|
|
- areaOptions: [],
|
|
|
+ overviewTableData: [],
|
|
|
+ overviewChartType: 'pie',
|
|
|
+ overviewChartInstance: null,
|
|
|
+
|
|
|
+ // ========== 设施分类 ==========
|
|
|
facsCategoryOptions: [],
|
|
|
- sumBySubCategoryChartOption: {},
|
|
|
- // 存储图表配置的变量
|
|
|
- selectedTimeRange: 'day',
|
|
|
- chartInstance: null,
|
|
|
- // 用于存储 ECharts 实例
|
|
|
- totalElecQuantity: 0,
|
|
|
- objOptions: [],
|
|
|
- objCode: undefined,
|
|
|
- dateRange: [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)],
|
|
|
- pickerOptions: {
|
|
|
+
|
|
|
+ // ========== 区域树 ==========
|
|
|
+ areaKeyword: '',
|
|
|
+ areaTreeData: [],
|
|
|
+ treeProps: { children: 'children', label: 'label' },
|
|
|
+ selectedAreaCode: '-1',
|
|
|
+ selectedAreaLabel: '全部',
|
|
|
+
|
|
|
+ // ========== 设施筛选 ==========
|
|
|
+ facsOptions: [],
|
|
|
+ selectedObjCode: null,
|
|
|
+
|
|
|
+ // ========== 详情数据 ==========
|
|
|
+ detailLoading: false,
|
|
|
+ detailChartLoading: false,
|
|
|
+ detailDateRange: [],
|
|
|
+ facsSummaryList: [],
|
|
|
+ hourlyDataByFacs: [],
|
|
|
+
|
|
|
+ // ========== 图表配置 ==========
|
|
|
+ detailChartType: 'bar',
|
|
|
+ showStacked: false,
|
|
|
+ detailChartInstance: null,
|
|
|
+
|
|
|
+ // ========== 日期选择器配置 ==========
|
|
|
+ datePickerOptions: {
|
|
|
shortcuts: [
|
|
|
{
|
|
|
- text: '最近一周',
|
|
|
- onClick (picker) {
|
|
|
- const end = new Date()
|
|
|
- const start = new Date()
|
|
|
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
|
|
- picker.$emit('pick', [start, end])
|
|
|
+ text: '今日',
|
|
|
+ onClick(picker) {
|
|
|
+ picker.$emit('pick', [
|
|
|
+ dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ])
|
|
|
}
|
|
|
- }, {
|
|
|
- text: '最近一个月',
|
|
|
- onClick (picker) {
|
|
|
- const end = new Date()
|
|
|
- const start = new Date()
|
|
|
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
|
|
|
- picker.$emit('pick', [start, end])
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: '昨日',
|
|
|
+ onClick(picker) {
|
|
|
+ picker.$emit('pick', [
|
|
|
+ dayjs().subtract(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ])
|
|
|
}
|
|
|
- }, {
|
|
|
- text: '最近三个月',
|
|
|
- onClick (picker) {
|
|
|
- const end = new Date()
|
|
|
- const start = new Date()
|
|
|
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
|
|
|
- picker.$emit('pick', [start, end])
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: '最近7天',
|
|
|
+ onClick(picker) {
|
|
|
+ picker.$emit('pick', [
|
|
|
+ dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: '本月',
|
|
|
+ onClick(picker) {
|
|
|
+ picker.$emit('pick', [
|
|
|
+ dayjs().startOf('month').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ])
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
- // 查询参数
|
|
|
+
|
|
|
+ // ========== 查询参数 ==========
|
|
|
queryParams: {
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
areaCode: '-1',
|
|
|
- objType: 2,
|
|
|
- objCode: null,
|
|
|
facsCategory: 'Z',
|
|
|
facsSubCategory: null,
|
|
|
- date: null,
|
|
|
- time: null,
|
|
|
- timeIndex: null,
|
|
|
- },
|
|
|
- queryObjParams: {
|
|
|
- refArea: null,
|
|
|
- facsCategory: null,
|
|
|
- subCategory: null
|
|
|
- },
|
|
|
- // 表单参数
|
|
|
- form: {},
|
|
|
- tableData: [],
|
|
|
- powerDate: [`${today} 00:00:00`, `${today} 23:59:59`],
|
|
|
- powerChartData: [],
|
|
|
- powerMaxLoad: '',
|
|
|
- equipPowerChartData: [],
|
|
|
- equipPowerMaxLoad: ''
|
|
|
+ meterCls: 45
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ formattedOverview() {
|
|
|
+ return {
|
|
|
+ totalQuantity: this.formatSmartNumber(this.overviewSummary.totalQuantity),
|
|
|
+ peakQuantity: this.formatSmartNumber(this.overviewSummary.peakQuantity),
|
|
|
+ valleyQuantity: this.formatSmartNumber(this.overviewSummary.valleyQuantity),
|
|
|
+ normalQuantity: this.formatSmartNumber(this.overviewSummary.normalQuantity),
|
|
|
+ totalCost: this.formatSmartNumber(this.overviewSummary.totalCost)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ facsSummaryTotal() {
|
|
|
+ return this.facsSummaryList.reduce((sum, item) => sum + (item.quantity || 0), 0)
|
|
|
}
|
|
|
},
|
|
|
+
|
|
|
watch: {
|
|
|
- // 根据名称筛选区域树
|
|
|
- areaName (val) {
|
|
|
- this.$refs.tree.filter(val)
|
|
|
+ overviewChartType() {
|
|
|
+ this.renderOverviewChart()
|
|
|
}
|
|
|
},
|
|
|
- created () {
|
|
|
- this.facsCategory = 'Z'
|
|
|
- this.facsSubCategory = ''
|
|
|
- this.getFacsCategory(this.facsCategory)
|
|
|
- this.getAreaTree(this.facsCategory, this.facsSubCategory)
|
|
|
- this.getList()
|
|
|
- this.getsumByFacsH()
|
|
|
- this.getSumBySubCategoryH();
|
|
|
- this.getPowerChart()
|
|
|
+
|
|
|
+ created() {
|
|
|
+ this.initDateRange()
|
|
|
+ this.loadFacsCategories()
|
|
|
+ this.loadOverviewData()
|
|
|
},
|
|
|
- computed: {
|
|
|
- barChartOptions () {
|
|
|
- const xAxisData = this.hList.map(item => item.time);
|
|
|
- const seriesData = this.hList.map(item => item.elecQuantity);
|
|
|
- return {
|
|
|
- toolbox: {
|
|
|
- itemGap: 10,
|
|
|
- itemSize: 16,
|
|
|
- right: 10,
|
|
|
- top: 0,
|
|
|
- show: true,
|
|
|
- feature: {
|
|
|
- magicType: {
|
|
|
- show: true,
|
|
|
- type: ['bar', 'line']
|
|
|
- },
|
|
|
- saveAsImage: {
|
|
|
- show: true
|
|
|
- }
|
|
|
+
|
|
|
+ mounted() {
|
|
|
+ window.addEventListener('resize', this.handleResize)
|
|
|
+ },
|
|
|
+
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener('resize', this.handleResize)
|
|
|
+ this.disposeCharts()
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ // ==================== 工具方法 ====================
|
|
|
+ formatSmartNumber(value) {
|
|
|
+ if (value === null || value === undefined) {
|
|
|
+ return { display: '0', suffix: '', full: '0', isShortened: false }
|
|
|
+ }
|
|
|
+ const num = parseFloat(value)
|
|
|
+ if (isNaN(num)) {
|
|
|
+ return { display: '0', suffix: '', full: '0', isShortened: false }
|
|
|
+ }
|
|
|
+ const fullValue = num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
|
+ const absNum = Math.abs(num)
|
|
|
+ if (absNum >= 100000000) {
|
|
|
+ return { display: (num / 100000000).toFixed(1), suffix: '亿', full: fullValue, isShortened: true }
|
|
|
+ }
|
|
|
+ if (absNum >= 10000) {
|
|
|
+ return { display: (num / 10000).toFixed(1), suffix: '万', full: fullValue, isShortened: true }
|
|
|
+ }
|
|
|
+ if (absNum >= 1000) {
|
|
|
+ return { display: Math.round(num).toLocaleString('zh-CN'), suffix: '', full: fullValue, isShortened: false }
|
|
|
+ }
|
|
|
+ return { display: num.toFixed(2), suffix: '', full: fullValue, isShortened: false }
|
|
|
+ },
|
|
|
+
|
|
|
+ formatNumber(value) {
|
|
|
+ if (value === null || value === undefined) return '0.00'
|
|
|
+ const num = parseFloat(value)
|
|
|
+ if (isNaN(num)) return '0.00'
|
|
|
+ return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
|
+ },
|
|
|
+
|
|
|
+ initDateRange() {
|
|
|
+ const today = dayjs()
|
|
|
+ this.overviewDateRange = [
|
|
|
+ today.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ]
|
|
|
+ this.detailDateRange = [...this.overviewDateRange]
|
|
|
+ },
|
|
|
+
|
|
|
+ changeTimeRange(rangeType) {
|
|
|
+ this.selectedTimeRange = rangeType
|
|
|
+ const today = dayjs()
|
|
|
+ if (rangeType === 'day') {
|
|
|
+ this.overviewDateRange = [
|
|
|
+ today.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ]
|
|
|
+ } else if (rangeType === 'month') {
|
|
|
+ this.overviewDateRange = [
|
|
|
+ today.startOf('month').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ]
|
|
|
+ } else if (rangeType === 'year') {
|
|
|
+ this.overviewDateRange = [
|
|
|
+ today.startOf('year').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ today.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ this.loadOverviewData()
|
|
|
+ },
|
|
|
+
|
|
|
+ formatDateRangeDisplay() {
|
|
|
+ if (!this.overviewDateRange || this.overviewDateRange.length !== 2) return ''
|
|
|
+ return `${dayjs(this.overviewDateRange[0]).format('MM-DD HH:mm')} 至 ${dayjs(this.overviewDateRange[1]).format('MM-DD HH:mm')}`
|
|
|
+ },
|
|
|
+
|
|
|
+ // ==================== 获取图表DOM(核心修复) ====================
|
|
|
+ getDetailChartDom() {
|
|
|
+ const refName = 'detailChart_' + this.activeName
|
|
|
+ const chartRef = this.$refs[refName]
|
|
|
+
|
|
|
+ console.log('[getDetailChartDom] refName:', refName, 'chartRef:', chartRef)
|
|
|
+
|
|
|
+ // v-for 中的 ref 在 Vue2 中返回数组
|
|
|
+ if (Array.isArray(chartRef)) {
|
|
|
+ return chartRef[0]
|
|
|
+ }
|
|
|
+ return chartRef
|
|
|
+ },
|
|
|
+
|
|
|
+ // ==================== 数据加载 ====================
|
|
|
+ async loadFacsCategories() {
|
|
|
+ try {
|
|
|
+ const response = await getFacsCategorygetByCode('Z')
|
|
|
+ this.facsCategoryOptions = response.data?.subtypeList || []
|
|
|
+ console.log('设施分类:', this.facsCategoryOptions)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载设施分类失败', error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async loadOverviewData() {
|
|
|
+ this.overviewLoading = true
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ startRecTime: this.overviewDateRange[0],
|
|
|
+ endRecTime: this.overviewDateRange[1],
|
|
|
+ areaCode: '-1',
|
|
|
+ meterCls: 45,
|
|
|
+ facsCategory: 'Z'
|
|
|
+ }
|
|
|
+ const response = await listByFacs(params)
|
|
|
+ const data = response.data || []
|
|
|
+
|
|
|
+ this.overviewTableData = data
|
|
|
+ .map(item => ({
|
|
|
+ objCode: item.objCode,
|
|
|
+ objName: item.objName,
|
|
|
+ quantity: item.quantity || 0
|
|
|
+ }))
|
|
|
+ .filter(item => item.quantity > 0)
|
|
|
+ .sort((a, b) => b.quantity - a.quantity)
|
|
|
+
|
|
|
+ const totalQuantity = data.reduce((sum, item) => sum + (item.quantity || 0), 0)
|
|
|
+ this.overviewSummary = {
|
|
|
+ totalQuantity: totalQuantity,
|
|
|
+ peakQuantity: 0,
|
|
|
+ valleyQuantity: 0,
|
|
|
+ normalQuantity: 0,
|
|
|
+ totalCost: 0
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$nextTick(() => this.renderOverviewChart())
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载总览数据失败', error)
|
|
|
+ this.$message.error('数据加载失败')
|
|
|
+ } finally {
|
|
|
+ this.overviewLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async loadAreaTree() {
|
|
|
+ try {
|
|
|
+ const response = await areaTreeByFacsCategory('Z', this.queryParams.facsSubCategory, false)
|
|
|
+ this.areaTreeData = [{ id: '-1', label: '全部', children: response.data || [] }]
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载区域树失败', error)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载详情数据 - 核心方法
|
|
|
+ */
|
|
|
+ async loadDetailData() {
|
|
|
+ if (!this.queryParams.facsSubCategory) {
|
|
|
+ console.warn('[loadDetailData] facsSubCategory 未设置')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[loadDetailData] 开始加载, facsSubCategory:', this.queryParams.facsSubCategory)
|
|
|
+
|
|
|
+ this.detailLoading = true
|
|
|
+ this.detailChartLoading = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ const baseParams = {
|
|
|
+ startRecTime: this.detailDateRange[0],
|
|
|
+ endRecTime: this.detailDateRange[1],
|
|
|
+ meterCls: 45,
|
|
|
+ areaCode: this.selectedAreaCode,
|
|
|
+ facsCategory: 'Z',
|
|
|
+ facsSubCategory: this.queryParams.facsSubCategory
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[loadDetailData] 请求参数:', baseParams)
|
|
|
+
|
|
|
+ // 1. 加载设施汇总数据
|
|
|
+ const summaryRes = await listByFacs(baseParams)
|
|
|
+ console.log('[loadDetailData] 设施汇总响应:', summaryRes)
|
|
|
+
|
|
|
+ this.facsSummaryList = (summaryRes.data || []).map(item => ({
|
|
|
+ objCode: item.objCode,
|
|
|
+ objName: item.objName,
|
|
|
+ quantity: item.quantity || 0
|
|
|
+ }))
|
|
|
+ console.log('[loadDetailData] 设施汇总数据:', this.facsSummaryList)
|
|
|
+
|
|
|
+ // 更新设施下拉选项
|
|
|
+ this.facsOptions = this.facsSummaryList.map(item => ({
|
|
|
+ objCode: item.objCode,
|
|
|
+ objName: item.objName
|
|
|
+ }))
|
|
|
+
|
|
|
+ this.detailLoading = false
|
|
|
+
|
|
|
+ // 2. 加载时段电耗数据 - 分别获取每个设施的小时数据
|
|
|
+ await this.loadHourlyDataByFacs(baseParams)
|
|
|
+
|
|
|
+ // 3. 渲染图表 - 使用更长的延迟确保DOM已渲染
|
|
|
+ this.$nextTick(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log('[loadDetailData] 准备渲染图表')
|
|
|
+ this.renderDetailChart()
|
|
|
+ }, 300)
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载详情数据失败', error)
|
|
|
+ this.$message.error('数据加载失败')
|
|
|
+ } finally {
|
|
|
+ this.detailLoading = false
|
|
|
+ this.detailChartLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分别加载每个设施的小时数据 - 关键修复
|
|
|
+ */
|
|
|
+ async loadHourlyDataByFacs(baseParams) {
|
|
|
+ console.log('[loadHourlyDataByFacs] 开始加载小时数据')
|
|
|
+
|
|
|
+ // 如果选择了具体设施,只查该设施
|
|
|
+ if (this.selectedObjCode) {
|
|
|
+ const params = { ...baseParams, objCode: this.selectedObjCode }
|
|
|
+ console.log('[loadHourlyDataByFacs] 查询单个设施:', this.selectedObjCode)
|
|
|
+
|
|
|
+ const hourlyRes = await listFacsMeter(params)
|
|
|
+ const rawData = hourlyRes.rows || []
|
|
|
+
|
|
|
+ const facs = this.facsSummaryList.find(f => f.objCode === this.selectedObjCode)
|
|
|
+ const facsName = facs ? facs.objName : this.selectedObjCode
|
|
|
+
|
|
|
+ this.hourlyDataByFacs = [{
|
|
|
+ objCode: this.selectedObjCode,
|
|
|
+ objName: facsName,
|
|
|
+ hourlyData: this.processHourlyData(rawData)
|
|
|
+ }]
|
|
|
+ console.log('[loadHourlyDataByFacs] 单设施时段数据:', this.hourlyDataByFacs)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 没有选择具体设施时,分别获取每个设施的数据
|
|
|
+ const facsListToQuery = this.facsSummaryList.length > 0 ? this.facsSummaryList : []
|
|
|
+ console.log('[loadHourlyDataByFacs] 需要查询的设施列表:', facsListToQuery)
|
|
|
+
|
|
|
+ if (facsListToQuery.length === 0) {
|
|
|
+ // 没有设施时,尝试按区域获取汇总数据
|
|
|
+ const hourlyRes = await listFacsMeter(baseParams)
|
|
|
+ const rawData = hourlyRes.rows || []
|
|
|
+
|
|
|
+ if (rawData.length > 0) {
|
|
|
+ this.hourlyDataByFacs = this.groupByAreaCode(rawData)
|
|
|
+ } else {
|
|
|
+ this.hourlyDataByFacs = []
|
|
|
+ }
|
|
|
+ console.log('[loadHourlyDataByFacs] 汇总时段数据(按区域分组):', this.hourlyDataByFacs)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 并行请求所有设施的小时数据
|
|
|
+ const hourlyPromises = facsListToQuery.map(async(facs) => {
|
|
|
+ try {
|
|
|
+ const params = { ...baseParams, objCode: facs.objCode }
|
|
|
+ console.log('[loadHourlyDataByFacs] 请求设施小时数据:', facs.objCode)
|
|
|
+
|
|
|
+ const hourlyRes = await listFacsMeter(params)
|
|
|
+ const rawData = hourlyRes.rows || []
|
|
|
+
|
|
|
+ return {
|
|
|
+ objCode: facs.objCode,
|
|
|
+ objName: facs.objName,
|
|
|
+ hourlyData: this.processHourlyData(rawData)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`加载设施 ${facs.objCode} 小时数据失败:`, error)
|
|
|
+ return {
|
|
|
+ objCode: facs.objCode,
|
|
|
+ objName: facs.objName,
|
|
|
+ hourlyData: []
|
|
|
}
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const results = await Promise.all(hourlyPromises)
|
|
|
+
|
|
|
+ // 过滤掉没有数据的设施
|
|
|
+ this.hourlyDataByFacs = results.filter(item => item.hourlyData.length > 0)
|
|
|
+ console.log('[loadHourlyDataByFacs] 多设施时段数据:', this.hourlyDataByFacs)
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理小时数据
|
|
|
+ */
|
|
|
+ processHourlyData(rawData) {
|
|
|
+ if (!rawData || rawData.length === 0) return []
|
|
|
+
|
|
|
+ return rawData
|
|
|
+ .map(item => ({
|
|
|
+ time: item.time ? item.time.substring(0, 5) : '', // "15:00:00" -> "15:00"
|
|
|
+ value: parseFloat(item.elecQuantity) || 0,
|
|
|
+ recordTime: item.recordTime
|
|
|
+ }))
|
|
|
+ .filter(item => item.time)
|
|
|
+ .sort((a, b) => a.time.localeCompare(b.time))
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按区域代码分组数据
|
|
|
+ */
|
|
|
+ groupByAreaCode(rawData) {
|
|
|
+ const grouped = {}
|
|
|
+
|
|
|
+ rawData.forEach(item => {
|
|
|
+ const areaCode = item.areaCode || 'unknown'
|
|
|
+ const areaName = item.deviceName || areaCode
|
|
|
+
|
|
|
+ if (!grouped[areaCode]) {
|
|
|
+ grouped[areaCode] = {
|
|
|
+ objCode: areaCode,
|
|
|
+ objName: areaName,
|
|
|
+ hourlyData: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ grouped[areaCode].hourlyData.push({
|
|
|
+ time: item.time ? item.time.substring(0, 5) : '',
|
|
|
+ value: parseFloat(item.elecQuantity) || 0,
|
|
|
+ recordTime: item.recordTime
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ Object.values(grouped).forEach(area => {
|
|
|
+ area.hourlyData.sort((a, b) => a.time.localeCompare(b.time))
|
|
|
+ })
|
|
|
+
|
|
|
+ return Object.values(grouped)
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设施选择变化
|
|
|
+ */
|
|
|
+ handleFacsChange() {
|
|
|
+ this.detailChartLoading = true
|
|
|
+ this.loadHourlyDataByFacs({
|
|
|
+ startRecTime: this.detailDateRange[0],
|
|
|
+ endRecTime: this.detailDateRange[1],
|
|
|
+ meterCls: 45,
|
|
|
+ areaCode: this.selectedAreaCode,
|
|
|
+ facsCategory: 'Z',
|
|
|
+ facsSubCategory: this.queryParams.facsSubCategory
|
|
|
+ }).then(() => {
|
|
|
+ this.detailChartLoading = false
|
|
|
+ this.$nextTick(() => this.renderDetailChart())
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // ==================== 图表渲染 ====================
|
|
|
+ renderOverviewChart() {
|
|
|
+ const chartDom = this.$refs.overviewChartRef
|
|
|
+ if (!chartDom || this.overviewTableData.length === 0) return
|
|
|
+ if (chartDom.offsetWidth === 0 || chartDom.offsetHeight === 0) {
|
|
|
+ setTimeout(() => this.renderOverviewChart(), 100)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.overviewChartInstance) this.overviewChartInstance.dispose()
|
|
|
+ this.overviewChartInstance = echarts.init(chartDom)
|
|
|
+
|
|
|
+ const data = this.overviewTableData.slice(0, 10).map(item => ({
|
|
|
+ name: item.objName,
|
|
|
+ value: item.quantity || 0
|
|
|
+ }))
|
|
|
+
|
|
|
+ const option = this.overviewChartType === 'pie'
|
|
|
+ ? this.getPieChartOption(data)
|
|
|
+ : this.getBarChartOption(data)
|
|
|
+
|
|
|
+ this.overviewChartInstance.setOption(option)
|
|
|
+ },
|
|
|
+
|
|
|
+ getPieChartOption(data) {
|
|
|
+ const total = data.reduce((sum, item) => sum + item.value, 0)
|
|
|
+ return {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item',
|
|
|
+ formatter: params => `
|
|
|
+ <div style="padding:8px;">
|
|
|
+ <div style="margin-bottom:4px;">${params.marker} ${params.name}</div>
|
|
|
+ <div style="font-weight:bold;">${this.formatNumber(params.value)} kW·h</div>
|
|
|
+ <div style="color:#999;">占比: ${((params.value / total) * 100).toFixed(1)}%</div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
},
|
|
|
+ legend: { type: 'scroll', orient: 'vertical', right: '5%', top: 'middle', textStyle: { fontSize: 12 } },
|
|
|
+ series: [{
|
|
|
+ name: '用电量',
|
|
|
+ type: 'pie',
|
|
|
+ radius: ['40%', '65%'],
|
|
|
+ center: ['40%', '50%'],
|
|
|
+ avoidLabelOverlap: true,
|
|
|
+ itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
|
|
|
+ label: { show: true, formatter: '{b}\n{d}%', fontSize: 11 },
|
|
|
+ labelLine: { length: 15, length2: 10 },
|
|
|
+ emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.3)' } },
|
|
|
+ data: data.map((item, index) => ({ ...item, itemStyle: { color: this.getChartColor(index) } }))
|
|
|
+ }]
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ getBarChartOption(data) {
|
|
|
+ return {
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'shadow'
|
|
|
- },
|
|
|
- formatter: (params) => {
|
|
|
- var relVal = params[0].name
|
|
|
- for (var i = 0, l = params.length; i < l; i++) {
|
|
|
- relVal =
|
|
|
- relVal +
|
|
|
- '<br/>' +
|
|
|
- params[i].marker +
|
|
|
- params[i].seriesName +
|
|
|
- ' ' +
|
|
|
- params[i].value +
|
|
|
- 'kW·h'
|
|
|
- }
|
|
|
- return relVal
|
|
|
- }
|
|
|
- },
|
|
|
- legend: {
|
|
|
- data: ['用电量']
|
|
|
+ axisPointer: { type: 'shadow' },
|
|
|
+ formatter: params => `
|
|
|
+ <div style="padding:8px;">
|
|
|
+ <div style="margin-bottom:4px;">${params[0].name}</div>
|
|
|
+ <div style="font-weight:bold;color:#4facfe;">${this.formatNumber(params[0].value)} kW·h</div>
|
|
|
+ </div>
|
|
|
+ `
|
|
|
},
|
|
|
+ grid: { left: '3%', right: '4%', bottom: '15%', top: '10%', containLabel: true },
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
- data: xAxisData
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- name: 'kW·h(千瓦时)',
|
|
|
- type: 'value',
|
|
|
+ data: data.map(item => item.name),
|
|
|
+ axisLabel: { rotate: 30, fontSize: 11, interval: 0 }
|
|
|
},
|
|
|
+ yAxis: { type: 'value', name: 'kW·h', nameTextStyle: { fontSize: 11 } },
|
|
|
series: [{
|
|
|
name: '用电量',
|
|
|
type: 'bar',
|
|
|
- data: seriesData,
|
|
|
- barWidth: 20,
|
|
|
+ data: data.map(item => item.value),
|
|
|
+ barMaxWidth: 40,
|
|
|
itemStyle: {
|
|
|
- normal: {
|
|
|
- color: '#6395FA'
|
|
|
- }
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: '#4facfe' },
|
|
|
+ { offset: 1, color: '#00f2fe' }
|
|
|
+ ]),
|
|
|
+ borderRadius: [4, 4, 0, 0]
|
|
|
}
|
|
|
}]
|
|
|
- };
|
|
|
+ }
|
|
|
},
|
|
|
- powerLineOptions () {
|
|
|
- const xAxisData = this.powerChartData.map(item => `${item.date.substr(5)} ${item.time.substr(0, 5)} `);
|
|
|
- const yData1 = this.powerChartData.map(item => parseFloat(item.s).toFixed(2));
|
|
|
- const yData2 = this.powerChartData.map(item => item.p);
|
|
|
- const yData3 = this.powerChartData.map(item => item.q);
|
|
|
- return {
|
|
|
- toolbox: {
|
|
|
- itemGap: 10,
|
|
|
- itemSize: 16,
|
|
|
- right: 10,
|
|
|
- top: 0,
|
|
|
- show: true,
|
|
|
- feature: {
|
|
|
- magicType: {
|
|
|
- show: true,
|
|
|
- type: ['bar', 'line']
|
|
|
- },
|
|
|
- saveAsImage: {
|
|
|
- show: true
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 渲染详情图表 - 多设施对比(核心修复版)
|
|
|
+ */
|
|
|
+ renderDetailChart() {
|
|
|
+ console.log('[renderDetailChart] 开始渲染, activeName:', this.activeName)
|
|
|
+
|
|
|
+ // 使用修复后的方法获取DOM
|
|
|
+ const chartDom = this.getDetailChartDom()
|
|
|
+
|
|
|
+ if (!chartDom) {
|
|
|
+ console.warn('[renderDetailChart] 图表DOM不存在, activeName:', this.activeName)
|
|
|
+ console.log('[renderDetailChart] 当前所有refs:', Object.keys(this.$refs))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[renderDetailChart] chartDom:', chartDom)
|
|
|
+ console.log('[renderDetailChart] hourlyDataByFacs.length:', this.hourlyDataByFacs.length)
|
|
|
+
|
|
|
+ if (this.hourlyDataByFacs.length === 0) {
|
|
|
+ console.warn('[renderDetailChart] 无时段数据')
|
|
|
+ if (this.detailChartInstance) {
|
|
|
+ this.detailChartInstance.dispose()
|
|
|
+ this.detailChartInstance = null
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查DOM尺寸
|
|
|
+ const width = chartDom.offsetWidth
|
|
|
+ const height = chartDom.offsetHeight
|
|
|
+ console.log('[renderDetailChart] DOM尺寸:', width, 'x', height)
|
|
|
+
|
|
|
+ if (!width || !height) {
|
|
|
+ console.log('[renderDetailChart] DOM尺寸为0,延迟200ms重试')
|
|
|
+ setTimeout(() => this.renderDetailChart(), 200)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 销毁旧实例
|
|
|
+ if (this.detailChartInstance) {
|
|
|
+ this.detailChartInstance.dispose()
|
|
|
+ this.detailChartInstance = null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化新实例
|
|
|
+ this.detailChartInstance = echarts.init(chartDom)
|
|
|
+ console.log('[renderDetailChart] ECharts实例已创建')
|
|
|
+
|
|
|
+ // 收集所有时间点并排序
|
|
|
+ const allTimes = new Set()
|
|
|
+ this.hourlyDataByFacs.forEach(facs => {
|
|
|
+ facs.hourlyData.forEach(d => allTimes.add(d.time))
|
|
|
+ })
|
|
|
+ const xAxisData = Array.from(allTimes).sort()
|
|
|
+
|
|
|
+ console.log('[renderDetailChart] X轴时间点:', xAxisData)
|
|
|
+
|
|
|
+ // 构建多系列数据
|
|
|
+ const legendData = this.hourlyDataByFacs.map(f => f.objName)
|
|
|
+ const series = this.hourlyDataByFacs.map((facs, index) => {
|
|
|
+ const baseColor = this.getSeriesColor(index)
|
|
|
+
|
|
|
+ // 将小时数据转为Map
|
|
|
+ const hourlyMap = new Map()
|
|
|
+ facs.hourlyData.forEach(d => hourlyMap.set(d.time, d.value))
|
|
|
+
|
|
|
+ // 按统一时间轴生成数据
|
|
|
+ const seriesData = xAxisData.map(time => hourlyMap.get(time) || 0)
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: facs.objName,
|
|
|
+ type: this.detailChartType,
|
|
|
+ stack: this.showStacked ? 'total' : null,
|
|
|
+ data: seriesData,
|
|
|
+ smooth: true,
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 6,
|
|
|
+ barMaxWidth: 30,
|
|
|
+ barGap: '10%',
|
|
|
+ itemStyle: {
|
|
|
+ color: this.detailChartType === 'bar'
|
|
|
+ ? new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: baseColor },
|
|
|
+ { offset: 1, color: this.adjustColorAlpha(baseColor, 0.6) }
|
|
|
+ ])
|
|
|
+ : baseColor,
|
|
|
+ borderRadius: this.detailChartType === 'bar' ? [4, 4, 0, 0] : 0
|
|
|
+ },
|
|
|
+ lineStyle: this.detailChartType === 'line' ? { width: 2, color: baseColor } : undefined,
|
|
|
+ areaStyle: (this.detailChartType === 'line' && this.showStacked) ? {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: this.adjustColorAlpha(baseColor, 0.4) },
|
|
|
+ { offset: 1, color: this.adjustColorAlpha(baseColor, 0.05) }
|
|
|
+ ])
|
|
|
+ } : undefined
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const option = {
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'shadow'
|
|
|
- },
|
|
|
+ axisPointer: { type: this.detailChartType === 'bar' ? 'shadow' : 'cross' },
|
|
|
+ backgroundColor: 'rgba(50, 50, 50, 0.9)',
|
|
|
+ borderColor: 'transparent',
|
|
|
+ textStyle: { color: '#fff' },
|
|
|
formatter: (params) => {
|
|
|
- var relVal = params[0].name
|
|
|
- for (var i = 0, l = params.length; i < l; i++) {
|
|
|
- relVal =
|
|
|
- relVal +
|
|
|
- '<br/>' +
|
|
|
- params[i].marker +
|
|
|
- params[i].seriesName +
|
|
|
- ' ' +
|
|
|
- params[i].value +
|
|
|
- 'kW'
|
|
|
+ let html = `<div style="padding:10px;">
|
|
|
+ <div style="margin-bottom:8px;font-weight:600;border-bottom:1px solid rgba(255,255,255,0.2);padding-bottom:6px;">
|
|
|
+ ${params[0].axisValue}
|
|
|
+ </div>`
|
|
|
+
|
|
|
+ let total = 0
|
|
|
+ params.forEach(p => {
|
|
|
+ total += p.value || 0
|
|
|
+ html += `<div style="margin:6px 0;display:flex;justify-content:space-between;align-items:center;">
|
|
|
+ <span>${p.marker} ${p.seriesName}</span>
|
|
|
+ <span style="font-weight:600;margin-left:20px;">${this.formatNumber(p.value)} kW·h</span>
|
|
|
+ </div>`
|
|
|
+ })
|
|
|
+
|
|
|
+ if (params.length > 1) {
|
|
|
+ html += `<div style="margin-top:8px;padding-top:6px;border-top:1px solid rgba(255,255,255,0.2);display:flex;justify-content:space-between;">
|
|
|
+ <span>合计</span>
|
|
|
+ <span style="font-weight:700;color:#4facfe;">${this.formatNumber(total)} kW·h</span>
|
|
|
+ </div>`
|
|
|
}
|
|
|
- return relVal
|
|
|
+
|
|
|
+ html += '</div>'
|
|
|
+ return html
|
|
|
}
|
|
|
},
|
|
|
- xAxis: {
|
|
|
- type: 'category',
|
|
|
- data: xAxisData
|
|
|
- },
|
|
|
- yAxis: {
|
|
|
- name: 'kW(千瓦)',
|
|
|
- type: 'value',
|
|
|
- },
|
|
|
- legend: {},
|
|
|
- dataZoom: [
|
|
|
- {
|
|
|
- type: "slider",
|
|
|
- start: 0,
|
|
|
- end: 100,
|
|
|
- height: 10,
|
|
|
- bottom: '10%',
|
|
|
- showDetail: false,
|
|
|
- showDataShadow: false,
|
|
|
- borderColor: "transparent"
|
|
|
- },
|
|
|
- {
|
|
|
- type: "inside",
|
|
|
- start: 0,
|
|
|
- end: 100,
|
|
|
- },
|
|
|
- ],
|
|
|
- series: [{
|
|
|
- name: '视在功率',
|
|
|
- type: 'line',
|
|
|
- data: yData1,
|
|
|
- markLine: {
|
|
|
- symbol: 'none',
|
|
|
- silent: true,
|
|
|
- lineStyle: {normal: {type: 'dashed'}},
|
|
|
- label: {position: 'end'},
|
|
|
- data: [
|
|
|
- {
|
|
|
- yAxis: this.powerMaxLoad,
|
|
|
- lineStyle: {width: 1.656, color: '#ff6367'},
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- textStyle: {
|
|
|
- color: '#ff6367'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- ]
|
|
|
- },
|
|
|
- markPoint: {
|
|
|
- silent: true,
|
|
|
- data: [
|
|
|
- {
|
|
|
- yAxis: this.powerMaxLoad,
|
|
|
- x: '100%',
|
|
|
- symbolSize: 0.1,
|
|
|
- symbolOffset: [0, 0],
|
|
|
- label: {
|
|
|
- textStyle: {color: '#ff6367'},
|
|
|
- fontSize: 12,
|
|
|
- position: 'left',
|
|
|
- formatter: '最高负荷'
|
|
|
- }
|
|
|
- },
|
|
|
- ]
|
|
|
- },
|
|
|
+ legend: {
|
|
|
+ data: legendData,
|
|
|
+ top: 5,
|
|
|
+ textStyle: { fontSize: 12, color: '#606266' },
|
|
|
+ itemWidth: 20,
|
|
|
+ itemHeight: 10
|
|
|
},
|
|
|
- {
|
|
|
- name: '有功功率',
|
|
|
- type: 'line',
|
|
|
- data: yData2,
|
|
|
- },
|
|
|
- {
|
|
|
- name: '无功功率',
|
|
|
- type: 'line',
|
|
|
- data: yData3,
|
|
|
- },
|
|
|
- ]
|
|
|
- };
|
|
|
- },
|
|
|
- equipPowerLineOptions () {
|
|
|
- const xAxisData = this.equipPowerChartData.map(item => `${item.date.substr(5)} ${item.time.substr(0, 5)} `);
|
|
|
- const yData1 = this.equipPowerChartData.map(item => parseFloat(item.s).toFixed(2));
|
|
|
- const yData2 = this.equipPowerChartData.map(item => item.p);
|
|
|
- const yData3 = this.equipPowerChartData.map(item => item.q);
|
|
|
- return {
|
|
|
toolbox: {
|
|
|
- itemGap: 10,
|
|
|
- itemSize: 16,
|
|
|
right: 10,
|
|
|
top: 0,
|
|
|
- show: true,
|
|
|
+ itemSize: 16,
|
|
|
feature: {
|
|
|
- magicType: {
|
|
|
- show: true,
|
|
|
- type: ['bar', 'line']
|
|
|
- },
|
|
|
- saveAsImage: {
|
|
|
- show: true
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- legend: {},
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis',
|
|
|
- axisPointer: {
|
|
|
- type: 'shadow'
|
|
|
- },
|
|
|
- formatter: (params) => {
|
|
|
- var relVal = params[0].name
|
|
|
- for (var i = 0, l = params.length; i < l; i++) {
|
|
|
- relVal =
|
|
|
- relVal +
|
|
|
- '<br/>' +
|
|
|
- params[i].marker +
|
|
|
- params[i].seriesName +
|
|
|
- ' ' +
|
|
|
- params[i].value +
|
|
|
- 'kW'
|
|
|
- }
|
|
|
- return relVal
|
|
|
+ magicType: { show: true, type: ['bar', 'line'] },
|
|
|
+ saveAsImage: { show: true, name: '设施时段电耗对比' }
|
|
|
}
|
|
|
},
|
|
|
+ grid: { left: '3%', right: '4%', bottom: '10%', top: '18%', containLabel: true },
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
- data: xAxisData
|
|
|
+ data: xAxisData,
|
|
|
+ axisLine: { lineStyle: { color: '#e0e0e0' } },
|
|
|
+ axisTick: { show: false },
|
|
|
+ axisLabel: { fontSize: 11, color: '#606266', rotate: xAxisData.length > 12 ? 45 : 0 }
|
|
|
},
|
|
|
yAxis: {
|
|
|
- name: 'kW(千瓦)',
|
|
|
+ name: 'kW·h(千瓦时)',
|
|
|
type: 'value',
|
|
|
+ nameTextStyle: { fontSize: 11, color: '#909399' },
|
|
|
+ axisLine: { show: false },
|
|
|
+ axisTick: { show: false },
|
|
|
+ splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } },
|
|
|
+ axisLabel: { fontSize: 11, color: '#606266' }
|
|
|
},
|
|
|
- dataZoom: [
|
|
|
- {
|
|
|
- type: "slider",
|
|
|
- start: 0,
|
|
|
- end: 100,
|
|
|
- height: 10,
|
|
|
- bottom: '10%',
|
|
|
- showDetail: false,
|
|
|
- showDataShadow: false,
|
|
|
- borderColor: "transparent"
|
|
|
- },
|
|
|
- {
|
|
|
- type: "inside",
|
|
|
- start: 0,
|
|
|
- end: 100,
|
|
|
- },
|
|
|
- ],
|
|
|
- series: [{
|
|
|
- name: '视在功率',
|
|
|
- type: 'line',
|
|
|
- data: yData1,
|
|
|
- markLine: {
|
|
|
- symbol: 'none',
|
|
|
- silent: true,
|
|
|
- lineStyle: {normal: {type: 'dashed'}},
|
|
|
- label: {position: 'end'},
|
|
|
- data: [
|
|
|
- {
|
|
|
- yAxis: this.equipPowerMaxLoad,
|
|
|
- lineStyle: {width: 1.656, color: '#ff6367'},
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- textStyle: {
|
|
|
- color: '#ff6367'
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- ]
|
|
|
- },
|
|
|
- markPoint: {
|
|
|
- silent: true,
|
|
|
- data: [
|
|
|
- {
|
|
|
- yAxis: this.equipPowerMaxLoad,
|
|
|
- x: '100%',
|
|
|
- symbolSize: 0.1,
|
|
|
- symbolOffset: [0, 0],
|
|
|
- label: {
|
|
|
- textStyle: {color: '#ff6367'},
|
|
|
- fontSize: 12,
|
|
|
- position: 'left',
|
|
|
- formatter: '最高负荷'
|
|
|
- }
|
|
|
- },
|
|
|
- ]
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: '有功功率',
|
|
|
- type: 'line',
|
|
|
- data: yData2,
|
|
|
- },
|
|
|
- {
|
|
|
- name: '无功功率',
|
|
|
- type: 'line',
|
|
|
- data: yData3,
|
|
|
- },
|
|
|
- ]
|
|
|
- };
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- // 新增方法:判断文本是否溢出
|
|
|
- isTextOverflow(text) {
|
|
|
- // 简单判断:超过一定字符数就显示tooltip
|
|
|
- // 可以根据实际情况调整这个数值
|
|
|
- return text && text.length > 8
|
|
|
- },
|
|
|
- /**平均功率-15分钟*/
|
|
|
- getPowerChart () {
|
|
|
- this.powerMaxLoad = ''
|
|
|
- this.powerChartData = []
|
|
|
- const [startRecTime, endRecTime] = this.powerDate
|
|
|
- const params = {
|
|
|
- startRecTime,
|
|
|
- endRecTime,
|
|
|
- areaCode: '-1',
|
|
|
- objType: '2',
|
|
|
+ series: series
|
|
|
}
|
|
|
- getPowerData({
|
|
|
- ...params,
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 999
|
|
|
- }).then(({code, rows}) => {
|
|
|
- if (code === 200) {
|
|
|
- this.powerChartData = rows
|
|
|
- }
|
|
|
- })
|
|
|
- getPowerMaxLoad(params).then(({code, data}) => {
|
|
|
- if (code === 200 && data) {
|
|
|
- this.powerMaxLoad = parseFloat(data.s).toFixed(2)
|
|
|
- }
|
|
|
- })
|
|
|
+
|
|
|
+ this.detailChartInstance.setOption(option)
|
|
|
+ console.log('[renderDetailChart] 图表渲染完成,系列数:', series.length, '时间点:', xAxisData.length)
|
|
|
},
|
|
|
- getEquipPowerChart () {
|
|
|
- this.equipPowerMaxLoad = ''
|
|
|
- this.equipPowerChartData = []
|
|
|
- const [startRecTime, endRecTime] = this.dateRange
|
|
|
- const params = {
|
|
|
- startRecTime,
|
|
|
- endRecTime,
|
|
|
- areaCode: '-1',
|
|
|
- objType: '2',
|
|
|
- facsCategory: 'Z',
|
|
|
- facsSubCategory: this.activeName,
|
|
|
- objCode: this.objCode
|
|
|
- }
|
|
|
- getPowerData({
|
|
|
- ...params,
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 999
|
|
|
- }).then(({code, rows}) => {
|
|
|
- if (code === 200) {
|
|
|
- this.equipPowerChartData = rows
|
|
|
- }
|
|
|
- })
|
|
|
- getPowerMaxLoad(params).then(({code, data}) => {
|
|
|
- if (code === 200) {
|
|
|
- this.equipPowerMaxLoad = parseFloat(data).toFixed(2)
|
|
|
- }
|
|
|
- })
|
|
|
+
|
|
|
+ // ==================== 颜色相关 ====================
|
|
|
+ getChartColor(index) {
|
|
|
+ const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140', '#30cfd0', '#667eea', '#f093fb', '#f5576c', '#00f2fe', '#38f9d7', '#f77062', '#fe5196']
|
|
|
+ return colors[index % colors.length]
|
|
|
},
|
|
|
- /** 查询用能计量-小时列表 */
|
|
|
- getList () {
|
|
|
- this.loading = true
|
|
|
- this.queryParams.objCode = this.objCode
|
|
|
- const params = {
|
|
|
- startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- meterCls:45,
|
|
|
- areaCode: this.queryParams.areaCode,
|
|
|
- facsCategory:this.queryParams.facsCategory,
|
|
|
- facsSubCategory: this.activeName,
|
|
|
- };
|
|
|
- listFacsMeter(params).then(response => {
|
|
|
- this.hList = response.rows
|
|
|
- this.total = response.total
|
|
|
- this.loading = false
|
|
|
- })
|
|
|
- this.getsumByFacsH()
|
|
|
- this.getEquipPowerChart()
|
|
|
- },
|
|
|
- /**根据设施查询能耗统计 */
|
|
|
- getsumByFacsH () {
|
|
|
- const params = {
|
|
|
- startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- meterCls:45,
|
|
|
- areaCode: this.queryParams.areaCode,
|
|
|
- facsCategory:this.queryParams.facsCategory,
|
|
|
- facsSubCategory: this.activeName,
|
|
|
- };
|
|
|
- listByFacs(params).then(response => {
|
|
|
- this.sumByFacsList = response.data.map(item => ({
|
|
|
- objCode: item.objCode,
|
|
|
- objName: item.objName,
|
|
|
- quantity: item.quantity || 0,
|
|
|
- }));
|
|
|
- console.log(" this.sumByFacsList ", this.sumByFacsList )
|
|
|
- })
|
|
|
+
|
|
|
+ getSeriesColor(index) {
|
|
|
+ const colors = ['#4facfe', '#43e97b', '#fa709a', '#fee140', '#667eea', '#30cfd0', '#f093fb', '#f5576c']
|
|
|
+ return colors[index % colors.length]
|
|
|
},
|
|
|
|
|
|
- /**时间范围切换按钮点击事件处理器*/
|
|
|
- changeTimeRange (rangeType) {
|
|
|
- this.selectedTimeRange = rangeType;
|
|
|
- let start = dayjs();
|
|
|
- let end = dayjs();
|
|
|
+ adjustColorAlpha(hex, alpha) {
|
|
|
+ const r = parseInt(hex.slice(1, 3), 16)
|
|
|
+ const g = parseInt(hex.slice(3, 5), 16)
|
|
|
+ const b = parseInt(hex.slice(5, 7), 16)
|
|
|
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
|
|
+ },
|
|
|
|
|
|
- if (rangeType === 'day') {
|
|
|
- // 日:当前日期的 00:00:00 到 23:59:59
|
|
|
- start = start.startOf('day');
|
|
|
- end = end.endOf('day');
|
|
|
- } else if (rangeType === 'month') {
|
|
|
- // 月:当前月份的第一天 00:00:00 到 最后一天 23:59:59
|
|
|
- start = start.date(1).hour(0).minute(0).second(0);
|
|
|
- end = end.endOf('month').hour(23).minute(59).second(59);
|
|
|
- } else if (rangeType === 'year') {
|
|
|
- // 年:当前年份的第一天 00:00:00 到 最后一天 23:59:59
|
|
|
- start = start.startOf('year');
|
|
|
- end = end.endOf('year').hour(23).minute(59).second(59);
|
|
|
- }
|
|
|
- this.dateRange = [start.format('YYYY-MM-DD HH:mm:ss'), end.format('YYYY-MM-DD HH:mm:ss')];
|
|
|
- this.getSumBySubCategoryH(); // 重新获取数据
|
|
|
- },
|
|
|
-
|
|
|
- /**总览饼图*/
|
|
|
- getSumBySubCategoryH () {
|
|
|
- const params = {
|
|
|
- startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- areaCode: this.queryParams.areaCode,
|
|
|
- meterCls:45,
|
|
|
- facsCategory:this.queryParams.facsCategory,
|
|
|
- };
|
|
|
- listByFacs(params).then(response => {
|
|
|
- console.log("pie图", response.data)
|
|
|
- this.tableData = response.data.map(item => ({
|
|
|
- name: item.objName,
|
|
|
- value: item.quantity || 0
|
|
|
- }))
|
|
|
- console.log("能耗总览",this.tableData)
|
|
|
- this.processDataForChart(response.data); // 处理数据并生成图表配置
|
|
|
- })
|
|
|
+ getProgressColor(percentage) {
|
|
|
+ if (percentage >= 30) return '#f56c6c'
|
|
|
+ if (percentage >= 15) return '#e6a23c'
|
|
|
+ return '#409eff'
|
|
|
+ },
|
|
|
+
|
|
|
+ // ==================== 事件处理 ====================
|
|
|
+ handleTabClick(tab) {
|
|
|
+ console.log('[handleTabClick] 切换Tab:', tab.name)
|
|
|
+
|
|
|
+ // 销毁旧图表实例
|
|
|
+ if (this.detailChartInstance) {
|
|
|
+ this.detailChartInstance.dispose()
|
|
|
+ this.detailChartInstance = null
|
|
|
+ }
|
|
|
+
|
|
|
+ if (tab.name === 'summary') {
|
|
|
+ this.$nextTick(() => this.loadOverviewData())
|
|
|
+ } else {
|
|
|
+ this.queryParams.facsSubCategory = tab.name
|
|
|
+ console.log('[handleTabClick] 切换到设施分类:', tab.name)
|
|
|
+
|
|
|
+ this.resetDetailState()
|
|
|
+ this.loadAreaTree()
|
|
|
|
|
|
+ // 增加延迟确保 tab-pane 完全渲染
|
|
|
+ this.$nextTick(() => {
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log('[handleTabClick] 开始加载详情数据')
|
|
|
+ this.loadDetailData()
|
|
|
+ }, 400)
|
|
|
+ })
|
|
|
+ }
|
|
|
},
|
|
|
- processDataForChart(data) {
|
|
|
- console.log("data", data);
|
|
|
- this.totalElecQuantity = data.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
|
|
- // 处理数据,生成图表配置
|
|
|
- const seriesData = data.map(item => ({
|
|
|
- name: item.objName,
|
|
|
- value: item.quantity || 0,
|
|
|
- percent: ((item.quantity || 0) / this.totalElecQuantity * 100).toFixed(2),
|
|
|
- }));
|
|
|
|
|
|
- console.log("seriesData", seriesData);
|
|
|
+ resetDetailState() {
|
|
|
+ this.selectedAreaCode = '-1'
|
|
|
+ this.selectedAreaLabel = '全部'
|
|
|
+ this.selectedObjCode = null
|
|
|
+ this.facsOptions = []
|
|
|
+ this.facsSummaryList = []
|
|
|
+ this.hourlyDataByFacs = []
|
|
|
+ this.detailDateRange = [
|
|
|
+ dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ ]
|
|
|
+ },
|
|
|
|
|
|
- // 设置图表配置
|
|
|
- this.sumBySubCategoryChartOption = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'item',
|
|
|
- formatter: function(params) {
|
|
|
- const { name, value, percent } = params;
|
|
|
- return `<div><h4>${name}</h4><p>${value}kW·h (${percent}%)</p></div>`;
|
|
|
- }
|
|
|
- },
|
|
|
- legend: {
|
|
|
- orient: 'vertical',
|
|
|
- top: '5%',
|
|
|
- left: '5%'
|
|
|
- },
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '能耗',
|
|
|
- type: 'pie',
|
|
|
- radius: ['35%', '55%'],
|
|
|
- data: seriesData,
|
|
|
- emphasis: {
|
|
|
- itemStyle: {
|
|
|
- shadowBlur: 10,
|
|
|
- shadowOffsetX: 0,
|
|
|
- shadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
- }
|
|
|
- },
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- position: 'outside',
|
|
|
- formatter: '{b}\n{d}%',
|
|
|
- },
|
|
|
- labelLine: {
|
|
|
- show: true,
|
|
|
- length: 30, // 标签线长度
|
|
|
- lineStyle: {
|
|
|
- width: 1,
|
|
|
- type: 'dashed', // 设置虚线样式
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- ]
|
|
|
- };
|
|
|
+ handleAreaNodeClick(data) {
|
|
|
+ this.selectedAreaCode = data.id
|
|
|
+ this.selectedAreaLabel = data.label
|
|
|
+ this.selectedObjCode = null
|
|
|
+ this.loadDetailData()
|
|
|
+ },
|
|
|
|
|
|
- // 渲染图表
|
|
|
- this.$nextTick(() => {
|
|
|
- const chart = echarts.init(this.$refs.sumBySubCategoryChart);
|
|
|
- chart.setOption(this.sumBySubCategoryChartOption);
|
|
|
- });
|
|
|
+ filterAreaTree() {
|
|
|
+ const refName = 'areaTree_' + this.activeName
|
|
|
+ const treeRef = this.$refs[refName]
|
|
|
+ const tree = Array.isArray(treeRef) ? treeRef[0] : treeRef
|
|
|
+ if (tree) {
|
|
|
+ tree.filter(this.areaKeyword)
|
|
|
+ }
|
|
|
},
|
|
|
- async getFacsCategory (code) {
|
|
|
- getFacsCategorygetByCode(code).then(response => {
|
|
|
- this.facsCategoryOptions = response.data.subtypeList
|
|
|
- })
|
|
|
+
|
|
|
+ filterNode(value, data) {
|
|
|
+ if (!value) return true
|
|
|
+ return data.label && data.label.indexOf(value) !== -1
|
|
|
},
|
|
|
- async getAreaTree (category, subCategory) {
|
|
|
- await areaTreeByFacsCategory(category, subCategory, false).then(response => {
|
|
|
- this.areaOptions = [{
|
|
|
- id: '-1',
|
|
|
- label: '全部',
|
|
|
- children: []
|
|
|
- }].concat(response.data)
|
|
|
- })
|
|
|
+
|
|
|
+ // ==================== 表格相关 ====================
|
|
|
+ getRankClass(index) {
|
|
|
+ if (index === 0) return 'rank-first'
|
|
|
+ if (index === 1) return 'rank-second'
|
|
|
+ if (index === 2) return 'rank-third'
|
|
|
+ return 'rank-normal'
|
|
|
+ },
|
|
|
+
|
|
|
+ getPercentage(value) {
|
|
|
+ const total = this.overviewSummary.totalQuantity || 0
|
|
|
+ if (total === 0) return 0
|
|
|
+ return ((value || 0) / total) * 100
|
|
|
},
|
|
|
- async getFacsObj (areaCode, category, subCategory) {
|
|
|
- this.queryObjParams.refArea = areaCode
|
|
|
- this.queryObjParams.facsCategory = category
|
|
|
- this.queryObjParams.subCategory = subCategory
|
|
|
-
|
|
|
- // listAllFacs(this.queryObjParams).then(response => {
|
|
|
- // this.objOptions = response.data
|
|
|
- // })
|
|
|
- const params = {
|
|
|
- startRecTime: dayjs(this.dateRange[0]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- endRecTime: dayjs(this.dateRange[1]).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
- meterCls:45,
|
|
|
- areaCode: this.queryParams.areaCode,
|
|
|
- facsCategory:this.queryParams.facsCategory,
|
|
|
- facsSubCategory: this.activeName,
|
|
|
- };
|
|
|
- listByFacs(params).then(response => {
|
|
|
- this.objOptions = response.data
|
|
|
- console.log(" this.objOptions ",this.objOptions )
|
|
|
+
|
|
|
+ getFacsPercentage(value) {
|
|
|
+ const total = this.facsSummaryTotal || 0
|
|
|
+ if (total === 0) return 0
|
|
|
+ return ((value || 0) / total) * 100
|
|
|
+ },
|
|
|
+
|
|
|
+ getOverviewSummaries({ columns }) {
|
|
|
+ const sums = []
|
|
|
+ columns.forEach((column, index) => {
|
|
|
+ if (index === 0) {
|
|
|
+ sums[index] = ''
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (index === 1) {
|
|
|
+ sums[index] = '合计'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (column.property === 'quantity') {
|
|
|
+ sums[index] = this.formatNumber(this.overviewSummary.totalQuantity)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ sums[index] = ''
|
|
|
})
|
|
|
+ return sums
|
|
|
},
|
|
|
- tabClick () {
|
|
|
- this.reset()
|
|
|
- if (this.activeName !== 'summery') {
|
|
|
- this.dateRange = [dayjs().format(DateTool.DateFormat.YYYY_MM_DD_00_00_00), dayjs().format(DateTool.DateFormat.YYYY_MM_DD_23_59_59)]
|
|
|
- this.queryParams.facsCategory = this.facsCategory
|
|
|
- this.queryParams.facsSubCategory = this.activeName
|
|
|
- this.queryParams.pageNum = 1
|
|
|
- this.selectedLabel = '全部'
|
|
|
- this.getList()
|
|
|
- } else {
|
|
|
|
|
|
- }
|
|
|
+ getFacsSummaries({ columns }) {
|
|
|
+ const sums = []
|
|
|
+ columns.forEach((column, index) => {
|
|
|
+ if (index === 0) {
|
|
|
+ sums[index] = '合计'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (index === 1) {
|
|
|
+ sums[index] = `${this.facsSummaryList.length} 个设施`
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (index === 2) {
|
|
|
+ sums[index] = this.formatNumber(this.facsSummaryTotal)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ sums[index] = ''
|
|
|
+ })
|
|
|
+ return sums
|
|
|
},
|
|
|
- /** 搜索按钮操作 */
|
|
|
- handleQuery () {
|
|
|
- this.queryParams.pageNum = 1
|
|
|
- this.getList()
|
|
|
- this.getsumByFacsH()
|
|
|
+
|
|
|
+ getTreeNodeIcon(data) {
|
|
|
+ if (data.id === '-1') return 'el-icon-s-home'
|
|
|
+ if (data.children && data.children.length > 0) return 'el-icon-folder'
|
|
|
+ return 'el-icon-place'
|
|
|
},
|
|
|
- handleObjSelectClick () {
|
|
|
- this.getFacsObj(this.queryParams.areaCode, this.facsCategory, this.activeName)
|
|
|
+
|
|
|
+ // ==================== 生命周期 ====================
|
|
|
+ handleResize() {
|
|
|
+ if (this.overviewChartInstance) this.overviewChartInstance.resize()
|
|
|
+ if (this.detailChartInstance) this.detailChartInstance.resize()
|
|
|
},
|
|
|
- // 筛选节点
|
|
|
- filterNode (value, data) {
|
|
|
- if (!value) return true
|
|
|
- return data.label.indexOf(value) !== -1
|
|
|
- },
|
|
|
- // 节点单击事件
|
|
|
- handleNodeClick (data) {
|
|
|
- this.queryParams.areaCode = data.id
|
|
|
- this.selectedLabel = data.label
|
|
|
- this.objCode = null
|
|
|
- this.handleQuery()
|
|
|
- },
|
|
|
- reset () {
|
|
|
- this.objCode = null
|
|
|
- this.queryParams.areaCode = '-1'
|
|
|
- this.queryParams.facsCategory = 'Z'
|
|
|
- this.queryParams.facsSubCategory = null
|
|
|
- this.queryParams.objCode = null
|
|
|
- this.objOptions = []
|
|
|
+
|
|
|
+ disposeCharts() {
|
|
|
+ if (this.overviewChartInstance) {
|
|
|
+ this.overviewChartInstance.dispose()
|
|
|
+ this.overviewChartInstance = null
|
|
|
+ }
|
|
|
+ if (this.detailChartInstance) {
|
|
|
+ this.detailChartInstance.dispose()
|
|
|
+ this.detailChartInstance = null
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
+
|
|
|
<style lang="scss" scoped>
|
|
|
-.time-range-buttons {
|
|
|
- .is-active {
|
|
|
- background-color: #409eff;
|
|
|
- color: white;
|
|
|
+.power-use-container {
|
|
|
+ padding: 20px;
|
|
|
+ background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
|
|
|
+ min-height: calc(100vh - 84px);
|
|
|
+
|
|
|
+ .main-tabs {
|
|
|
+ ::v-deep .el-tabs__header {
|
|
|
+ background: #fff;
|
|
|
+ padding: 0 20px;
|
|
|
+ border-radius: 8px 8px 0 0;
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-tabs__content {
|
|
|
+ background: #fff;
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 0 0 8px 8px;
|
|
|
+ min-height: 600px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep .el-tabs__item {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ &.is-active {
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.first-content {
|
|
|
- display: flex;
|
|
|
- margin-top: 20px;
|
|
|
+ .summary-section {
|
|
|
+ .time-range-bar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+
|
|
|
+ .time-buttons .el-button {
|
|
|
+ margin-right: 8px;
|
|
|
+ border-radius: 6px;
|
|
|
+
|
|
|
+ &.is-active {
|
|
|
+ background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
|
|
|
+ border-color: #409eff;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-display {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-cards {
|
|
|
+ margin-bottom: 20px;
|
|
|
+
|
|
|
+ .stat-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 18px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ cursor: default;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-3px);
|
|
|
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-icon {
|
|
|
+ width: 48px;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-right: 14px;
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 22px;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-info {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .card-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-value {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #303133;
|
|
|
+
|
|
|
+ .value-suffix {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #606266;
|
|
|
+ margin: 0 2px;
|
|
|
+ padding: 1px 6px;
|
|
|
+ background: rgba(0, 0, 0, 0.05);
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ small {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 400;
|
|
|
+ color: #909399;
|
|
|
+ margin-left: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- >div:first-child {
|
|
|
- flex: 3;
|
|
|
+ &.primary-card .card-icon {
|
|
|
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.warning-card .card-icon {
|
|
|
+ background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.info-card .card-icon {
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.success-card .card-icon {
|
|
|
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .content-row {
|
|
|
+ .chart-panel, .table-panel {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .panel-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 14px 16px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
|
|
+
|
|
|
+ .panel-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin: 0;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 8px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-container, .table-container {
|
|
|
+ padding: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- >div:last-child {
|
|
|
- flex: 2;
|
|
|
+ .tree-panel {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .tree-search {
|
|
|
+ padding: 12px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tree-content {
|
|
|
+ max-height: calc(100vh - 280px);
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 8px;
|
|
|
+
|
|
|
+ .tree-node-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 4px 0;
|
|
|
+
|
|
|
+ .node-icon {
|
|
|
+ margin-right: 8px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .node-label {
|
|
|
+ font-size: 13px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background: #c1c1c1;
|
|
|
+ border-radius: 3px;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.container-block {
|
|
|
- padding-left: 20px;
|
|
|
-}
|
|
|
+ .detail-content {
|
|
|
+ .filter-section {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
|
|
|
-.ctl-container {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
-}
|
|
|
+ .filter-left .current-area {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #409eff;
|
|
|
|
|
|
-/* 树容器样式 - 统一美化设计 */
|
|
|
-.tree-container {
|
|
|
- height: calc(100vh - 200px);
|
|
|
- overflow-y: auto;
|
|
|
- border: 1px solid #e8e8e8;
|
|
|
- border-radius: 4px;
|
|
|
- padding: 10px;
|
|
|
- background-color: #fafafa;
|
|
|
-}
|
|
|
+ i {
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-/* 树节点样式 */
|
|
|
-.tree-node {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- width: 100%;
|
|
|
- padding: 2px 0;
|
|
|
- transition: all 0.3s;
|
|
|
- cursor: pointer;
|
|
|
-}
|
|
|
+ .filter-right {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-.node-icon {
|
|
|
- margin-right: 8px;
|
|
|
- font-size: 16px;
|
|
|
- transition: all 0.3s;
|
|
|
- color: #409EFF;
|
|
|
-}
|
|
|
+ .detail-table-section, .detail-chart-section {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 10px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ overflow: hidden;
|
|
|
|
|
|
-.node-label {
|
|
|
- flex: 1;
|
|
|
- font-size: 14px;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
-}
|
|
|
+ .section-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ background: linear-gradient(180deg, #fafbfc 0%, #fff 100%);
|
|
|
|
|
|
-/* 节点hover效果 */
|
|
|
-.tree-node:hover {
|
|
|
- background-color: #f0f7ff;
|
|
|
- border-radius: 4px;
|
|
|
- padding-left: 4px;
|
|
|
-}
|
|
|
+ .section-title {
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin: 0;
|
|
|
|
|
|
-.tree-node:hover .node-icon {
|
|
|
- color: #2b7bff;
|
|
|
- transform: scale(1.1);
|
|
|
-}
|
|
|
+ i {
|
|
|
+ margin-right: 8px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-/* 高亮当前选中的节点 */
|
|
|
-.el-tree-node.is-current > .el-tree-node__content .tree-node {
|
|
|
- background-color: #e6f7ff;
|
|
|
- border-radius: 4px;
|
|
|
- padding-left: 4px;
|
|
|
-}
|
|
|
+ .section-summary {
|
|
|
+ display: flex;
|
|
|
+ gap: 20px;
|
|
|
|
|
|
-.el-tree-node.is-current > .el-tree-node__content .node-icon {
|
|
|
- color: #1890ff;
|
|
|
-}
|
|
|
+ .summary-item {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
|
|
|
-/* 覆盖默认的el-tree样式,确保自定义样式生效 */
|
|
|
-.el-tree-node__content {
|
|
|
- height: auto;
|
|
|
- padding: 0 !important;
|
|
|
-}
|
|
|
+ i {
|
|
|
+ margin-right: 4px;
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
|
|
|
-.el-tree-node__content:hover {
|
|
|
- background-color: transparent;
|
|
|
-}
|
|
|
+ em {
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #409eff;
|
|
|
+ margin: 0 2px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
|
|
|
- background-color: transparent;
|
|
|
-}
|
|
|
+ .chart-controls {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-/* 树节点展开图标调整 */
|
|
|
-.el-tree-node__expand-icon {
|
|
|
- color: #909399;
|
|
|
- font-size: 14px;
|
|
|
-}
|
|
|
+ .chart-container {
|
|
|
+ padding: 16px;
|
|
|
+ position: relative;
|
|
|
+ min-height: 340px;
|
|
|
|
|
|
-.el-tree-node__expand-icon:hover {
|
|
|
- color: #409EFF;
|
|
|
-}
|
|
|
+ .empty-chart {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ color: #909399;
|
|
|
|
|
|
-/* 滚动条美化 */
|
|
|
-.tree-container::-webkit-scrollbar {
|
|
|
- width: 6px;
|
|
|
-}
|
|
|
+ i {
|
|
|
+ font-size: 48px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
|
|
|
-.tree-container::-webkit-scrollbar-track {
|
|
|
- background: #f1f1f1;
|
|
|
- border-radius: 3px;
|
|
|
-}
|
|
|
+ span {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .detail-table-section {
|
|
|
+ ::v-deep .el-table {
|
|
|
+ .facs-name {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 8px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .quantity-value {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
|
|
|
-.tree-container::-webkit-scrollbar-thumb {
|
|
|
- background: #c1c1c1;
|
|
|
- border-radius: 3px;
|
|
|
+ .quantity-text {
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .percentage-cell {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ ::v-deep .el-progress {
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .percentage-text {
|
|
|
+ font-size: 11px;
|
|
|
+ color: #909399;
|
|
|
+ min-width: 40px;
|
|
|
+ text-align: right;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rank-first {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ background: linear-gradient(135deg, #ffd700 0%, #ffb800 100%);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rank-second {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ background: linear-gradient(135deg, #c0c0c0 0%, #a8a8a8 100%);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rank-third {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ background: linear-gradient(135deg, #cd7f32 0%, #b87333 100%);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .rank-normal {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.tree-container::-webkit-scrollbar-thumb:hover {
|
|
|
- background: #a8a8a8;
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .power-use-container .summary-section .content-row .el-col {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|