<template>
|
<div class="dashboard-container">
|
<div class="data-dashboard">
|
|
<!-- 顶部标题栏 -->
|
<!-- <div class="dashboard-header">
|
<div class="factory-name">生产统计看板</div>
|
</div> -->
|
|
<!-- 筛选区域 -->
|
<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 class="filter-section">
|
<span class="filter-label">产品类型:</span>
|
<el-radio-group v-model="productType" @change="handleProductTypeChange" class="radio-group">
|
<el-radio-button label="block">砌块</el-radio-button>
|
<el-radio-button label="plate">板材</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="chart-container">
|
<div ref="productionChart" style="width: 100%; height: 100%"></div>
|
</div>
|
</div>
|
<div class="panel-card card-2">
|
<div class="panel-title">固废处理量</div>
|
<div class="chart-container">
|
<div ref="solidWasteChart" style="width: 100%; height: 100%"></div>
|
</div>
|
</div>
|
<div class="panel-card card-3">
|
<div class="panel-title">综合统计</div>
|
<div class="stats-grid">
|
<div class="stat-item">
|
<div class="stat-label">总产能</div>
|
<div class="stat-value">{{ totalProduction }}</div>
|
<div class="stat-unit">立方米</div>
|
</div>
|
<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">平均单耗</div>
|
<div class="stat-value">{{ averageUnitConsumption }}</div>
|
<div class="stat-unit">吨/立方米</div>
|
</div>
|
<div class="stat-item">
|
<div class="stat-label">总能耗</div>
|
<div class="stat-value">{{ totalEnergy }}</div>
|
<div class="stat-unit">kWh</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 第二行 -->
|
<div class="row row-2">
|
<div class="panel-card card-4">
|
<div class="panel-title">生产成本单耗</div>
|
<div class="chart-container">
|
<div ref="costChart" style="width: 100%; height: 100%"></div>
|
</div>
|
</div>
|
<div class="panel-card card-5">
|
<div class="panel-title">生产能耗数据</div>
|
<div class="chart-container">
|
<div ref="energyChart" style="width: 100%; height: 100%"></div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 第三行 -->
|
<div class="row row-3">
|
<div class="panel-card card-6">
|
<div class="panel-title">单耗数据明细</div>
|
<div class="table-container">
|
<el-table :data="costTableData" style="width: 100%">
|
<el-table-column prop="material" label="物料类型" width="120" />
|
<el-table-column prop="unit" label="单位" width="100" />
|
<el-table-column prop="monthlyConsumption" label="月度累计用量" />
|
<el-table-column prop="monthlyProduction" label="月度累计产量" />
|
<el-table-column prop="monthlyUnitConsumption" label="月度累计单耗" />
|
<el-table-column prop="yearlyConsumption" label="年度累计用量" />
|
<el-table-column prop="yearlyProduction" label="年度累计产量" />
|
<el-table-column prop="yearlyUnitConsumption" label="年度累计单耗" />
|
</el-table>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
import * as echarts from 'echarts'
|
|
// 筛选条件
|
const dateType = ref('month') // month 或 year
|
const productType = ref('block') // block 或 plate
|
|
// 图表引用
|
const productionChart = ref(null)
|
const solidWasteChart = ref(null)
|
const costChart = ref(null)
|
const energyChart = ref(null)
|
|
// 图表实例
|
let productionChartInstance = null
|
let solidWasteChartInstance = null
|
let costChartInstance = null
|
let energyChartInstance = null
|
|
// 模拟数据
|
const productionData = ref({
|
month: [
|
{ name: '1月', block: 1200, plate: 800 },
|
{ name: '2月', block: 1300, plate: 850 },
|
{ name: '3月', block: 1100, plate: 750 },
|
{ name: '4月', block: 1400, plate: 900 },
|
{ name: '5月', block: 1500, plate: 950 },
|
{ name: '6月', block: 1350, plate: 880 },
|
{ name: '7月', block: 1450, plate: 920 },
|
{ name: '8月', block: 1600, plate: 1000 },
|
{ name: '9月', block: 1550, plate: 980 },
|
{ name: '10月', block: 1700, plate: 1050 },
|
{ name: '11月', block: 1650, plate: 1020 },
|
{ name: '12月', block: 1800, plate: 1100 }
|
],
|
year: [
|
{ name: '2023', block: 15000, plate: 9500 },
|
{ name: '2024', block: 16500, plate: 10200 },
|
{ name: '2025', block: 18000, plate: 11000 }
|
]
|
})
|
|
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: '2023', 粉煤灰: 2500, 石膏: 1800, 石灰: 1200 },
|
{ name: '2024', 粉煤灰: 2700, 石膏: 1950, 石灰: 1300 },
|
{ name: '2025', 粉煤灰: 2900, 石膏: 2100, 石灰: 1400 }
|
]
|
})
|
|
const costData = ref({
|
materials: ['水泥', '铝粉膏', '脱模剂', '防腐剂', '氯化剂', '冷拔丝'],
|
month: {
|
consumption: [1200, 50, 80, 30, 40, 60],
|
production: [12000, 12000, 12000, 8000, 8000, 8000],
|
unitConsumption: [0.1, 0.0042, 0.0067, 0.0038, 0.005, 0.0075]
|
},
|
year: {
|
consumption: [14000, 600, 950, 350, 480, 720],
|
production: [140000, 140000, 140000, 95000, 95000, 95000],
|
unitConsumption: [0.1, 0.0043, 0.0068, 0.0037, 0.0051, 0.0076]
|
}
|
})
|
|
const energyData = ref({
|
month: [
|
{ name: '1月', 电量: 12000, 水量: 8000, 气量: 5000 },
|
{ name: '2月', 电量: 13000, 水量: 8500, 气量: 5500 },
|
{ name: '3月', 电量: 11000, 水量: 7500, 气量: 4800 },
|
{ name: '4月', 电量: 14000, 水量: 9000, 气量: 6000 },
|
{ name: '5月', 电量: 15000, 水量: 9500, 气量: 6500 },
|
{ name: '6月', 电量: 13500, 水量: 8800, 气量: 5800 }
|
],
|
year: [
|
{ name: '2023', 电量: 140000, 水量: 95000, 气量: 65000 },
|
{ name: '2024', 电量: 150000, 水量: 100000, 气量: 70000 },
|
{ name: '2025', 电量: 160000, 水量: 105000, 气量: 75000 }
|
]
|
})
|
|
// 计算属性
|
const productionChartOption = computed(() => {
|
const data = productionData.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: 'line',
|
data: data.map(item => item.block),
|
smooth: true,
|
lineStyle: {
|
width: 3
|
},
|
itemStyle: {
|
color: '#409EFF'
|
}
|
},
|
{
|
name: '板材',
|
type: 'line',
|
data: data.map(item => item.plate),
|
smooth: true,
|
lineStyle: {
|
width: 3
|
},
|
itemStyle: {
|
color: '#67C23A'
|
}
|
}
|
]
|
}
|
})
|
|
const solidWasteChartOption = 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 costChartOption = computed(() => {
|
const data = costData.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.materials,
|
axisLabel: {
|
color: '#333',
|
rotate: 45
|
}
|
},
|
yAxis: {
|
type: 'value',
|
name: '单耗 (吨/立方米)',
|
axisLabel: {
|
color: '#333'
|
}
|
},
|
series: [
|
{
|
name: '月度单耗',
|
type: 'bar',
|
data: data.month.unitConsumption,
|
itemStyle: {
|
color: '#409EFF'
|
}
|
},
|
{
|
name: '年度单耗',
|
type: 'bar',
|
data: data.year.unitConsumption,
|
itemStyle: {
|
color: '#67C23A'
|
}
|
}
|
]
|
}
|
})
|
|
const energyChartOption = computed(() => {
|
const data = energyData.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: 'line',
|
data: data.map(item => item.电量),
|
smooth: true,
|
lineStyle: {
|
width: 3
|
},
|
itemStyle: {
|
color: '#409EFF'
|
}
|
},
|
{
|
name: '水量',
|
type: 'line',
|
data: data.map(item => item.水量),
|
smooth: true,
|
lineStyle: {
|
width: 3
|
},
|
itemStyle: {
|
color: '#67C23A'
|
}
|
},
|
{
|
name: '气量',
|
type: 'line',
|
data: data.map(item => item.气量),
|
smooth: true,
|
lineStyle: {
|
width: 3
|
},
|
itemStyle: {
|
color: '#E6A23C'
|
}
|
}
|
]
|
}
|
})
|
|
const costTableData = computed(() => {
|
const data = costData.value
|
const materials = data.materials
|
const monthData = data.month
|
const yearData = data.year
|
|
return materials.map((material, index) => ({
|
material,
|
unit: '吨/立方米',
|
monthlyConsumption: monthData.consumption[index],
|
monthlyProduction: monthData.production[index],
|
monthlyUnitConsumption: monthData.unitConsumption[index].toFixed(4),
|
yearlyConsumption: yearData.consumption[index],
|
yearlyProduction: yearData.production[index],
|
yearlyUnitConsumption: yearData.unitConsumption[index].toFixed(4)
|
}))
|
})
|
|
const totalProduction = computed(() => {
|
const data = productionData.value[dateType.value]
|
if (dateType.value === 'month') {
|
return data.reduce((sum, item) => sum + item[productType.value === 'block' ? 'block' : 'plate'], 0)
|
} else {
|
return data[data.length - 1][productType.value === 'block' ? 'block' : 'plate']
|
}
|
})
|
|
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 averageUnitConsumption = computed(() => {
|
const data = costData.value
|
const unitConsumption = dateType.value === 'month' ? data.month.unitConsumption : data.year.unitConsumption
|
const average = unitConsumption.reduce((sum, value) => sum + value, 0) / unitConsumption.length
|
return average.toFixed(4)
|
})
|
|
const totalEnergy = computed(() => {
|
const data = energyData.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 handleDateTypeChange = () => {
|
updateCharts()
|
}
|
|
const handleProductTypeChange = () => {
|
updateCharts()
|
}
|
|
// 初始化图表
|
const initCharts = () => {
|
if (productionChart.value) {
|
productionChartInstance = echarts.init(productionChart.value)
|
productionChartInstance.setOption(productionChartOption.value)
|
}
|
|
if (solidWasteChart.value) {
|
solidWasteChartInstance = echarts.init(solidWasteChart.value)
|
solidWasteChartInstance.setOption(solidWasteChartOption.value)
|
}
|
|
if (costChart.value) {
|
costChartInstance = echarts.init(costChart.value)
|
costChartInstance.setOption(costChartOption.value)
|
}
|
|
if (energyChart.value) {
|
energyChartInstance = echarts.init(energyChart.value)
|
energyChartInstance.setOption(energyChartOption.value)
|
}
|
}
|
|
// 更新图表
|
const updateCharts = () => {
|
if (productionChartInstance) {
|
productionChartInstance.setOption(productionChartOption.value)
|
}
|
|
if (solidWasteChartInstance) {
|
solidWasteChartInstance.setOption(solidWasteChartOption.value)
|
}
|
|
if (costChartInstance) {
|
costChartInstance.setOption(costChartOption.value)
|
}
|
|
if (energyChartInstance) {
|
energyChartInstance.setOption(energyChartOption.value)
|
}
|
}
|
|
// 调整图表大小
|
const resizeCharts = () => {
|
productionChartInstance?.resize()
|
solidWasteChartInstance?.resize()
|
costChartInstance?.resize()
|
energyChartInstance?.resize()
|
}
|
|
// 窗口大小变化处理
|
const handleResize = () => {
|
// 延迟执行,确保DOM更新完成
|
setTimeout(() => {
|
resizeCharts()
|
}, 100)
|
}
|
|
// 生命周期钩子
|
onMounted(() => {
|
// 使用nextTick确保DOM完全渲染后再初始化
|
nextTick(() => {
|
// 初始化图表
|
initCharts()
|
})
|
|
window.addEventListener('resize', handleResize)
|
})
|
|
onBeforeUnmount(() => {
|
window.removeEventListener('resize', handleResize)
|
|
// 销毁图表实例
|
productionChartInstance?.dispose()
|
solidWasteChartInstance?.dispose()
|
costChartInstance?.dispose()
|
energyChartInstance?.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);
|
}
|
|
.dashboard-header {
|
position: relative;
|
z-index: 1;
|
height: 86px;
|
background-color: #ffffff;
|
border-bottom: 1px solid #e4e7ed;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.factory-name {
|
font-weight: 600;
|
font-size: 32px;
|
color: #303133;
|
}
|
|
.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;
|
}
|
|
/* 第一行:3个卡片 */
|
.row-1 {
|
height: 300px;
|
}
|
|
/* 第二行:2个卡片 */
|
.row-2 {
|
height: 300px;
|
}
|
|
/* 第三行:1个卡片 */
|
.row-3 {
|
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: 0.8;
|
}
|
|
.card-4 {
|
flex: 1.2;
|
}
|
|
.card-5 {
|
flex: 0.8;
|
}
|
|
.card-6 {
|
flex: 1;
|
}
|
|
.panel-card {
|
background-color: #ffffff;
|
border-radius: 8px;
|
border: 1px solid #e4e7ed;
|
overflow: hidden;
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.panel-title {
|
padding: 15px 20px;
|
font-size: 16px;
|
font-weight: 500;
|
color: #303133;
|
border-bottom: 1px solid #e4e7ed;
|
background-color: #fafafa;
|
}
|
|
.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);
|
grid-template-rows: repeat(2, 1fr);
|
gap: 15px;
|
}
|
|
.stat-item {
|
background-color: #fafafa;
|
border-radius: 8px;
|
padding: 15px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
border: 1px solid #e4e7ed;
|
min-height: 80px;
|
}
|
|
.stat-label {
|
font-size: 13px;
|
color: #606266;
|
margin-bottom: 8px;
|
}
|
|
.stat-value {
|
font-size: 20px;
|
font-weight: 600;
|
color: #303133;
|
margin-bottom: 3px;
|
}
|
|
.stat-unit {
|
font-size: 11px;
|
color: #909399;
|
}
|
|
/* 表格样式 */
|
: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;
|
}
|
|
/* 按钮样式 */
|
:deep(.el-radio-button__inner) {
|
border-radius: 4px;
|
}
|
|
:deep(.el-radio-button__orig-radio:checked + .el-radio-button__inner) {
|
background-color: #409eff;
|
border-color: #409eff;
|
color: #ffffff;
|
}
|
</style>
|