<template>
|
<div class="wms-dashboard">
|
<!-- 顶部标题栏 -->
|
<div class="header">
|
<h1>WMS仓储可视化图表看板</h1>
|
<div class="header-right">
|
<span>2025-12-24 16:11:44</span>
|
<el-select v-model="month" placeholder="本月" style="margin-left: 10px; width: 80px;">
|
<el-option label="本月" value="month"></el-option>
|
<el-option label="上月" value="lastMonth"></el-option>
|
</el-select>
|
<el-button type="primary" style="margin-left: 10px;" @click="refreshCharts">刷新</el-button>
|
</div>
|
</div>
|
|
<!-- 统计卡片区域 -->
|
<el-row :gutter="20" class="stats-card-row">
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="card-title">总库存(件)</div>
|
<div class="card-value">269,225</div>
|
<div class="card-change"><el-tag type="success">↑ 2.1% 较昨日</el-tag></div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="card-title">待出库订单</div>
|
<div class="card-value">425</div>
|
<div class="card-change"><el-tag type="warning">↑ 5.3% 较1小时前</el-tag></div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="card-title">今日作业完成率</div>
|
<div class="card-value">93.9%</div>
|
<div class="card-change"><el-tag type="success">↑ 1.8% 较昨日</el-tag></div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="stats-card">
|
<div class="card-title">未处理异常</div>
|
<div class="card-value">7</div>
|
<div class="card-change"><el-tag type="danger">↑ 1 较30分钟前</el-tag></div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 图表区域(第一行) -->
|
<el-row :gutter="20" class="chart-row">
|
<el-col :span="12">
|
<el-card class="chart-card">
|
<div class="chart-title">库存库位分布(图像化占比)<el-button type="text" class="view-btn">切换视图</el-button></div>
|
<div ref="inventoryPieRef" class="chart-container"></div>
|
</el-card>
|
</el-col>
|
<el-col :span="12">
|
<el-card class="chart-card">
|
<div class="chart-title">近7日出入库趋势(图像化走势)
|
<el-button-group class="btn-group">
|
<el-button type="primary" size="small">全部</el-button>
|
<el-button type="default" size="small">入库</el-button>
|
<el-button type="default" size="small">出库</el-button>
|
</el-button-group>
|
</div>
|
<div ref="stockTrendRef" class="chart-container"></div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 图表区域(第二行) -->
|
<el-row :gutter="20" class="chart-row">
|
<el-col :span="8">
|
<el-card class="chart-card">
|
<div class="chart-title">库位利用率</div>
|
<div ref="locationRateRef" class="chart-container"></div>
|
</el-card>
|
</el-col>
|
<el-col :span="8">
|
<el-card class="chart-card">
|
<div class="chart-title">作业类型分布<el-button type="text" class="view-btn">查看详情</el-button></div>
|
<div ref="operationRadarRef" class="chart-container"></div>
|
</el-card>
|
</el-col>
|
<el-col :span="8">
|
<el-card class="chart-card">
|
<div class="chart-title">异常类型统计趋势<el-button type="text" class="view-btn">筛选</el-button></div>
|
<div ref="exceptionTrendRef" class="chart-container"></div>
|
</el-card>
|
</el-col>
|
</el-row>
|
|
<!-- 表格区域 -->
|
<el-row :gutter="20" class="table-row">
|
<el-col :span="12">
|
<el-card class="table-card">
|
<div class="table-title">订单处理进度<el-select v-model="orderType" placeholder="退货入库"
|
style="width: 100px; margin-left: 10px;">
|
<el-option label="退货入库" value="return"></el-option>
|
<el-option label="普通出库" value="normal"></el-option>
|
</el-select></div>
|
<el-table :data="orderList" border style="width: 100%;">
|
<el-table-column prop="orderNo" label="订单号" />
|
<el-table-column prop="type" label="类型" />
|
<el-table-column prop="priority" label="优先级">
|
<template #default="scope">
|
<el-tag
|
:type="scope.row.priority === '紧急' ? 'danger' : scope.row.priority === '加急' ? 'warning' : 'success'">
|
{{ scope.row.priority }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="progress" label="进度">
|
<template #default="scope">
|
<el-progress :percentage="scope.row.progress" :color="scope.row.progressColor" />
|
</template>
|
</el-table-column>
|
</el-table>
|
<div class="table-pagination">
|
<el-button type="text">加载更多订单</el-button>
|
<el-pagination layout="prev, pager, next, jumper" :current-page="1" :total="50" />
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="12">
|
<el-card class="table-card">
|
<div class="table-title">实时作业监控
|
<el-button-group class="btn-group">
|
<el-button type="primary" size="small">全部作业</el-button>
|
<el-button type="default" size="small">入库</el-button>
|
<el-button type="default" size="small">出库</el-button>
|
<el-button type="default" size="small">盘点</el-button>
|
</el-button-group>
|
</div>
|
<el-table :data="operationList" border style="width: 100%;">
|
<el-table-column prop="opNo" label="作业单号" />
|
<el-table-column prop="opType" label="作业类型" />
|
<el-table-column prop="operator" label="操作人员" />
|
<el-table-column prop="startTime" label="开始时间" />
|
<el-table-column prop="status" label="当前状态">
|
<template #default="scope">
|
<el-tag :type="scope.row.status === '处理中' ? 'info' : scope.row.status === '已完成' ? 'success' : 'danger'">
|
{{ scope.row.status }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="操作"><el-button type="text">详情</el-button></el-table-column>
|
</el-table>
|
<div class="table-pagination">
|
<el-pagination layout="prev, pager, next, jumper" :current-page="1" :total="50" />
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
import * as echarts from 'echarts';
|
|
// 响应式数据
|
const month = ref('month');
|
const orderType = ref('return');
|
|
// 模拟数据
|
const orderList = ref([
|
{ orderNo: 'OD20251224001', type: '普通出库', priority: '紧急', progress: 60, progressColor: '#ff4d4f' },
|
{ orderNo: 'OD20251224002', type: '退货入库', priority: '普通', progress: 80, progressColor: '#1890ff' },
|
{ orderNo: 'OD20251224003', type: '调拨订单', priority: '加急', progress: 40, progressColor: '#faad14' },
|
{ orderNo: 'OD20251224004', type: '普通出库', priority: '常规', progress: 100, progressColor: '#52c41a' }
|
]);
|
const operationList = ref([
|
{ opNo: 'JW251224001', opType: '入库', operator: '张三', startTime: '15:30:22', status: '处理中' },
|
{ opNo: 'CK251224002', opType: '出库', operator: '李四', startTime: '15:25:10', status: '已完成' },
|
{ opNo: 'PD251224003', opType: '盘点', operator: '王五', startTime: '15:20:05', status: '待确认' },
|
{ opNo: 'SC251224005', opType: '上架', operator: '孙七', startTime: '15:10:18', status: '异常' }
|
]);
|
|
// 图表容器
|
const inventoryPieRef = ref(null);
|
const stockTrendRef = ref(null);
|
const locationRateRef = ref(null);
|
const operationRadarRef = ref(null);
|
const exceptionTrendRef = ref(null);
|
|
// 图表实例
|
let inventoryPieChart = null;
|
let stockTrendChart = null;
|
let locationRateChart = null;
|
let operationRadarChart = null;
|
let exceptionTrendChart = null;
|
|
// 初始化库存库位分布饼图
|
const initInventoryPie = () => {
|
if (!inventoryPieRef.value) return;
|
|
if (inventoryPieChart) {
|
inventoryPieChart.dispose();
|
}
|
|
inventoryPieChart = echarts.init(inventoryPieRef.value);
|
const option = {
|
tooltip: {
|
trigger: 'item',
|
formatter: '{a} <br/>{b}: {c}%'
|
},
|
legend: {
|
bottom: 0,
|
left: 'center',
|
data: ['常温区A区', '冷藏区B区', '保税区C区', '残次品区D区']
|
},
|
series: [{
|
name: '库存库位分布',
|
type: 'pie',
|
radius: ['40%', '70%'],
|
center: ['50%', '40%'],
|
avoidLabelOverlap: false,
|
itemStyle: {
|
borderRadius: 10,
|
borderColor: '#fff',
|
borderWidth: 2
|
},
|
label: {
|
show: false,
|
position: 'center'
|
},
|
emphasis: {
|
label: {
|
show: true,
|
fontSize: 20,
|
fontWeight: 'bold'
|
}
|
},
|
labelLine: {
|
show: false
|
},
|
data: [
|
{ value: 48.7, name: '常温区A区', itemStyle: { color: '#5470c6' } },
|
{ value: 29.2, name: '冷藏区B区', itemStyle: { color: '#91cc75' } },
|
{ value: 21.9, name: '保税区C区', itemStyle: { color: '#fac858' } },
|
{ value: 2.2, name: '残次品区D区', itemStyle: { color: '#ee6666' } }
|
]
|
}]
|
};
|
|
inventoryPieChart.setOption(option);
|
return inventoryPieChart;
|
};
|
|
// 初始化近7日出入库趋势图
|
const initStockTrend = () => {
|
if (!stockTrendRef.value) return;
|
|
if (stockTrendChart) {
|
stockTrendChart.dispose();
|
}
|
|
stockTrendChart = echarts.init(stockTrendRef.value);
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross'
|
}
|
},
|
legend: {
|
data: ['入库量', '出库量'],
|
top: 10
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
top: '15%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: true,
|
data: ['12-18', '12-19', '12-20', '12-21', '12-22', '12-23', '12-24']
|
},
|
yAxis: {
|
type: 'value',
|
name: '数量(千件)',
|
max: 25
|
},
|
series: [
|
{
|
name: '入库量',
|
type: 'bar',
|
barWidth: '30%',
|
data: [10, 12, 10, 12, 10, 12, 12],
|
itemStyle: { color: '#52c41a' }
|
},
|
{
|
name: '出库量',
|
type: 'bar',
|
barWidth: '30%',
|
data: [16, 18, 14, 18, 16, 18, 20],
|
itemStyle: { color: '#1890ff' }
|
}
|
]
|
};
|
|
stockTrendChart.setOption(option);
|
return stockTrendChart;
|
};
|
|
// 初始化库位利用率环形图
|
const initLocationRate = () => {
|
if (!locationRateRef.value) return;
|
|
if (locationRateChart) {
|
locationRateChart.dispose();
|
}
|
|
locationRateChart = echarts.init(locationRateRef.value);
|
const option = {
|
tooltip: {
|
formatter: '{a} <br/>{b} : {c}%'
|
},
|
series: [{
|
name: '库位利用率',
|
type: 'gauge',
|
min: 0,
|
max: 100,
|
splitNumber: 10,
|
radius: '90%',
|
center: ['50%', '55%'],
|
startAngle: 180,
|
endAngle: 0,
|
progress: {
|
show: true,
|
width: 20,
|
itemStyle: {
|
color: '#1890ff'
|
}
|
},
|
axisLine: {
|
lineStyle: {
|
width: 20,
|
color: [[1, 'rgba(200,200,200,0.3)']]
|
}
|
},
|
axisTick: {
|
distance: -30,
|
splitNumber: 5,
|
lineStyle: {
|
width: 2,
|
color: '#999'
|
}
|
},
|
splitLine: {
|
distance: -30,
|
length: 14,
|
lineStyle: {
|
width: 3,
|
color: '#999'
|
}
|
},
|
axisLabel: {
|
distance: -20,
|
color: '#999',
|
fontSize: 12
|
},
|
anchor: {
|
show: false
|
},
|
title: {
|
show: true,
|
offsetCenter: [0, '30%'],
|
fontSize: 16,
|
fontWeight: 'bold'
|
},
|
detail: {
|
valueAnimation: true,
|
formatter: '{value}%',
|
fontSize: 20,
|
fontWeight: 'bold',
|
offsetCenter: [0, '70%']
|
},
|
data: [{
|
value: 86.2,
|
name: '库位利用率'
|
}]
|
}]
|
};
|
|
locationRateChart.setOption(option);
|
return locationRateChart;
|
};
|
|
// 初始化作业类型雷达图
|
const initOperationRadar = () => {
|
if (!operationRadarRef.value) return;
|
|
if (operationRadarChart) {
|
operationRadarChart.dispose();
|
}
|
|
operationRadarChart = echarts.init(operationRadarRef.value);
|
const option = {
|
tooltip: {
|
trigger: 'item'
|
},
|
legend: {
|
show: false
|
},
|
radar: {
|
indicator: [
|
{ name: '出库作业', max: 100 },
|
{ name: '入库作业', max: 100 },
|
{ name: '调拨作业', max: 100 },
|
{ name: '盘点作业', max: 100 }
|
],
|
shape: 'circle',
|
splitNumber: 5,
|
axisName: {
|
color: '#666'
|
},
|
splitLine: {
|
lineStyle: {
|
color: 'rgba(0,0,0,0.1)'
|
}
|
},
|
splitArea: {
|
show: true,
|
areaStyle: {
|
color: ['rgba(255,255,255,0.8)', 'rgba(200,200,200,0.2)']
|
}
|
}
|
},
|
series: [{
|
name: '作业类型分布',
|
type: 'radar',
|
symbolSize: 8,
|
areaStyle: {
|
color: 'rgba(24,144,255,0.3)'
|
},
|
lineStyle: {
|
width: 2
|
},
|
itemStyle: {
|
color: '#1890ff'
|
},
|
data: [{
|
value: [45, 30, 15, 10],
|
name: '作业占比',
|
label: {
|
show: true,
|
formatter: function (params) {
|
return params.value + '%';
|
}
|
}
|
}]
|
}]
|
};
|
|
operationRadarChart.setOption(option);
|
return operationRadarChart;
|
};
|
|
// 初始化异常趋势折线图
|
const initExceptionTrend = () => {
|
if (!exceptionTrendRef.value) return;
|
|
if (exceptionTrendChart) {
|
exceptionTrendChart.dispose();
|
}
|
|
exceptionTrendChart = echarts.init(exceptionTrendRef.value);
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross'
|
}
|
},
|
legend: {
|
data: ['库存不足', '订单超时', '库位异常', '盘点差异'],
|
top: 10
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
top: '15%',
|
containLabel: true
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
data: ['12-18', '12-19', '12-20', '12-21', '12-22', '12-23', '12-24']
|
},
|
yAxis: {
|
type: 'value',
|
name: '异常数量'
|
},
|
series: [
|
{
|
name: '库存不足',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 8,
|
data: [10, 11, 9, 12, 10, 13, 12],
|
itemStyle: { color: '#ff4d4f' },
|
lineStyle: {
|
width: 3
|
}
|
},
|
{
|
name: '订单超时',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 8,
|
data: [8, 9, 7, 8, 7, 9, 8],
|
itemStyle: { color: '#faad14' },
|
lineStyle: {
|
width: 3
|
}
|
},
|
{
|
name: '库位异常',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 8,
|
data: [4, 5, 2, 4, 3, 5, 4],
|
itemStyle: { color: '#722ed1' },
|
lineStyle: {
|
width: 3
|
}
|
},
|
{
|
name: '盘点差异',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 8,
|
data: [2, 3, 1, 2, 1, 3, 2],
|
itemStyle: { color: '#13c2c2' },
|
lineStyle: {
|
width: 3
|
}
|
}
|
]
|
};
|
|
exceptionTrendChart.setOption(option);
|
return exceptionTrendChart;
|
};
|
|
// 刷新所有图表
|
const refreshCharts = () => {
|
const charts = [
|
initInventoryPie,
|
initStockTrend,
|
initLocationRate,
|
initOperationRadar,
|
initExceptionTrend
|
];
|
|
charts.forEach(initFunc => {
|
const chart = initFunc();
|
if (chart) {
|
chart.resize();
|
}
|
});
|
};
|
|
// 监听窗口大小变化
|
const handleResize = () => {
|
const charts = [
|
inventoryPieChart,
|
stockTrendChart,
|
locationRateChart,
|
operationRadarChart,
|
exceptionTrendChart
|
];
|
|
charts.forEach(chart => {
|
if (chart) {
|
chart.resize();
|
}
|
});
|
};
|
|
// 挂载后初始化图表
|
onMounted(() => {
|
nextTick(() => {
|
initInventoryPie();
|
initStockTrend();
|
initLocationRate();
|
initOperationRadar();
|
initExceptionTrend();
|
|
window.addEventListener('resize', handleResize);
|
});
|
});
|
|
// 组件卸载时清理
|
onUnmounted(() => {
|
const charts = [
|
inventoryPieChart,
|
stockTrendChart,
|
locationRateChart,
|
operationRadarChart,
|
exceptionTrendChart
|
];
|
|
charts.forEach(chart => {
|
if (chart) {
|
chart.dispose();
|
}
|
});
|
|
window.removeEventListener('resize', handleResize);
|
});
|
</script>
|
|
<style scoped>
|
.wms-dashboard {
|
padding: 20px;
|
background: #f5f7fa;
|
min-height: 100vh;
|
box-sizing: border-box;
|
}
|
|
.header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
background: white;
|
padding: 15px 20px;
|
border-radius: 8px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
}
|
|
.stats-card-row,
|
.chart-row,
|
.table-row {
|
margin-bottom: 20px;
|
}
|
|
.stats-card {
|
height: 120px;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
padding: 0 20px;
|
transition: all 0.3s ease;
|
}
|
|
.stats-card:hover {
|
transform: translateY(-2px);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
}
|
|
.card-title {
|
font-size: 14px;
|
color: #666;
|
margin-bottom: 8px;
|
}
|
|
.card-value {
|
font-size: 28px;
|
font-weight: bold;
|
margin: 8px 0 4px;
|
color: #333;
|
}
|
|
.card-change {
|
margin-top: 8px;
|
}
|
|
.chart-card {
|
height: 320px;
|
padding: 15px;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.chart-container {
|
width: 100%;
|
height: 100%;
|
min-height: 250px;
|
flex: 1;
|
}
|
|
.chart-title,
|
.table-title {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
font-size: 16px;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.view-btn {
|
font-size: 12px;
|
}
|
|
.table-pagination {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-top: 15px;
|
}
|
|
.btn-group {
|
margin-left: 10px;
|
}
|
|
/* 确保图表容器有明确尺寸 */
|
:deep(.el-card__body) {
|
height: 100%;
|
display: flex;
|
flex-direction: column;
|
}
|
</style>
|