<template>
|
<div class="dashboard-container">
|
<div class="data-dashboard">
|
<!-- 筛选区域 -->
|
<div class="filter-area">
|
<div class="filter-section">
|
<span class="filter-label">时间维度:</span>
|
<el-radio-group v-model="dateType"
|
@change="handleDateTypeChange"
|
class="radio-group">
|
<el-radio-button label="month">月度</el-radio-button>
|
<el-radio-button label="year">年度</el-radio-button>
|
</el-radio-group>
|
</div>
|
</div>
|
<!-- 主要内容区域 -->
|
<div class="dashboard-content">
|
<!-- 第一行:核心指标 -->
|
<div class="row row-1">
|
<div class="panel-card card-1">
|
<div class="panel-title">核心指标</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-label">合计量</div>
|
<div class="stat-value">{{ totalSolidWaste }}</div>
|
<div class="stat-unit">吨</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">2022年至今累计消纳量</div>
|
<div class="stat-value">{{ totalSolidWasteSince2022 }}</div>
|
<div class="stat-unit">吨</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<!-- 第二行:固废消纳趋势 -->
|
<div class="row row-2">
|
<div class="panel-card card-2">
|
<div class="panel-title">固废消纳趋势</div>
|
<div class="chart-container">
|
<div ref="trendChart"
|
style="width: 100%; height: 100%"></div>
|
</div>
|
</div>
|
</div>
|
<!-- 第三行:固废类型分布 -->
|
<div class="row row-3">
|
<div class="panel-card card-3">
|
<div class="panel-title">固废类型分布</div>
|
<div class="chart-container">
|
<div ref="distributionChart"
|
style="width: 100%; height: 100%"></div>
|
</div>
|
</div>
|
</div>
|
<!-- 第四行:消纳量明细 -->
|
<div class="row row-4">
|
<div class="panel-card card-4">
|
<div class="panel-title">消纳量明细</div>
|
<div class="table-container">
|
<el-table :data="wasteTableData"
|
style="width: 100%">
|
<el-table-column prop="time"
|
label="时间"
|
width="120" />
|
<el-table-column prop="type"
|
label="固废类型"
|
width="120"
|
align="center">
|
<template #default="scope">
|
<el-tag :type="getWasteTypeType(scope.row.type)">
|
{{ scope.row.type }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="quantity"
|
label="消纳量"
|
align="right">
|
<template #default="scope">
|
<span class="data-value">{{ scope.row.quantity }}</span>
|
</template>
|
</el-table-column>
|
<el-table-column prop="unit"
|
label="单位"
|
width="80" />
|
<el-table-column prop="source"
|
label="来源" />
|
</el-table>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted, onBeforeUnmount, nextTick } from "vue";
|
import * as echarts from "echarts";
|
import {
|
getSolidWasteTrends,
|
getSolidWasteCoreIndicators,
|
getSolidWasteTypeDistribution,
|
} from "@/api/solidWaste/index.js";
|
|
// 筛选条件:月度 -> dateType 1,年度 -> 2
|
const dateType = ref("month");
|
|
// 图表引用
|
const trendChart = ref(null);
|
const distributionChart = ref(null);
|
|
let trendChartInstance = null;
|
let distributionChartInstance = null;
|
|
/** @type {import('vue').Ref<Array<{ dateStr: string, total?: number, flyAsh: number, gypsum: number, lime: number }>>} */
|
const trendsList = ref([]);
|
|
/** 固废类型分布 /home/solidWaste/typeDistribution */
|
const typeDistributionList = ref([]);
|
|
/** 核心指标 /home/solidWaste/coreIndicators */
|
const totalSolidWaste = ref(0);
|
const totalSolidWasteSince2022 = ref(0);
|
|
const wasteTableData = computed(() => {
|
const list = trendsList.value || [];
|
const result = [];
|
list.forEach(row => {
|
result.push({
|
time: row.dateStr,
|
type: "粉煤灰",
|
quantity: Number(row.flyAsh) || 0,
|
unit: "吨",
|
source: "生产过程",
|
});
|
result.push({
|
time: row.dateStr,
|
type: "石膏",
|
quantity: Number(row.gypsum) || 0,
|
unit: "吨",
|
source: "生产过程",
|
});
|
result.push({
|
time: row.dateStr,
|
type: "石灰",
|
quantity: Number(row.lime) || 0,
|
unit: "吨",
|
source: "生产过程",
|
});
|
});
|
return result;
|
});
|
|
const trendChartOption = computed(() => {
|
const list = trendsList.value || [];
|
return {
|
tooltip: {
|
trigger: "axis",
|
axisPointer: {
|
type: "shadow",
|
},
|
},
|
legend: {
|
data: ["粉煤灰", "石膏", "石灰"],
|
textStyle: {
|
color: "#333",
|
},
|
},
|
grid: {
|
left: "3%",
|
right: "4%",
|
bottom: "3%",
|
containLabel: true,
|
},
|
xAxis: {
|
type: "category",
|
data: list.map(item => item.dateStr),
|
axisLabel: {
|
color: "#333",
|
},
|
},
|
yAxis: {
|
type: "value",
|
name: "消纳量 (吨)",
|
axisLabel: {
|
color: "#333",
|
},
|
},
|
series: [
|
{
|
name: "粉煤灰",
|
type: "bar",
|
data: list.map(item => Number(item.flyAsh) || 0),
|
itemStyle: {
|
color: "#909399",
|
},
|
},
|
{
|
name: "石膏",
|
type: "bar",
|
data: list.map(item => Number(item.gypsum) || 0),
|
itemStyle: {
|
color: "#E6A23C",
|
},
|
},
|
{
|
name: "石灰",
|
type: "bar",
|
data: list.map(item => Number(item.lime) || 0),
|
itemStyle: {
|
color: "#F56C6C",
|
},
|
},
|
],
|
};
|
});
|
|
const typePieColor = name => {
|
const map = { 粉煤灰: "#909399", 石膏: "#E6A23C", 石灰: "#F56C6C" };
|
return map[name] || "#909399";
|
};
|
|
const distributionChartOption = computed(() => {
|
const list = typeDistributionList.value || [];
|
const pieData = list.map(item => ({
|
name: item.name,
|
value: parseFloat(String(item.value ?? 0)) || 0,
|
rate: item.rate,
|
}));
|
|
return {
|
tooltip: {
|
trigger: "item",
|
formatter: params => {
|
const rate =
|
params.data?.rate != null && params.data.rate !== ""
|
? `${params.data.rate}%`
|
: `${params.percent}%`;
|
return `${params.seriesName}<br/>${params.marker}${params.name}: ${params.value} 吨 (${rate})`;
|
},
|
},
|
legend: {
|
orient: "vertical",
|
left: "left",
|
data: pieData.map(d => d.name),
|
textStyle: {
|
color: "#333",
|
},
|
},
|
series: [
|
{
|
name: "固废类型",
|
type: "pie",
|
radius: "60%",
|
center: ["50%", "50%"],
|
data: pieData,
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
},
|
},
|
itemStyle: {
|
color: params => typePieColor(params.name),
|
},
|
},
|
],
|
};
|
});
|
|
const mapDateTypeParam = () => (dateType.value === "year" ? "2" : "1");
|
|
const loadCoreIndicators = async () => {
|
try {
|
const res = await getSolidWasteCoreIndicators({
|
dateType: mapDateTypeParam(),
|
});
|
const d = res.data && typeof res.data === "object" ? res.data : {};
|
totalSolidWaste.value = Number(d.totalAmount) || 0;
|
totalSolidWasteSince2022.value = Number(d.cumulativeAmount) || 0;
|
} catch (e) {
|
console.error(e);
|
totalSolidWaste.value = 0;
|
totalSolidWasteSince2022.value = 0;
|
}
|
};
|
|
const loadTrends = async () => {
|
try {
|
const res = await getSolidWasteTrends({ dateType: mapDateTypeParam() });
|
trendsList.value = Array.isArray(res.data) ? res.data : [];
|
} catch (e) {
|
console.error(e);
|
trendsList.value = [];
|
}
|
};
|
|
const loadTypeDistribution = async () => {
|
try {
|
const res = await getSolidWasteTypeDistribution({
|
dateType: mapDateTypeParam(),
|
});
|
typeDistributionList.value = Array.isArray(res.data) ? res.data : [];
|
} catch (e) {
|
console.error(e);
|
typeDistributionList.value = [];
|
}
|
};
|
|
const refreshDashboard = async () => {
|
await Promise.all([
|
loadTrends(),
|
loadCoreIndicators(),
|
loadTypeDistribution(),
|
]);
|
await nextTick();
|
updateCharts();
|
};
|
|
const handleDateTypeChange = () => {
|
refreshDashboard();
|
};
|
|
const initCharts = () => {
|
if (trendChart.value && !trendChartInstance) {
|
trendChartInstance = echarts.init(trendChart.value);
|
}
|
if (distributionChart.value && !distributionChartInstance) {
|
distributionChartInstance = echarts.init(distributionChart.value);
|
}
|
updateCharts();
|
};
|
|
const updateCharts = () => {
|
if (trendChartInstance) {
|
trendChartInstance.setOption(trendChartOption.value);
|
}
|
if (distributionChartInstance) {
|
distributionChartInstance.setOption(distributionChartOption.value);
|
}
|
};
|
|
const resizeCharts = () => {
|
trendChartInstance?.resize();
|
distributionChartInstance?.resize();
|
};
|
|
const handleResize = () => {
|
setTimeout(() => {
|
resizeCharts();
|
}, 100);
|
};
|
|
const getWasteTypeType = type => {
|
const typeMap = {
|
粉煤灰: "info",
|
石膏: "warning",
|
石灰: "danger",
|
};
|
return typeMap[type] || "info";
|
};
|
|
onMounted(async () => {
|
await refreshDashboard();
|
nextTick(() => {
|
initCharts();
|
});
|
window.addEventListener("resize", handleResize);
|
});
|
|
onBeforeUnmount(() => {
|
window.removeEventListener("resize", handleResize);
|
trendChartInstance?.dispose();
|
distributionChartInstance?.dispose();
|
});
|
</script>
|
|
<style scoped>
|
/* 外部容器 - 占据整个视口 */
|
.dashboard-container {
|
position: relative;
|
width: 100%;
|
/* 页面在常规布局下(有顶栏)默认减去 84px,避免内容被裁切 */
|
min-height: calc(100vh - 84px);
|
background-color: #f5f7fa;
|
overflow: hidden;
|
}
|
|
/* 内部内容区域 - 自适应宽度 */
|
.data-dashboard {
|
position: relative;
|
width: 100%;
|
min-height: 100%;
|
background-color: #ffffff;
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
}
|
|
.filter-area {
|
padding: 20px;
|
background-color: #ffffff;
|
border-bottom: 1px solid #e4e7ed;
|
display: flex;
|
gap: 40px;
|
align-items: center;
|
flex-wrap: wrap;
|
}
|
|
.filter-section {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.filter-label {
|
font-size: 14px;
|
font-weight: 500;
|
color: #303133;
|
white-space: nowrap;
|
}
|
|
.radio-group {
|
display: flex;
|
align-items: center;
|
}
|
|
/* 按钮样式 */
|
:deep(.el-radio-button__inner) {
|
border-radius: 4px;
|
padding: 8px 20px;
|
font-size: 14px;
|
transition: all 0.3s ease;
|
}
|
|
:deep(.el-radio-button__orig-radio:checked + .el-radio-button__inner) {
|
background-color: #409eff;
|
border-color: #409eff;
|
color: #ffffff;
|
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
}
|
|
:deep(.el-radio-button__inner:hover) {
|
color: #409eff;
|
border-color: #c6e2ff;
|
}
|
|
:deep(.el-radio-button:first-child .el-radio-button__inner) {
|
border-radius: 4px 0 0 4px;
|
}
|
|
:deep(.el-radio-button:last-child .el-radio-button__inner) {
|
border-radius: 0 4px 4px 0;
|
}
|
|
.dashboard-content {
|
position: relative;
|
z-index: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 20px;
|
padding: 20px;
|
min-height: 800px;
|
overflow: hidden;
|
}
|
|
/* 行布局 */
|
.row {
|
display: flex;
|
gap: 20px;
|
align-items: stretch;
|
}
|
|
/* 第一行:核心指标 */
|
.row-1 {
|
height: 250px;
|
}
|
|
/* 第二行:固废消纳趋势 */
|
.row-2 {
|
height: 300px;
|
}
|
|
/* 第三行:固废类型分布 */
|
.row-3 {
|
height: 300px;
|
}
|
|
/* 第四行:消纳量明细 */
|
.row-4 {
|
min-height: 250px;
|
}
|
|
/* 卡片样式 */
|
.panel-card {
|
background-color: #ffffff;
|
border-radius: 8px;
|
border: 1px solid #e4e7ed;
|
overflow: hidden;
|
display: flex;
|
flex-direction: column;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
transition: all 0.3s ease;
|
}
|
|
.panel-card:hover {
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
transform: translateY(-2px);
|
}
|
|
/* 卡片布局 */
|
.card-1 {
|
flex: 1;
|
}
|
|
.card-2 {
|
flex: 1;
|
}
|
|
.card-3 {
|
flex: 1;
|
}
|
|
.card-4 {
|
flex: 1;
|
}
|
|
.panel-title {
|
padding: 15px 20px;
|
font-size: 16px;
|
font-weight: 500;
|
color: #303133;
|
border-bottom: 1px solid #e4e7ed;
|
background-color: #fafafa;
|
}
|
|
.card-1 .panel-title {
|
border-left: 4px solid #409eff;
|
}
|
|
.card-2 .panel-title {
|
border-left: 4px solid #f56c6c;
|
}
|
|
.card-3 .panel-title {
|
border-left: 4px solid #e6a23c;
|
}
|
|
.card-4 .panel-title {
|
border-left: 4px solid #67c23a;
|
}
|
|
.chart-container {
|
flex: 1;
|
padding: 20px;
|
}
|
|
.table-container {
|
flex: 1;
|
padding: 20px;
|
overflow: auto;
|
}
|
|
.stats-grid {
|
flex: 1;
|
padding: 15px;
|
display: grid;
|
grid-template-columns: repeat(2, 1fr);
|
gap: 15px;
|
}
|
|
.stat-item {
|
background-color: #ffffff;
|
border-radius: 12px;
|
padding: 25px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
border: 2px solid #e4e7ed;
|
min-height: 120px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
transition: all 0.3s ease;
|
}
|
|
.stat-item:hover {
|
box-shadow: 0 8px 24px rgba(64, 158, 255, 0.2);
|
border-color: #409eff;
|
transform: translateY(-4px);
|
}
|
|
.stat-label {
|
font-size: 14px;
|
font-weight: 500;
|
color: #303133;
|
margin-bottom: 10px;
|
}
|
|
.stat-value {
|
font-size: 32px;
|
font-weight: 700;
|
color: #409eff;
|
margin-bottom: 5px;
|
text-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
transition: all 0.3s ease;
|
}
|
|
.stat-value:hover {
|
transform: scale(1.05);
|
}
|
|
.stat-unit {
|
font-size: 12px;
|
font-weight: 500;
|
color: #606266;
|
}
|
|
/* 表格样式 */
|
:deep(.el-table) {
|
border-radius: 8px;
|
overflow: hidden;
|
}
|
|
:deep(.el-table th) {
|
background-color: #fafafa;
|
font-weight: 500;
|
}
|
|
:deep(.el-table tr:hover > td) {
|
background-color: #ecf5ff;
|
}
|
|
.data-value {
|
font-weight: bold;
|
color: #409eff;
|
}
|
</style>
|