<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";
|
|
// 筛选条件
|
const dateType = ref("month"); // month 或 year
|
|
// 图表引用
|
const trendChart = ref(null);
|
const distributionChart = ref(null);
|
|
// 图表实例
|
let trendChartInstance = null;
|
let distributionChartInstance = null;
|
|
// 模拟数据
|
const solidWasteData = ref({
|
month: [
|
{ name: "1月", 粉煤灰: 200, 石膏: 150, 石灰: 100 },
|
{ name: "2月", 粉煤灰: 220, 石膏: 160, 石灰: 110 },
|
{ name: "3月", 粉煤灰: 190, 石膏: 140, 石灰: 95 },
|
{ name: "4月", 粉煤灰: 230, 石膏: 170, 石灰: 115 },
|
{ name: "5月", 粉煤灰: 240, 石膏: 180, 石灰: 120 },
|
{ name: "6月", 粉煤灰: 225, 石膏: 165, 石灰: 112 },
|
],
|
year: [
|
{ name: "2022", 粉煤灰: 2300, 石膏: 1700, 石灰: 1100 },
|
{ name: "2023", 粉煤灰: 2500, 石膏: 1800, 石灰: 1200 },
|
{ name: "2024", 粉煤灰: 2700, 石膏: 1950, 石灰: 1300 },
|
{ name: "2025", 粉煤灰: 2900, 石膏: 2100, 石灰: 1400 },
|
],
|
});
|
|
// 计算属性
|
const totalSolidWaste = computed(() => {
|
const data = solidWasteData.value[dateType.value];
|
if (dateType.value === "month") {
|
return data.reduce(
|
(sum, item) => sum + item.粉煤灰 + item.石膏 + item.石灰,
|
0
|
);
|
} else {
|
const lastItem = data[data.length - 1];
|
return lastItem.粉煤灰 + lastItem.石膏 + lastItem.石灰;
|
}
|
});
|
|
const totalSolidWasteSince2022 = computed(() => {
|
const data = solidWasteData.value.year;
|
return data.reduce(
|
(sum, item) => sum + item.粉煤灰 + item.石膏 + item.石灰,
|
0
|
);
|
});
|
|
const wasteTableData = computed(() => {
|
const data = solidWasteData.value[dateType.value];
|
const result = [];
|
|
data.forEach(item => {
|
result.push({
|
time: item.name,
|
type: "粉煤灰",
|
quantity: item.粉煤灰,
|
unit: "吨",
|
source: "生产过程",
|
});
|
result.push({
|
time: item.name,
|
type: "石膏",
|
quantity: item.石膏,
|
unit: "吨",
|
source: "生产过程",
|
});
|
result.push({
|
time: item.name,
|
type: "石灰",
|
quantity: item.石灰,
|
unit: "吨",
|
source: "生产过程",
|
});
|
});
|
|
return result;
|
});
|
|
// 图表配置
|
const trendChartOption = computed(() => {
|
const data = solidWasteData.value[dateType.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: data.map(item => item.name),
|
axisLabel: {
|
color: "#333",
|
},
|
},
|
yAxis: {
|
type: "value",
|
name: "消纳量 (吨)",
|
axisLabel: {
|
color: "#333",
|
},
|
},
|
series: [
|
{
|
name: "粉煤灰",
|
type: "bar",
|
data: data.map(item => item.粉煤灰),
|
itemStyle: {
|
color: "#909399",
|
},
|
},
|
{
|
name: "石膏",
|
type: "bar",
|
data: data.map(item => item.石膏),
|
itemStyle: {
|
color: "#E6A23C",
|
},
|
},
|
{
|
name: "石灰",
|
type: "bar",
|
data: data.map(item => item.石灰),
|
itemStyle: {
|
color: "#F56C6C",
|
},
|
},
|
],
|
};
|
});
|
|
const distributionChartOption = computed(() => {
|
const data = solidWasteData.value[dateType.value];
|
const lastItem = data[data.length - 1];
|
|
return {
|
tooltip: {
|
trigger: "item",
|
formatter: "{a} <br/>{b}: {c} ({d}%)",
|
},
|
legend: {
|
orient: "vertical",
|
left: "left",
|
textStyle: {
|
color: "#333",
|
},
|
},
|
series: [
|
{
|
name: "固废类型",
|
type: "pie",
|
radius: "60%",
|
center: ["50%", "50%"],
|
data: [
|
{ value: lastItem.粉煤灰, name: "粉煤灰" },
|
{ value: lastItem.石膏, name: "石膏" },
|
{ value: lastItem.石灰, name: "石灰" },
|
],
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
},
|
},
|
itemStyle: {
|
color: function (params) {
|
const colors = ["#909399", "#E6A23C", "#F56C6C"];
|
return colors[params.dataIndex];
|
},
|
},
|
},
|
],
|
};
|
});
|
|
// 事件处理
|
const handleDateTypeChange = () => {
|
updateCharts();
|
};
|
|
// 初始化图表
|
const initCharts = () => {
|
if (trendChart.value) {
|
trendChartInstance = echarts.init(trendChart.value);
|
trendChartInstance.setOption(trendChartOption.value);
|
}
|
|
if (distributionChart.value) {
|
distributionChartInstance = echarts.init(distributionChart.value);
|
distributionChartInstance.setOption(distributionChartOption.value);
|
}
|
};
|
|
// 更新图表
|
const updateCharts = () => {
|
if (trendChartInstance) {
|
trendChartInstance.setOption(trendChartOption.value);
|
}
|
|
if (distributionChartInstance) {
|
distributionChartInstance.setOption(distributionChartOption.value);
|
}
|
};
|
|
// 调整图表大小
|
const resizeCharts = () => {
|
trendChartInstance?.resize();
|
distributionChartInstance?.resize();
|
};
|
|
// 窗口大小变化处理
|
const handleResize = () => {
|
// 延迟执行,确保DOM更新完成
|
setTimeout(() => {
|
resizeCharts();
|
}, 100);
|
};
|
|
// 获取固废类型标签类型
|
const getWasteTypeType = type => {
|
const typeMap = {
|
粉煤灰: "info",
|
石膏: "warning",
|
石灰: "danger",
|
};
|
return typeMap[type] || "info";
|
};
|
|
// 生命周期钩子
|
onMounted(() => {
|
// 使用nextTick确保DOM完全渲染后再初始化
|
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>
|