从 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 系统的核心。它负责创建:

  • WebGLRenderer
  • PerspectiveCamera
  • OrbitControls
  • EffectComposer
  • UnrealBloomPass
  • FXAA
  • 灯光、地面、区域、仓储、设备、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);

progress0-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 状态包括:

  • idle
  • moving
  • loading
  • charging
  • error

当状态不是 moving 时,AGV 会暂停路径推进,模拟装载、充电或故障等业务场景。

六、IoT 设备管理与交互

设备由 DeviceManager.ts 统一创建和管理。每个设备包含:

  • id
  • name
  • zone
  • status
  • temperature
  • power
  • warning
  • position

设备状态包括:

  • running
  • warning
  • error
  • offline

状态会映射到不同的颜色与发光强度:

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 支持:

  • GLTFLoader
  • DRACOLoader
  • KTX2Loader
  • 模型 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,而是可以继续演进的前端工程。

项目地址:wenxingjun/threejs-factory-demo

更多推荐