从 0 到 1 构建一个智慧工厂数字孪生前端项目:Vue3 + Three.js + ECharts 工业大屏实践
从 0 到 1 构建一个智慧工厂数字孪生前端项目:Vue3 + Three.js + ECharts 工业大屏实践
本文基于一个完整的智慧工厂数字孪生前端项目,分享如何使用 Vue3、TypeScript、Vite、Three.js、Element Plus、ECharts 构建一个具备 3D 工厂场景、AGV 巡航、IoT 实时数据、设备告警和数据大屏联动能力的工业可视化系统。

一、项目背景
工业数字孪生项目的前端并不只是把 3D 模型放到页面上。一个相对完整的工厂数字孪生系统,通常需要同时处理这些问题:
- 如何组织复杂的 3D 场景生命周期。
- 如何让设备、AGV、告警、日志等业务数据驱动 3D 表现。
- 如何在工业大屏 UI 中同时展示实时指标、图表、列表和三维场景。
- 如何保证场景运行流畅,并为后续接入真实模型和真实 WebSocket 数据预留扩展能力。
这个项目的目标是构建一个作品集级别的智慧工厂数字孪生前端 Demo,但工程结构和核心模块尽量按照真实商业项目的方式设计,避免把所有逻辑堆在一个组件里。
二、技术栈选择
项目使用的核心技术如下:
- Vue 3:负责页面组件化和响应式 UI。
- TypeScript:提供类型约束,提升大型前端项目的可维护性。
- Vite:作为现代前端构建工具,开发和构建速度都比较轻量。
- Three.js:负责 3D 工厂场景、设备、AGV、灯光、后期和交互。
- Element Plus:提供基础 UI 组件,例如 Tag、Progress。
- ECharts:负责数据大屏中的仪表盘、饼图和趋势图。
- Pinia:管理实时设备、AGV、告警和日志状态。
这套组合的优势是:Vue 负责界面,Three.js 负责空间表达,ECharts 负责数据表达,Pinia 负责状态统一。每一层职责清晰,后期扩展时不会互相拖拽。
三、工程目录设计
项目目录按照功能边界拆分:
src/
assets/ 全局样式与静态资源
components/ 大屏布局、图表和 Three.js 视口组件
managers/ AGV、设备等业务对象管理器
scene/ Three.js 主场景编排
store/ Pinia 实时状态管理
three/ 模型加载、缓存和释放能力
types/ 工厂业务类型定义
utils/ 时间、资源释放等工具函数
views/ 页面级视图
websocket/ WebSocket 实时数据模拟服务
public/
basis/ KTX2 / Basis 纹理解码器
draco/ Draco 模型解码器
这里最关键的设计是把 Three.js 的核心逻辑从 Vue 组件中抽离出去:
FactoryScene.ts负责 Three.js 场景生命周期。AGVController.ts负责 AGV 运动与状态。DeviceManager.ts负责设备实体和交互。ModelManager.ts负责模型加载、缓存和释放。WebSocketService.ts负责实时数据推送模拟。
Vue 组件只负责挂载容器、订阅数据、渲染 UI,这样代码会更接近真实项目中的可维护结构。
四、Three.js 场景初始化
FactoryScene.ts 是整个 3D 系统的核心。它负责创建:
WebGLRendererPerspectiveCameraOrbitControlsEffectComposerUnrealBloomPassFXAA- 灯光、地面、区域、仓储、设备、AGV 路径和交互
场景使用低俯视角观察工厂区域,更符合工业控制大屏的视觉习惯。后期链路采用:
RenderPass -> UnrealBloomPass -> FXAA
Bloom 用来强化设备状态灯、AGV 路径、区域边界和数据看板的发光效果;FXAA 用来缓解高对比发光边缘的锯齿感。
灯光使用组合方式:
HemisphereLight提供整体环境照明。DirectionalLight提供主体阴影层次。- 少量
PointLight强化局部工业科技感。
这类场景不适合堆太多强光,否则会变成廉价的“霓虹效果”。更好的方式是让发光材质、区域线框、Bloom 和暗色 UI 形成层次。
五、AGV 巡航系统设计
AGV 是数字孪生中非常适合展示动态能力的对象。项目中将 AGV 封装为 AGVController,它具备:
- 循环路径巡航
- 多机器人同时运行
- 平滑转向
- 速度配置
- 状态机
- 实时遥测输出
路径基于 CatmullRomCurve3:
this.curve = new THREE.CatmullRomCurve3(options.path, true, 'catmullrom', 0.12);
progress 在 0-1 区间循环推进,AGV 根据曲线采样点更新位置。
转向则使用目标方向计算 yaw,并通过四元数插值完成:
this.targetQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), yaw);
this.group.quaternion.slerp(this.targetQuaternion, Math.min(1, delta * 6));
这样 AGV 在转弯时不会突然“瞬移式转向”,而是有一个平滑的旋转过程。
AGV 状态包括:
idlemovingloadingchargingerror
当状态不是 moving 时,AGV 会暂停路径推进,模拟装载、充电或故障等业务场景。
六、IoT 设备管理与交互
设备由 DeviceManager.ts 统一创建和管理。每个设备包含:
- id
- name
- zone
- status
- temperature
- power
- warning
- position
设备状态包括:
runningwarningerroroffline
状态会映射到不同的颜色与发光强度:
const statusColor: Record<DeviceStatus, number> = {
running: 0x39f5b6,
warning: 0xffc857,
error: 0xff4d6d,
offline: 0x6d7c8d
};
这样 UI 中看到的设备状态,与 3D 场景中设备的颜色表现是一致的。
交互上使用 Raycaster。为了避免地面、路径、装饰模型干扰点击,射线检测只针对设备集合:
const hits = this.raycaster.intersectObjects(
this.deviceManager.getInteractiveObjects(),
true
);
当命中子 Mesh 时,再向父级对象回溯 deviceId,找到对应业务设备。这种方式适合真实 glb 模型,因为真实模型通常会有多层子节点。
七、实时数据系统
项目中的 WebSocketService.ts 目前是前端模拟服务,但它的消息结构按照真实实时系统设计:
snapshot:初始化全量快照。device:update:设备状态更新。agv:update:AGV 状态更新。alarm:实时告警。log:实时日志。
Vue 页面订阅消息后写入 Pinia:
WebSocketService -> Pinia Store -> Vue UI / Three.js Scene
这条数据链路的价值在于:UI 和 3D 场景不是各自模拟数据,而是由同一份状态驱动。设备温度变化、告警出现、AGV 状态变化,会同时影响左侧列表、右侧图表、底部日志和中间 3D 场景。
如果未来要接入真实后端,只需要替换 WebSocketService 的数据来源,保留消息结构即可。
八、数据大屏 UI
页面布局采用典型工业大屏结构:
- 顶部:系统标题、时间、在线设备数量。
- 左侧:实时告警、设备状态。
- 右侧:数据统计、AGV 信息。
- 中间:Three.js 主场景。
- 底部:实时日志。
ECharts 图表包括:
- 设备在线率仪表盘。
- 告警统计环形图。
- 温度趋势折线图。
图表组件 DataCharts.vue 使用 ECharts core 按需注册,而不是全量引入:
echarts.use([
CanvasRenderer,
GaugeChart,
LineChart,
PieChart,
GridComponent,
LegendComponent,
TooltipComponent
]);
这可以减少不必要的包体积,并保持图表模块清晰。
九、模型加载与资源管理
虽然当前场景主要使用程序化几何体构建,但项目已经预留真实 glb/gltf 模型接入能力。
ModelManager.ts 支持:
GLTFLoaderDRACOLoaderKTX2Loader- 模型 Promise 缓存
- 模型实例克隆
- 资源释放
其中一个比较重要的细节是缓存 Promise:
private readonly cache = new Map<string, Promise<GLTF>>();
这样当多个模块同时请求同一个模型时,不会重复发起加载请求,而是共享同一个 Promise。
释放资源时,需要遍历对象树,释放 geometry 和 material。真实项目中如果不做这一步,场景切换或模型频繁加载后很容易出现显存持续增长。
十、性能优化实践
这个项目中使用了几类基础但有效的性能策略:
1. requestAnimationFrame
所有动画统一在 requestAnimationFrame 中推进,避免多个模块各自维护计时器。
2. InstancedMesh
地面发光砖和仓储货箱使用 InstancedMesh,减少重复几何体造成的 draw call。
3. Frustum Culling
模型对象开启视锥裁剪,不在相机范围内的对象不参与渲染提交。
4. Draco 与 KTX2
项目在 public/ 中提供 Draco 和 Basis/KTX2 解码器,为后续接入压缩模型与压缩纹理做准备。
5. Resize 自适应
Three.js 和 ECharts 都需要在容器尺寸变化时主动 resize,尤其是工业大屏在 1920x1080、2K、4K 显示器上经常会遇到比例变化。
6. dispose 资源释放
场景销毁时释放 controls、composer、renderer、geometry、material 和模型实例,避免内存泄漏。
十一、可扩展方向
这个项目可以继续向真实商业系统扩展:
- 接入真实 glb/gltf 工厂模型。
- 接入后端 WebSocket、MQTT 或 OPC-UA 网关数据。
- 增加设备台账和设备详情页。
- 增加 AGV 调度任务、路径规划和任务回放。
- 增加告警确认、告警等级、告警闭环处理。
- 增加多车间、多楼层、多工厂切换。
- 增加摄像头视频流与 3D 摄像头点位联动。
- 增加权限系统和运维工作台。
十二、总结
一个好的数字孪生前端项目,核心并不只是“画得酷”,而是要把 3D 场景、实时数据、业务状态和 UI 大屏组织成一个稳定的系统。
这个项目的核心思路可以总结为:
- 用 Vue 负责页面和组件。
- 用 Three.js 负责空间表达。
- 用 ECharts 负责数据表达。
- 用 Pinia 统一实时状态。
- 用独立 Manager 管理 AGV、设备和模型。
- 用 WebSocketService 保持真实数据接入的可能性。
当这些边界划清楚后,数字孪生项目就不再只是一个视觉 Demo,而是可以继续演进的前端工程。
更多推荐


所有评论(0)