<template>
|
<div class="dashboard">
|
<div class="header">
|
<h1>静置库库位分析看板</h1>
|
<div class="header-info">
|
<span class="time">{{ currentTime }}</span>
|
<span class="date">{{ currentDate }}</span>
|
</div>
|
</div>
|
|
<div class="content">
|
<!-- 第一行:超时库位报警、库位统计、高温库位状态 -->
|
<div class="row">
|
<div class="card">
|
<div class="card-header">
|
<h3>超时库位报警</h3>
|
<div class="card-icon">⚠️</div>
|
</div>
|
<div ref="timeoutChart" class="chart-container"></div>
|
</div>
|
<div class="card">
|
<div class="card-header">
|
<h3>库位统计</h3>
|
<div class="card-icon">📊</div>
|
</div>
|
<div ref="storageStatsChart" class="chart-container"></div>
|
</div>
|
<div class="card">
|
<div class="card-header">
|
<h3>高温库位状态统计</h3>
|
<div class="card-icon">🌡️</div>
|
</div>
|
<div ref="highTempChart" class="chart-container"></div>
|
</div>
|
</div>
|
|
<!-- 第二行:AGV利用率、库位状态详情 -->
|
<div class="row">
|
<div class="card">
|
<div class="card-header">
|
<h3>常温库AGV利用率</h3>
|
<div class="card-icon">🤖</div>
|
</div>
|
<div ref="agvUsageChart" class="chart-container"></div>
|
</div>
|
<div class="card wide">
|
<div class="card-header">
|
<h3>常温库位状态统计</h3>
|
<div class="card-icon">📋</div>
|
</div>
|
<div class="table-wrapper">
|
<table class="data-table">
|
<thead>
|
<tr>
|
<th v-for="header in tableHeaders" :key="header">{{ header }}</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr v-for="(row, rowIndex) in tableData" :key="rowIndex">
|
<td v-for="(cell, cellIndex) in row" :key="cellIndex">{{ cell }}</td>
|
</tr>
|
</tbody>
|
</table>
|
</div>
|
</div>
|
<div class="card">
|
<div class="card-header">
|
<h3>高温库AGV利用率</h3>
|
<div class="card-icon">🔥</div>
|
</div>
|
<div ref="highAgvUsageChart" class="chart-container"></div>
|
</div>
|
</div>
|
|
<!-- 第三行:出库趋势 -->
|
<div class="row">
|
<div class="card full-width">
|
<div class="card-header">
|
<h3>常温库出库料框数时趋势</h3>
|
<div class="card-icon">📈</div>
|
</div>
|
<div ref="outboundTrendChart" class="chart-container large"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onUnmounted, onBeforeUnmount } from 'vue';
|
import * as echarts from 'echarts';
|
import dayjs from 'dayjs';
|
|
// 图表引用
|
const timeoutChart = ref(null);
|
const storageStatsChart = ref(null);
|
const highTempChart = ref(null);
|
const agvUsageChart = ref(null);
|
const highAgvUsageChart = ref(null);
|
const outboundTrendChart = ref(null);
|
|
// 存储图表实例
|
let chartInstances = [];
|
|
// 时间相关
|
const currentTime = ref('');
|
const currentDate = ref('');
|
let timeInterval = null;
|
|
// 更新时间
|
const updateTime = () => {
|
const now = dayjs();
|
currentTime.value = now.format('HH:mm:ss');
|
currentDate.value = now.format('YYYY年MM月DD日 dddd');
|
};
|
|
// 表格数据
|
const tableHeaders = ['库位区域', '库位号', '条码', '静置上限时长', '已静置时长'];
|
const tableData = ref([
|
['常温库', 'G-13-11-04', 'TA7862-0051', '300m', '330m'],
|
['常温库', 'G-13-11-05', 'TA7862-0052', '300m', '280m'],
|
['常温库', 'G-13-11-06', 'TA7862-0053', '300m', '250m'],
|
['高温库', 'H-15-12-01', 'TB7863-0101', '240m', '220m'],
|
['高温库', 'H-15-12-02', 'TB7863-0102', '240m', '180m']
|
]);
|
|
// 初始化图表
|
const initCharts = () => {
|
// 超时库位报警环形图
|
const timeoutChartInstance = echarts.init(timeoutChart.value);
|
timeoutChartInstance.setOption({
|
tooltip: {
|
trigger: 'item',
|
formatter: '{b}: {c} ({d}%)'
|
},
|
legend: {
|
orient: 'vertical',
|
left: 'left',
|
textStyle: {
|
color: '#fff'
|
}
|
},
|
series: [{
|
type: 'pie',
|
radius: ['40%', '70%'],
|
avoidLabelOverlap: false,
|
label: {
|
show: true,
|
color: '#fff'
|
},
|
emphasis: {
|
label: {
|
show: true,
|
fontSize: 16,
|
fontWeight: 'bold'
|
}
|
},
|
data: [
|
{ value: 43, name: '占用' },
|
{ value: 57, name: '空闲' }
|
],
|
itemStyle: {
|
borderRadius: 10,
|
borderColor: '#fff',
|
borderWidth: 2,
|
color: (params) => {
|
const colorList = ['#2196F3', '#4CAF50'];
|
return colorList[params.dataIndex];
|
}
|
}
|
}]
|
});
|
chartInstances.push(timeoutChartInstance);
|
|
// 库位统计柱状图
|
const storageStatsChartInstance = echarts.init(storageStatsChart.value);
|
storageStatsChartInstance.setOption({
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'shadow'
|
}
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
data: ['常温总', '常温占用', '常温空闲', '高温总', '高温占用', '高温空闲'],
|
axisLabel: {
|
color: '#fff',
|
interval: 0,
|
rotate: 30
|
}
|
},
|
yAxis: {
|
type: 'value',
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
series: [{
|
data: [100, 57, 43, 115, 62, 38],
|
type: 'bar',
|
barWidth: '40%',
|
itemStyle: {
|
color: (params) => {
|
const colorList = ['#2196F3', '#1976D2', '#64B5F6', '#FF5722', '#E64A19', '#FF8A65'];
|
return colorList[params.dataIndex];
|
},
|
borderRadius: [5, 5, 0, 0]
|
}
|
}]
|
});
|
chartInstances.push(storageStatsChartInstance);
|
|
// 高温库位状态统计环形图
|
const highTempChartInstance = echarts.init(highTempChart.value);
|
highTempChartInstance.setOption({
|
tooltip: {
|
trigger: 'item',
|
formatter: '{b}: {c} ({d}%)'
|
},
|
legend: {
|
orient: 'vertical',
|
left: 'left',
|
textStyle: {
|
color: '#fff'
|
}
|
},
|
series: [{
|
type: 'pie',
|
radius: ['40%', '70%'],
|
avoidLabelOverlap: false,
|
label: {
|
show: true,
|
color: '#fff'
|
},
|
emphasis: {
|
label: {
|
show: true,
|
fontSize: 16,
|
fontWeight: 'bold'
|
}
|
},
|
data: [
|
{ value: 62, name: '占用' },
|
{ value: 38, name: '空闲' }
|
],
|
itemStyle: {
|
borderRadius: 10,
|
borderColor: '#fff',
|
borderWidth: 2,
|
color: (params) => {
|
const colorList = ['#FF5722', '#8BC34A'];
|
return colorList[params.dataIndex];
|
}
|
}
|
}]
|
});
|
chartInstances.push(highTempChartInstance);
|
|
// 常温库AGV利用率条形图
|
const agvUsageChartInstance = echarts.init(agvUsageChart.value);
|
agvUsageChartInstance.setOption({
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'shadow'
|
}
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
data: ['A001', 'A002', 'A003', 'A004', 'A005'],
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
yAxis: {
|
type: 'value',
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
series: [{
|
data: [77, 71, 81, 85, 77],
|
type: 'bar',
|
barWidth: '40%',
|
itemStyle: {
|
color: '#FFC107',
|
borderRadius: [5, 5, 0, 0]
|
},
|
label: {
|
show: true,
|
position: 'top',
|
color: '#fff'
|
}
|
}]
|
});
|
chartInstances.push(agvUsageChartInstance);
|
|
// 高温库AGV利用率条形图
|
const highAgvUsageChartInstance = echarts.init(highAgvUsageChart.value);
|
highAgvUsageChartInstance.setOption({
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'shadow'
|
}
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
data: ['A006', 'A007', 'A008', 'A009', 'A011'],
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
yAxis: {
|
type: 'value',
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
series: [{
|
data: [85, 71, 63, 69, 69],
|
type: 'bar',
|
barWidth: '40%',
|
itemStyle: {
|
color: '#FFC107',
|
borderRadius: [5, 5, 0, 0]
|
},
|
label: {
|
show: true,
|
position: 'top',
|
color: '#fff'
|
}
|
}]
|
});
|
chartInstances.push(highAgvUsageChartInstance);
|
|
// 出库料框数时趋势折线图
|
const outboundTrendChartInstance = echarts.init(outboundTrendChart.value);
|
outboundTrendChartInstance.setOption({
|
tooltip: {
|
trigger: 'axis'
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
data: ['0时', '4时', '8时', '12时', '16时', '20时'],
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
yAxis: {
|
type: 'value',
|
axisLabel: {
|
color: '#fff'
|
}
|
},
|
series: [{
|
name: '出库料框数',
|
data: [12, 19, 3, 5, 2, 3],
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 8,
|
itemStyle: {
|
color: '#E91E63'
|
},
|
lineStyle: {
|
width: 3
|
},
|
areaStyle: {
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
{ offset: 0, color: 'rgba(233, 30, 99, 0.3)' },
|
{ offset: 1, color: 'rgba(233, 30, 99, 0.05)' }
|
])
|
},
|
label: {
|
show: true,
|
position: 'top',
|
color: '#fff'
|
}
|
}]
|
});
|
chartInstances.push(outboundTrendChartInstance);
|
};
|
|
// 组件挂载后初始化图表和时间更新
|
onMounted(() => {
|
updateTime();
|
timeInterval = setInterval(updateTime, 1000);
|
initCharts();
|
|
// 监听窗口大小变化,调整图表尺寸
|
window.addEventListener('resize', handleResize);
|
});
|
|
// 处理窗口大小变化
|
const handleResize = () => {
|
chartInstances.forEach(chart => chart.resize());
|
};
|
|
// 组件卸载前清理资源
|
onBeforeUnmount(() => {
|
if (timeInterval) {
|
clearInterval(timeInterval);
|
}
|
window.removeEventListener('resize', handleResize);
|
chartInstances.forEach(chart => chart.dispose());
|
chartInstances = [];
|
});
|
</script>
|
|
<style scoped>
|
* {
|
box-sizing: border-box;
|
}
|
|
.dashboard {
|
min-height: 100vh;
|
padding: 20px;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
background: linear-gradient(135deg, #0a192f 0%, #112240 100%);
|
color: #fff;
|
}
|
|
.header {
|
text-align: center;
|
margin-bottom: 30px;
|
padding: 20px 0;
|
background: rgba(16, 35, 63, 0.3);
|
border-radius: 10px;
|
backdrop-filter: blur(10px);
|
}
|
|
.header h1 {
|
margin: 0;
|
font-size: 32px;
|
font-weight: 600;
|
background: linear-gradient(90deg, #64ffda, #00bcd4);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
background-clip: text;
|
text-shadow: 0 0 20px rgba(100, 255, 218, 0.3);
|
}
|
|
.header-info {
|
margin-top: 10px;
|
display: flex;
|
justify-content: center;
|
gap: 20px;
|
font-size: 14px;
|
color: #8892b0;
|
}
|
|
.header-info span {
|
padding: 5px 15px;
|
background: rgba(100, 255, 218, 0.1);
|
border-radius: 20px;
|
border: 1px solid rgba(100, 255, 218, 0.2);
|
}
|
|
.content {
|
max-width: 1800px;
|
margin: 0 auto;
|
}
|
|
.row {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
|
.card {
|
background: rgba(17, 34, 64, 0.7);
|
border-radius: 15px;
|
padding: 20px;
|
flex: 1;
|
min-width: 350px;
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
border: 1px solid rgba(100, 255, 218, 0.1);
|
}
|
|
.card:hover {
|
transform: translateY(-5px);
|
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.2);
|
}
|
|
.card.wide {
|
flex: 2;
|
min-width: 500px;
|
}
|
|
.card.full-width {
|
flex: 100%;
|
min-width: 100%;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
padding-bottom: 10px;
|
border-bottom: 1px solid rgba(100, 255, 218, 0.2);
|
}
|
|
.card-header h3 {
|
margin: 0;
|
font-size: 18px;
|
font-weight: 500;
|
color: #64ffda;
|
}
|
|
.card-icon {
|
font-size: 20px;
|
}
|
|
.chart-container {
|
height: 280px;
|
width: 100%;
|
}
|
|
.chart-container.large {
|
height: 350px;
|
}
|
|
.table-wrapper {
|
max-height: 300px;
|
overflow-y: auto;
|
padding-right: 5px;
|
}
|
|
.table-wrapper::-webkit-scrollbar {
|
width: 6px;
|
}
|
|
.table-wrapper::-webkit-scrollbar-track {
|
background: rgba(100, 255, 218, 0.1);
|
border-radius: 3px;
|
}
|
|
.table-wrapper::-webkit-scrollbar-thumb {
|
background: rgba(100, 255, 218, 0.3);
|
border-radius: 3px;
|
}
|
|
.table-wrapper::-webkit-scrollbar-thumb:hover {
|
background: rgba(100, 255, 218, 0.5);
|
}
|
|
.data-table {
|
width: 100%;
|
border-collapse: collapse;
|
font-size: 14px;
|
}
|
|
.data-table thead {
|
position: sticky;
|
top: 0;
|
z-index: 1;
|
}
|
|
.data-table th {
|
padding: 12px;
|
text-align: left;
|
background: rgba(100, 255, 218, 0.1);
|
color: #64ffda;
|
font-weight: 500;
|
border-bottom: 2px solid rgba(100, 255, 218, 0.2);
|
}
|
|
.data-table td {
|
padding: 12px;
|
text-align: left;
|
border-bottom: 1px solid rgba(100, 255, 218, 0.1);
|
color: #ccd6f6;
|
}
|
|
.data-table tbody tr:hover {
|
background: rgba(100, 255, 218, 0.05);
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 1200px) {
|
.card {
|
min-width: 300px;
|
}
|
|
.card.wide {
|
min-width: 100%;
|
}
|
}
|
|
@media (max-width: 768px) {
|
.dashboard {
|
padding: 10px;
|
}
|
|
.header h1 {
|
font-size: 24px;
|
}
|
|
.header-info {
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.card {
|
min-width: 100%;
|
}
|
|
.chart-container {
|
height: 250px;
|
}
|
|
.chart-container.large {
|
height: 300px;
|
}
|
}
|
</style>
|