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
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>
|
|
|