ECharts 智慧物流可视化平台实战
📋 项目概述
构建智慧物流管理系统,可视化展示物流网络、配送路线、仓储状态等核心业务数据。
核心功能
- ✅ 物流网络:全国配送路线图
- ✅ 实时追踪:包裹位置与状态
- ✅ 仓储监控:仓库容量与周转
- ✅ 配送优化:路径规划可视化
- ✅ 时效分析:配送时效统计
💻 核心实现
1. 全国物流网络图
typescript
// components/Logistics/NetworkMap.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import { LinesChart, ScatterChart, EffectScatterChart } from 'echarts/charts';
import {
GeoComponent,
TooltipComponent,
LegendComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import 'echarts/map/js/china.js';
import BaseChart from '@/components/Chart/BaseChart';
echarts.use([
LinesChart,
ScatterChart,
EffectScatterChart,
GeoComponent,
TooltipComponent,
LegendComponent,
CanvasRenderer
]);
interface DistributionCenter {
id: string;
name: string;
city: string;
longitude: number;
latitude: number;
type: 'hub' | 'station';
capacity: number;
utilization: number;
}
interface DeliveryRoute {
from: string;
to: string;
fromCoord: [number, number];
toCoord: [number, number];
volume: number;
status: 'normal' | 'delayed' | 'blocked';
}
interface NetworkMapProps {
centers: DistributionCenter[];
routes: DeliveryRoute[];
}
const NetworkMap: React.FC<NetworkMapProps> = ({ centers, routes }) => {
const option = useMemo(() => {
// 转换中心点数据
const centerData = centers.map(center => ({
name: center.name,
value: [center.longitude, center.latitude, center.capacity],
type: center.type,
utilization: center.utilization,
itemStyle: {
color: center.type === 'hub' ? '#ff6b6b' : '#4ecdc4',
shadowBlur: center.type === 'hub' ? 20 : 10,
shadowColor: center.type === 'hub' ? '#ff6b6b' : '#4ecdc4'
},
symbolSize: center.type === 'hub' ? 20 : 12
}));
// 转换路线数据
const routeData = routes.map(route => ({
coords: [route.fromCoord, route.toCoord],
volume: route.volume,
status: route.status,
lineStyle: {
color: getRouteColor(route.status),
width: Math.sqrt(route.volume) / 5,
opacity: 0.6,
curveness: 0.3
}
}));
return {
backgroundColor: '#0f1419',
tooltip: {
trigger: 'item',
formatter: (params: any) => {
if (params.seriesType === 'lines') {
return `
<div style="padding: 8px;">
<div>路线: ${params.data.from} → ${params.data.to}</div>
<div>货量: ${params.data.volume}件</div>
<div>状态: ${getStatusText(params.data.status)}</div>
</div>
`;
} else {
const data = params.data;
return `
<div style="padding: 8px;">
<div style="font-weight: bold;">${data.name}</div>
<div>类型: ${data.type === 'hub' ? '枢纽中心' : '配送站'}</div>
<div>容量: ${data.value[2]}件</div>
<div>利用率: ${data.utilization}%</div>
</div>
`;
}
}
},
legend: {
orient: 'vertical',
right: 20,
top: 20,
data: ['枢纽中心', '配送站', '配送路线'],
textStyle: { color: '#fff' }
},
geo: {
map: 'china',
roam: true,
zoom: 1.2,
label: { show: false },
itemStyle: {
areaColor: '#1a2332',
borderColor: '#2c3e50',
borderWidth: 1
},
emphasis: {
itemStyle: { areaColor: '#2c3e50' }
}
},
series: [
{
name: '枢纽中心',
type: 'effectScatter',
coordinateSystem: 'geo',
data: centerData.filter(d => d.type === 'hub'),
symbolSize: 20,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke',
scale: 3
},
zlevel: 1
},
{
name: '配送站',
type: 'scatter',
coordinateSystem: 'geo',
data: centerData.filter(d => d.type === 'station'),
symbolSize: 12,
zlevel: 1
},
{
name: '配送路线',
type: 'lines',
coordinateSystem: 'geo',
data: routeData,
polyline: false,
large: true,
largeThreshold: 100,
effect: {
show: true,
period: 6,
trailLength: 0.7,
color: '#fff',
symbolSize: 3
},
zlevel: 2
}
]
};
}, [centers, routes]);
return <BaseChart option={option} height={700} />;
};
function getRouteColor(status: string): string {
const colors: Record<string, string> = {
normal: '#52c41a',
delayed: '#faad14',
blocked: '#ff4d4f'
};
return colors[status] || '#52c41a';
}
function getStatusText(status: string): string {
const texts: Record<string, string> = {
normal: '🟢 正常',
delayed: '🟡 延迟',
blocked: '🔴 阻塞'
};
return texts[status] || status;
}
export default NetworkMap;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
2. 配送路线优化图
typescript
// components/Logistics/RouteOptimization.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import { LinesChart, ScatterChart } from 'echarts/charts';
import {
GeoComponent,
TooltipComponent,
TitleComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import BaseChart from '@/components/Chart/BaseChart';
echarts.use([
LinesChart,
ScatterChart,
GeoComponent,
TooltipComponent,
TitleComponent,
CanvasRenderer
]);
interface DeliveryPoint {
id: string;
address: string;
longitude: number;
latitude: number;
priority: 'high' | 'medium' | 'low';
timeWindow: string;
}
interface OptimizedRoute {
points: DeliveryPoint[];
totalDistance: number;
estimatedTime: number;
}
interface RouteOptimizationProps {
routes: OptimizedRoute[];
center: [number, number];
}
const RouteOptimization: React.FC<RouteOptimizationProps> = ({ routes, center }) => {
const option = useMemo(() => {
const allPoints = routes.flatMap(route =>
route.points.map(point => ({
name: point.address,
value: [point.longitude, point.latitude],
priority: point.priority,
timeWindow: point.timeWindow,
itemStyle: {
color: getPriorityColor(point.priority)
},
symbolSize: getPrioritySize(point.priority)
}))
);
const allRoutes = routes.flatMap((route, index) => {
const coords: Array<[number, number]> = [];
for (let i = 0; i < route.points.length - 1; i++) {
coords.push([
route.points[i].longitude,
route.points[i].latitude,
route.points[i + 1].longitude,
route.points[i + 1].latitude
]);
}
return coords.map(coord => ({
coords: [coord.slice(0, 2) as [number, number], coord.slice(2) as [number, number]],
lineStyle: {
color: getRouteColorByIndex(index),
width: 2,
curveness: 0.2
}
}));
});
return {
title: {
text: '配送路线优化',
subtext: `总路线数: ${routes.length}`,
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: (params: any) => {
if (params.seriesType === 'scatter') {
return `
<div style="padding: 8px;">
<div>${params.data.name}</div>
<div>优先级: ${getPriorityText(params.data.priority)}</div>
<div>时间窗: ${params.data.timeWindow}</div>
</div>
`;
}
return '';
}
},
geo: {
map: 'china',
roam: true,
center: center,
zoom: 12,
itemStyle: {
areaColor: '#f0f0f0',
borderColor: '#ccc'
}
},
series: [
{
name: '配送点',
type: 'scatter',
coordinateSystem: 'geo',
data: allPoints,
zlevel: 1
},
{
name: '配送路线',
type: 'lines',
coordinateSystem: 'geo',
data: allRoutes,
effect: {
show: true,
period: 4,
trailLength: 0.5,
color: '#fff',
symbolSize: 4
},
zlevel: 2
}
]
};
}, [routes, center]);
return <BaseChart option={option} height={600} />;
};
function getPriorityColor(priority: string): string {
const colors: Record<string, string> = {
high: '#ff4d4f',
medium: '#faad14',
low: '#52c41a'
};
return colors[priority] || '#52c41a';
}
function getPrioritySize(priority: string): number {
const sizes: Record<string, number> = {
high: 15,
medium: 10,
low: 8
};
return sizes[priority] || 10;
}
function getPriorityText(priority: string): string {
const texts: Record<string, string> = {
high: '🔴 高',
medium: '🟡 中',
low: '🟢 低'
};
return texts[priority] || priority;
}
function getRouteColorByIndex(index: number): string {
const colors = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'];
return colors[index % colors.length];
}
export default RouteOptimization;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
3. 仓储容量监控
typescript
// components/Logistics/WarehouseMonitor.tsx
import React, { useMemo } from 'react';
import * as echarts from 'echarts/core';
import { BarChart, GaugeChart, PieChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import BaseChart from '@/components/Chart/BaseChart';
echarts.use([
BarChart,
GaugeChart,
PieChart,
GridComponent,
TooltipComponent,
LegendComponent,
CanvasRenderer
]);
interface WarehouseData {
id: string;
name: string;
totalCapacity: number;
usedCapacity: number;
turnover: number;
categories: Array<{
name: string;
value: number;
}>;
}
interface WarehouseMonitorProps {
warehouses: WarehouseData[];
}
const WarehouseMonitor: React.FC<WarehouseMonitorProps> = ({ warehouses }) => {
// 容量使用率柱状图
const barOption = useMemo(() => ({
title: { text: '仓库容量使用率' },
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
const data = params[0];
return `${data.name}<br/>使用率: ${data.value}%`;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: warehouses.map(w => w.name),
axisLabel: { rotate: 45 }
},
yAxis: {
type: 'value',
max: 100,
axisLabel: { formatter: '{value}%' }
},
series: [{
type: 'bar',
data: warehouses.map(w => ({
value: (w.usedCapacity / w.totalCapacity * 100).toFixed(1),
itemStyle: {
color: getCapacityColor(w.usedCapacity / w.totalCapacity)
}
})),
barWidth: '60%'
}]
}), [warehouses]);
// 库存分类饼图
const pieOption = useMemo(() => {
const totalCategories = {};
warehouses.forEach(w => {
w.categories.forEach(cat => {
totalCategories[cat.name] = (totalCategories[cat.name] || 0) + cat.value;
});
});
return {
title: { text: '库存分类占比' },
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [{
type: 'pie',
radius: '50%',
data: Object.entries(totalCategories).map(([name, value]) => ({
name,
value
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
}, [warehouses]);
return (
<div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: '20px' }}>
<BaseChart option={barOption} height={400} />
<BaseChart option={pieOption} height={400} />
</div>
);
};
function getCapacityColor(ratio: number): string {
if (ratio > 0.9) return '#ff4d4f';
if (ratio > 0.7) return '#faad14';
return '#52c41a';
}
export default WarehouseMonitor;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
💎 总结
本实战案例展示了智慧物流的核心可视化方案:
- 物流网络:全国配送路线与枢纽
- 路径优化:智能路线规划
- 仓储监控:容量与周转分析
适用于物流管理、供应链优化等场景。
