楼宇能效监测控制系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1344 lines
49 KiB

<template>
<div class="profit-analysis-container">
<!-- 顶部核心指标卡片区 -->
<div class="cards-container">
<div class="card">
<div class="card-icon water-icon">
<svg-icon icon-class="water" class-name="card-icon-svg" />
</div>
<div class="card-content">
<div class="card-label">用水金额</div>
<div class="card-value">{{ waterAmount }}</div>
<div class="card-sub">{{ getAverageLabel() }} {{ waterDailyAvg }}</div>
</div>
</div>
<div class="card">
<div class="card-icon electric-icon">
<svg-icon icon-class="elect" class-name="card-icon-svg" />
</div>
<div class="card-content">
<div class="card-label">用电金额</div>
<div class="card-value">{{ electricAmount }}</div>
<div class="card-sub">{{ getAverageLabel() }} {{ electricDailyAvg }}</div>
</div>
</div>
<div class="card">
<div class="card-icon unit-icon">
<svg-icon icon-class="unit" class-name="card-icon-svg" />
</div>
<div class="card-content">
<div class="card-label">单耗/</div>
<div class="card-value">{{ unitConsumption }}</div>
</div>
</div>
<div class="card">
<div class="card-icon income-icon">
<svg-icon icon-class="income" class-name="card-icon-svg" />
</div>
<div class="card-content">
<div class="card-label">总收入</div>
<div class="card-value">{{ totalIncome }}</div>
</div>
</div>
</div>
<!-- 筛选与控制区 -->
<div class="filter-container">
<div class="time-type-selector">
<div v-for="type in timeTypes" :key="type.value"
:class="['time-type-item', { active: dateType === type.value }]"
@click="handleTimeTypeChange(type.value)">
{{ type.label }}
</div>
</div>
<div class="date-picker-container">
<el-date-picker v-if="dateType === 'day'" v-model="dayDate" type="daterange" range-separator="至"
value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" @change="dateChange" />
<el-date-picker v-if="dateType === 'month'" v-model="monthDate" type="monthrange" range-separator="至"
start-placeholder="开始月份" end-placeholder="结束月份" value-format="yyyy-MM" @change="dateChange" />
<div class="year-selector" v-if="dateType === 'year'">
<el-date-picker v-model="startYear" type="year" placeholder="选择开始年份" value-format="yyyy"
@change="dateChange" />
<span class="year-separator">至</span>
<el-date-picker v-model="endYear" type="year" placeholder="选择结束年份" value-format="yyyy"
@change="dateChange" />
</div>
<div class="success-btn" style="margin: 0 0.12rem">
<el-button type="success" @click="handleQuery">查询</el-button>
</div>
<div class="warning-btn">
<el-button type="warning" @click="handleExport">导出</el-button>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-container">
<!-- 左侧:成本与收益对比图 -->
<div class="chart-section">
<div class="section-title">成本与收益对比图</div>
<div class="chart-box" ref="costChart_ref"></div>
</div>
<!-- 右侧:收益趋势 -->
<div class="trend-section">
<!-- 成本构成饼图 -->
<div class="chart-item" style="margin-bottom: 20px;">
<div class="section-title">成本构成</div>
<div class="chart-box-small" ref="pieChart_ref"></div>
</div>
<!-- 收益趋势折线图 -->
<div class="chart-item">
<div class="section-title">收益趋势</div>
<div class="chart-box-small" ref="trendChart_ref"></div>
</div>
</div>
</div>
<!-- 表格区域 -->
<div class="table-section">
<div class="section-title">收益明细表</div>
<div class="table-box">
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="curDate" label="日期" />
<el-table-column prop="waterPrice" label="用水单价(元)" />
<el-table-column prop="totalUseWaterAmount" label="用水量(吨)" />
<el-table-column prop="totalUseWater" label="用水金额(元)" />
<el-table-column prop="electPrice" label="用电单价(元)" />
<el-table-column prop="totalUseEleAmount" label="用电量(度)" />
<el-table-column prop="totalUseEle" label="用电金额(元)" />
<el-table-column prop="unitConsumption" label="单耗(度/吨)" />
<el-table-column prop="totalIncome" label="收入(元)">
<template slot-scope="{ row }">
<span
:class="{ 'profit-positive': row.totalIncome > 0, 'profit-negative': row.totalIncome < 0 }">
{{ row.totalIncome }}
</span>
</template>
</el-table-column>
</el-table>
</div>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
</div>
</template>
<script>
import * as echarts from "echarts";
import { waternErgySum, waterRevenueQuery } from "@/api/hotWater/profitAnalysis";
export default {
data() {
return {
// 核心指标数据
waterAmount: "0",
waterDailyAvg: "0",
electricAmount: "0",
electricDailyAvg: "0",
unitConsumption: "0",
totalIncome: "0",
// 时间类型
dateType: "day",
timeTypes: [
{ label: "日", value: "day" },
{ label: "月", value: "month" },
{ label: "年", value: "year" }
],
// 总条数
total: 0,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
startTime: "",
endTime: "",
},
// 日期选择
dayDate: [],
monthDate: [],
startYear: "",
endYear: "",
// 图表实例
costChart: null,
pieChart: null,
trendChart: null,
// 表格数据
tableData: [
]
};
},
mounted() {
this.initDefaultDates();
this.initCostChart();
this.initPieChart();
this.initTrendChart();
window.addEventListener("resize", this.handleResize);
this.handleResize();
// 初始化时加载数据
this.fetchCardData();
this.getList();
},
beforeDestroy() {
window.removeEventListener("resize", this.handleResize);
if (this.costChart) {
this.costChart.dispose();
}
if (this.pieChart) {
this.pieChart.dispose();
}
if (this.trendChart) {
this.trendChart.dispose();
}
},
methods: {
// 初始化成本与收益对比图
initCostChart() {
const dates = this.tableData.map(item => item.curDate).reverse();
const waterAmountData = this.tableData.map(item => item.totalUseWater).reverse();
const electricAmountData = this.tableData.map(item => item.totalUseEle).reverse();
const unitConsumptionData = this.tableData.map(item => item.unitConsumption).reverse();
console.log(" electricAmountData", electricAmountData)
this.costChart = echarts.init(this.$refs.costChart_ref);
var Min1 = 0,
Min2 = 0,
Max1 = Math.ceil(Math.max(...waterAmountData, ...electricAmountData)),
Max2 = Math.ceil(Math.max(...unitConsumptionData));
const titleFontSize = this.$refs.costChart_ref.offsetWidth / 100;
const option = {
tooltip: {
trigger: "axis",
formatter: function (params) {
var res = params[0].name + "<br/>";
for (var i = 0, l = params.length; i < l; i++) {
var seriesName = params[i].seriesName;
var value = params[i].value;
var marker =
'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' +
params[i].color.colorStops[0].color +
'"></span>';
if (seriesName.includes("金额")) {
res +=
marker +
seriesName +
":" +
'<span style="color: #000000; font-weight: bold;margin-left:5px">' +
value +
" " +
"元" +
"</span><br>";
} else {
res +=
marker +
seriesName +
":" +
'<span style="color: #000000; font-weight: bold;margin-left:5px">' +
value +
" " +
"</span><br>";
}
}
return res;
},
},
legend: {
data: ["用水金额", "用电金额", "单耗"],
show: true,
textStyle: {
color: "white",
},
selectedMode: true,
},
grid: {
top: "15%",
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
//设置为true代表离零刻度间隔一段距离
boundaryGap: true,
// 修饰刻度标签的颜色即x坐标数据
axisLabel: {
// interval: 0, //强制显示所有x轴数据
// rotate: 30, //x轴坐标字体倾斜30度
color: "rgba(255, 255, 255, 1)",
fontSize: 14, // 设置字体大小,可根据需要调整
},
axisTick: {
show: false, // 不显示坐标轴刻度线
},
// x坐标轴的颜色
axisLine: {
show: true,
lineStyle: {
color: "#365576",
},
},
splitLine: {
lineStyle: {
color: "#e2e6f0",
},
}, //x轴分割线
data: dates,
},
yAxis: [
{
type: "value",
name: "金额(元)",
// 设置 name 的样式
nameTextStyle: {
color: "rgba(255, 255, 255, 1)",
fontSize: 12,
},
// 修饰刻度标签的颜色即y坐标数据
axisLabel: {
color: "rgba(255, 255, 255, 1)",
},
// 显示y坐标轴
axisLine: {
show: true,
lineStyle: {
color: "#365576", // 设置 y 轴线的颜色
},
},
splitLine: {
lineStyle: {
color: "#1a3d62", // 设置分割线的颜色
type: "dashed", // 设置分割线为虚线
},
},
scale: true,
min: Min1,
max: Max1,
splitNumber: 10,
interval: (Max1 - Min1) / 10,
},
{
type: "value",
name: "单耗(度/吨)",
// 设置 name 的样式
nameTextStyle: {
color: "rgba(255, 255, 255, 1)",
fontSize: 12,
},
// 修饰刻度标签的颜色即y坐标数据
axisLabel: {
color: "rgba(255, 255, 255, 1)",
},
// 显示y坐标轴
axisLine: {
show: true,
lineStyle: {
color: "#365576", // 设置 y 轴线的颜色
},
},
splitLine: {
lineStyle: {
color: "#1a3d62", // 设置分割线的颜色
type: "dashed", // 设置分割线为虚线
},
},
scale: true,
min: Min2,
max: Max2,
splitNumber: 10,
interval: (Max2 - Min2) / 10,
},
],
series: [
{
name: "用水金额",
type: "bar",
smooth: true,
symbol: "circle",
// 拐点大小
symbolSize: 8,
// 开始不显示拐点, 鼠标经过显示
showSymbol: false,
//折线颜色
itemStyle: {
color: "#0b75d3",
// 使用颜色渐变
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "rgba(1, 102, 251, 1)", // 起始颜色
},
{
offset: 1,
color: "rgba(1, 102, 251, 0)", // 结束颜色
},
],
global: false, // 缺省为 false
},
borderRadius: [5, 5, 0, 0], // 分别对应左上、右上、右下、左下的圆角半径
},
data: waterAmountData,
},
{
name: "用电金额",
type: "bar",
smooth: true,
symbol: "circle",
// 拐点大小
symbolSize: 8,
// 开始不显示拐点, 鼠标经过显示
showSymbol: false,
//折线颜色
itemStyle: {
color: "#0b75d3",
// 使用颜色渐变
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "rgba(0, 224, 225, 1)", // 起始颜色
},
{
offset: 1,
color: "rgba(0, 224, 225, 0)", // 结束颜色
},
],
global: false, // 缺省为 false
},
borderRadius: [5, 5, 0, 0],
},
data: electricAmountData,
},
{
name: "单耗",
type: "line",
smooth: false,
symbol: "circle",
// 拐点大小
symbolSize: 8,
yAxisIndex: 1,
// 开始不显示拐点, 鼠标经过显示
showSymbol: false,
//折线颜色
itemStyle: {
color: "#EE5217", //折线点的颜色
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#EE5217", // 起始颜色
},
{
offset: 1,
color: "#EE5217", // 结束颜色
},
],
global: false, // 缺省为 false
},
lineStyle: {
color: "#EE5217", //折线的颜色
},
},
data: unitConsumptionData,
},
]
};
this.costChart.setOption(option);
// 监听图例选择变化事件
this.costChart.on('legendselectchanged', (params) => {
this.handleLegendChanged(params);
});
},
// 处理图例变化,重新计算y轴范围
handleLegendChanged(params) {
const waterAmountData = this.tableData.map(item => item.totalUseWater).reverse();
const electricAmountData = this.tableData.map(item => item.totalUseEle).reverse();
const unitConsumptionData = this.tableData.map(item => item.unitConsumption).reverse();
// 获取当前选中的图例
const selected = params.selected;
const selectedNames = Object.keys(selected).filter(name => selected[name]);
// 根据选中的图例重新计算y轴范围
let Min1 = 0, Max1 = 0;
let Min2 = 0, Max2 = 0;
let hasAmountData = false;
let hasUnitData = false;
// 检查是否有金额相关的图例被选中
if (selectedNames.includes('用水金额') || selectedNames.includes('用电金额')) {
const activeAmountData = [];
if (selectedNames.includes('用水金额')) {
activeAmountData.push(...waterAmountData);
}
if (selectedNames.includes('用电金额')) {
activeAmountData.push(...electricAmountData);
}
if (activeAmountData.length > 0) {
Max1 = Math.ceil(Math.max(...activeAmountData));
hasAmountData = true;
}
}
// 检查是否有单耗图例被选中
if (selectedNames.includes('单耗')) {
Max2 = Math.ceil(Math.max(...unitConsumptionData));
hasUnitData = true;
}
// 如果没有数据被选中,设置默认值
if (!hasAmountData && !hasUnitData) {
Max1 = 10;
Max2 = 1;
}
// 更新图表选项
this.costChart.setOption({
yAxis: [
{
min: hasAmountData ? Min1 : 0,
max: hasAmountData ? Max1 : 10,
splitNumber: 10,
interval: hasAmountData ? (Max1 - Min1) / 10 : 1
},
{
min: hasUnitData ? Min2 : 0,
max: hasUnitData ? Max2 : 1,
splitNumber: 10,
interval: hasUnitData ? (Max2 - Min2) / 10 : 0.1
}
]
});
},
// 初始化成本构成饼图
initPieChart() {
this.pieChart = echarts.init(this.$refs.pieChart_ref);
// 使用顶部卡片的用水和用电金额(汇总数据)
// 移除可能存在的千分位逗号
const totalWaterAmount = parseFloat(String(this.waterAmount).replace(/,/g, '')) || 0;
const totalElectricAmount = parseFloat(String(this.electricAmount).replace(/,/g, '')) || 0;
const option = {
tooltip: {
trigger: "item",
formatter: function (params) {
return `<div style="padding: 8px;">
<div style="color: #fff; font-weight: bold; margin-bottom: 4px;">${params.name}</div>
<div style="color: #fff;">金额: <span style="color: ${params.color}; font-weight: bold;">${params.value}</span> 元</div>
<div style="color: #fff;">占比: <span style="color: ${params.color}; font-weight: bold;">${params.percent}%</span></div>
</div>`;
},
backgroundColor: 'rgba(0, 0, 0, 0.8)',
borderColor: '#3b82f6',
borderWidth: 1,
borderRadius: 8,
padding: 12
},
legend: {
orient: "horizontal",
bottom: 0,
left: "center",
data: ["用水金额", "用电金额"],
itemGap: 20,
itemWidth: 14,
itemHeight: 14,
textStyle: {
color: "#ffffff",
fontSize: 12,
fontWeight: 500
},
padding: [5, 0, 5, 0]
},
series: [
{
name: "成本构成",
type: "pie",
radius: ["40%", "60%"],
center: ["50%", "45%"],
avoidLabelOverlap: true,
hoverOffset: 10,
selectedOffset: 5,
label: {
show: true,
position: "outside",
formatter: function (params) {
return [
`{name|${params.name}}`,
`{value|${params.value}}`,
`{percent|${params.percent}%}`
].join('\n');
},
rich: {
name: {
color: '#ffffff',
fontSize: 11,
fontWeight: 500,
padding: [0, 0, 2, 0]
},
value: {
color: '#ffffff',
fontSize: 12,
fontWeight: 'bold',
padding: [0, 0, 2, 0]
},
percent: {
color: '#fbbf24',
fontSize: 11,
fontWeight: 'bold'
}
},
lineHeight: 15,
distanceToLabelLine: 5
},
emphasis: {
label: {
show: true,
fontSize: 12,
fontWeight: 'bold'
},
scale: true,
scaleSize: 10
},
labelLine: {
show: true,
length: 15,
length2: 10,
lineStyle: {
color: 'rgba(255, 255, 255, 0.6)',
width: 1,
type: 'solid'
},
smooth: true
},
data: [
{
value: totalWaterAmount,
name: "用水金额",
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 1,
colorStops: [
{ offset: 0, color: '#3b82f6' },
{ offset: 1, color: '#8b5cf6' }
]
},
borderWidth: 2,
borderColor: 'rgba(255, 255, 255, 0.2)',
shadowBlur: 10,
shadowColor: 'rgba(59, 130, 246, 0.5)'
}
},
{
value: totalElectricAmount,
name: "用电金额",
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 1,
colorStops: [
{ offset: 0, color: '#10b981' },
{ offset: 1, color: '#06b6d4' }
]
},
borderWidth: 2,
borderColor: 'rgba(255, 255, 255, 0.2)',
shadowBlur: 10,
shadowColor: 'rgba(16, 185, 129, 0.5)'
}
}
]
}
]
};
this.pieChart.setOption(option);
},
// 初始化收益趋势折线图
initTrendChart() {
this.trendChart = echarts.init(this.$refs.trendChart_ref);
const dates = this.tableData.map(item => item.curDate).reverse();
const incomeData = this.tableData.map(item => item.totalIncome).reverse();
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross"
}
},
legend: {
show: false,
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
data: dates,
//设置为true代表离零刻度间隔一段距离
boundaryGap: true,
// 修饰刻度标签的颜色即x坐标数据
axisLabel: {
// interval: 0, //强制显示所有x轴数据
// rotate: 30, //x轴坐标字体倾斜30度
color: "rgba(255, 255, 255, 1)",
fontSize: 14, // 设置字体大小,可根据需要调整
},
axisTick: {
show: false, // 不显示坐标轴刻度线
},
// x坐标轴的颜色
axisLine: {
show: true,
lineStyle: {
color: "#365576",
},
},
splitLine: {
lineStyle: {
color: "#e2e6f0",
},
}, //x轴分割线
},
yAxis: {
type: "value",
name: "金额(元)",
// 设置 name 的样式
nameTextStyle: {
color: "rgba(255, 255, 255, 1)",
fontSize: 12,
},
// 修饰刻度标签的颜色即y坐标数据
axisLabel: {
color: "rgba(255, 255, 255, 1)",
},
// 显示y坐标轴
axisLine: {
show: true,
lineStyle: {
color: "#365576", // 设置 y 轴线的颜色
},
},
splitLine: {
lineStyle: {
color: "#1a3d62", // 设置分割线的颜色
type: "dashed", // 设置分割线为虚线
},
},
},
series: [
{
name: "收入",
type: "line",
data: incomeData,
smooth: true,
itemStyle: {
color: "#10b981"
},
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: "rgba(16, 185, 129, 0.3)" },
{ offset: 1, color: "rgba(16, 185, 129, 0.1)" }
]
}
}
}
]
};
this.trendChart.setOption(option);
},
// 窗口大小调整
handleResize() {
if (this.costChart) {
this.costChart.resize();
}
if (this.pieChart) {
this.pieChart.resize();
}
if (this.trendChart) {
this.trendChart.resize();
}
},
// 时间类型切换
handleTimeTypeChange(type) {
this.dateType = type;
this.fetchCardData();
this.queryParams.pageNum = 1;
this.getList();
},
// 获取平均值标签
getAverageLabel() {
const labelMap = {
'day': '日均',
'month': '月均',
'year': '年均'
};
return labelMap[this.dateType] || '日均';
},
// 日期变化
dateChange() {
// 可以在这里添加日期变化后的逻辑
console.log("日期变化");
},
// 查询
handleQuery() {
// 验证日期范围是否已选择
const dateRange = this.getDateRange();
if (!dateRange.start || !dateRange.end) {
this.$message.warning('请选择日期范围');
return;
}
console.log("查询数据");
this.fetchCardData();
this.queryParams.pageNum = 1;
this.getList();
},
// 获取顶部卡片数据
async fetchCardData() {
try {
// 构建查询参数
const params = {
startDate: this.getDateRange().start,
endDate: this.getDateRange().end,
type: this.getTypeValue(),
buildingId: '所有'
};
const res = await waternErgySum(params);
if (res.code === 200 && res.rows && res.rows.length > 0) {
const data = res.rows[0];
// 更新卡片数据
this.waterAmount = data.totalUseWater ? data.totalUseWater.toFixed(2) : '0';
this.waterDailyAvg = data.avgUseWater ? data.avgUseWater.toFixed(2) : '0';
this.electricAmount = data.totalUseEle ? data.totalUseEle.toFixed(2) : '0';
this.electricDailyAvg = data.avgUseEle ? data.avgUseEle.toFixed(2) : '0';
this.unitConsumption = data.unitConsumption !== null ? data.unitConsumption.toString() : '0';
this.totalIncome = data.totalIncome ? data.totalIncome.toFixed(2) : '0';
}
} catch (error) {
console.error('获取卡片数据失败:', error);
}
},
// 获取日期范围
getDateRange() {
let start = '';
let end = '';
if (this.dateType === 'day' && this.dayDate && this.dayDate.length === 2) {
// 日:年-月-日
start = this.dayDate[0];
end = this.dayDate[1];
} else if (this.dateType === 'month' && this.monthDate && this.monthDate.length === 2) {
// 月:年-月
start = this.monthDate[0];
end = this.monthDate[1];
} else if (this.dateType === 'year' && this.startYear && this.endYear) {
// 年:年
start = this.startYear;
end = this.endYear;
}
return { start, end };
},
// 获取type对应的值
getTypeValue() {
const typeMap = {
'day': 1,
'month': 2,
'year': 3
};
return typeMap[this.dateType] || 1;
},
// 导出
handleExport() {
console.log("导出数据");
if (this.tableData) {
import("@/assets/excel/Export2Excel").then((excel) => {
var tHeader = [
"日期",
"用水单价(元)",
"用水量(吨)",
"用水金额(元)",
"用电单价(元)",
"用电量(度)",
"用电金额(元)",
"单耗(度/吨)",
"收入(元)",
]; // 导出的excel表头名信息
var filterVal = [
"curDate",
"waterPrice",
"totalUseWaterAmount",
"totalUseWater",
"electPrice",
"totalUseEleAmount",
"totalUseEle",
"unitConsumption",
"totalIncome",
]; // 导出的excel表头字段名,需要导出表格字段名
const data = this.formatJson(filterVal, this.tableData);
const autoWidth = true;
excel.export_json_to_excel({
header: tHeader, //表头
data, //数据
filename: "收益分析报表(" + this.getDateRange().start + "至" + this.getDateRange().end + ")", //名称
autoWidth: true, //宽度自适应
});
});
this.$message({
type: "success",
message: "导出成功!",
});
} else {
this.$message.error("导出失败!");
}
},
//格式转换,不需要改动
formatJson(filterVal, jsonData) {
return jsonData.map((v) =>
filterVal.map((j) => {
if (j === "installDate") {
return format(v[j]);
} else {
return v[j];
}
})
);
},
// 初始化默认日期
initDefaultDates() {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1;
const currentDay = now.getDate();
// 日:今年这个月的1号到今天
const monthStart = new Date(currentYear, currentMonth - 1, 1);
const today = new Date(currentYear, currentMonth - 1, currentDay);
const formatDate = (date) => {
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}`;
};
this.dayDate = [formatDate(monthStart), formatDate(today)];
// 月:今年1月到当前月
const formatMonth = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
return `${year}-${month}`;
};
const yearStart = new Date(currentYear, 0, 1);
const currentMonthDate = new Date(currentYear, currentMonth - 1, 1);
this.monthDate = [formatMonth(yearStart), formatMonth(currentMonthDate)];
// 年:当前年份
this.startYear = String(currentYear);
this.endYear = String(currentYear);
},
// 获取列表数据(图表和表格)
async getList() {
try {
// 构建查询参数
const params = {
startDate: this.getDateRange().start,
endDate: this.getDateRange().end,
type: this.getTypeValue(),
buildingId: '所有',
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize
};
const res = await waterRevenueQuery(params);
if (res.code === 200 && res.rows) {
// 处理表格数据
this.tableData = res.rows;
this.total = res.total || 0;
// 更新图表
this.$nextTick(() => {
this.updateCharts();
});
}
} catch (error) {
console.error('获取列表数据失败:', error);
}
},
// 更新所有图表
updateCharts() {
this.initCostChart();
this.initPieChart();
this.initTrendChart();
},
}
};
</script>
<style lang="scss" scoped>
.profit-analysis-container {
width: 100%;
min-height: 100vh;
box-sizing: border-box;
.section-title {
font-size: 16px;
font-weight: bold;
color: #ffffff;
margin-bottom: 16px;
}
.cards-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 20px;
.card {
background: rgba(29, 111, 233, 0.3);
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
transition: all 0.3s ease;
&:hover {
background: rgba(255, 255, 255, 0.12);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transform: translateY(-2px);
}
.card-icon {
width: 60px;
height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
font-size: 28px;
.card-icon-svg {
width: 32px;
height: 32px;
fill: white;
}
&.water-icon {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
&.electric-icon {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
&.unit-icon {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
color: white;
}
&.income-icon {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
color: white;
}
}
.card-content {
flex: 1;
.card-label {
font-size: 14px;
color: #a0aec0;
margin-bottom: 8px;
}
.card-value {
font-size: 24px;
font-weight: bold;
color: #ffffff;
margin-bottom: 4px;
}
.card-sub {
font-size: 12px;
color: #cbd5e0;
&.profit-rate {
color: #10b981;
font-weight: 500;
}
}
}
}
}
.filter-container {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(29, 111, 233, 0.3);
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
display: flex;
justify-content: flex-start;
gap: 20px;
flex-wrap: wrap;
.time-type-selector {
display: flex;
gap: 8px;
.time-type-item {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid rgba(29, 111, 233, 0.3);
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
&:hover {
background: rgba(29, 111, 233, 0.3);
}
&.active {
background: #3b82f6;
color: white;
border-color: #3b82f6;
}
}
}
.date-picker-container {
flex: 1;
display: flex;
align-items: center;
.year-selector {
display: flex;
align-items: center;
gap: 10px;
.year-separator {
color: #cbd5e0;
}
}
}
}
.charts-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: stretch;
.chart-section {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(29, 111, 233, 0.3);
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
width: 68%;
.chart-box {
flex: 1;
min-height: 400px;
}
}
.trend-section {
display: flex;
flex-direction: column;
width: 30%;
.chart-item {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(29, 111, 233, 0.3);
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
width: 100%;
.chart-box-small {
flex: 1;
min-height: 250px;
}
}
}
}
.table-section {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(29, 111, 233, 0.3);
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-top: 20px;
.table-box {
min-height: 400px;
overflow: auto;
::v-deep .el-table {
border: none;
&::before {
display: none;
}
th {
background-color: rgba(255, 255, 255, 0.1);
color: #ffffff;
font-weight: 600;
}
tr {
transition: all 0.3s ease;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
td {
border: none;
padding: 12px;
color: #ffffff;
}
}
.profit-positive {
color: #10b981;
font-weight: bold;
}
.profit-negative {
color: #ef4444;
font-weight: bold;
}
}
}
.table-footer {
text-align: right;
color: #cbd5e0;
font-size: 12px;
padding: 10px 0 0 0;
}
}
}
// 响应式设计
@media (max-width: 1200px) {
.chart-section {
width: 58% !important;
}
.trend-section {
width: 40% !important;
}
}
@media (max-width: 768px) {
.cards-container {
grid-template-columns: 1fr;
}
.filter-container {
flex-direction: column;
align-items: stretch;
.date-picker-container {
width: 100%;
}
.button-group {
width: 100%;
.el-button {
flex: 1;
}
}
}
}
</style>