简介:这是一套基于Three.js构建的仓库三维可视化管理系统的完整前端源码,面向Web前端开发者、3D可视化学习者及智慧仓储系统实施人员,解决传统二维仓库管理系统空间感知弱、交互性差、数据呈现不直观等痛点。资源共89个文件,包含37个JavaScript核心逻辑与Three.js渲染脚本、20个Vue组件实现模块化UI与状态管理、2个GLB格式3D模型(货架与货物)、8个PNG纹理贴图及配套JSON配置与XML元数据,整体压缩包仅3.43MB,轻量易部署。已有1933人学习下载,适合中高级前端工程师深入理解WebGL场景搭建、实时数据绑定、3D交互控制及性能优化实践。源码采用标准Vue CLI工程结构,含完整路由、状态管理(Vuex)、API封装与three.js初始化流程,目录清晰、注释充分,可直接运行调试并快速扩展入库动画、路径规划、库存热力图等企业级功能。 去年接了一个仓库管理相关的项目,客户提的需求很直接:要一个能“看到整个仓库”的 3D 大屏,不是平面图,不是饼图柱状图,是那种能旋转、能点货架、能看到货物堆在哪里的 3D 场景。我当时想的是,这不就是给管理页面套个三维皮肤嘛,但真做起来才发现,基于 Three.js 的仓库可视化管理系统,难点不在“画得好看”,而在怎么把 WMS 的业务数据准确映射到三维场景里,还要保证大仓库不卡、点击拾取不飘、坐标不错位。这篇文章就把这套系统的技术拆解和踩坑经历完整写出来,适合正在做仓储可视化、智慧园区大屏,或者想拿 Three.js 做正经业务项目的人参考。
1. 我为什么要在仓库系统里上三维可视化
1.1 表格里的库位永远不够直观
传统 WMS 系统里,库存数据长什么样?一个库位编号,比如 A-03-02-01,后面跟着 SKU 编码、批次号、数量、入库时间。数据没问题,但人脑要从一串编码里想象出“这个货架第三层第二格还剩多少空间”,是非常反直觉的。
仓库管理员的日常工作里,大量决策依赖空间感知:哪个区域快满了、哪个巷道被临时摆放的货物堵住、某个热销 SKU 分布在多少个库位。表格能回答“有没有”,但很难回答“在哪里”“是不是顺路”。
我调研过的客户里,不少团队的管理方式是靠老师傅记忆,或者定期去现场拍照。这在小仓库勉强够用,一旦上了立体库、多楼层、几千个库位,光靠表格和记忆就会出问题。
1.2 可视化之后业务到底获得了什么
把仓库场景搬进浏览器之后,业务上的提升是能直接感知到的。
第一是空间利用率一目了然。整个仓库的鸟瞰视角下,库位按占用率着色,哪些区域长期空置、哪些区域爆仓,一眼就能看出来。运营排位的时候,可以直接在三维场景里找空位,而不是在表格里一个一个筛。
第二是异常定位速度变快。温湿度传感器告警、某个库位库存冻结、货架超期未动,这些信息通过颜色闪烁或告警标签挂在对应三维模型上,管理人员就不用再靠“先查库位号,再翻地图”的方式定位。
第三是可远程巡检。疫情之后,很多园区不允许无关人员频繁进出仓库,三维场景配合摄像头点位和传感器数据,能实现一定程度的远程巡检。虽然不能完全替代现场,但日常巡查频次可以明显下降。
1.3 三维可视化和“大屏好看”是两回事
我必须提醒一句:三维可视化管理系统,核心是“管理”,不是“可视化”。如果只是做几个旋转的货架模型、加一圈流光粒子,那叫宣传片,不叫管理系统。
真正能落地的系统,一定是数据驱动的。货架上的箱体模型必须对应真实库存记录,库位颜色必须反映真实的占用和冻结状态,设备动画必须挂接真实的状态上报。换句话说,Three.js 在这里的角色是渲染引擎,它把数据库里的记录“翻译”成空间里的物体,翻译错了,画面再漂亮也没有用。
这也是我在这套系统里坚持“配置驱动场景、数据驱动状态”的原因,后面会详细讲。
2. 技术选型复盘:Three.js 是主力渲染引擎的理由
2.1 先后对比过几条技术路线
动手之前,我列了几个候选方向。这个项目既要跑在浏览器里,又要能快速迭代,所以技术选型很关键。
Unity 和 Unreal 的效果当然好,光影和物理引擎都强,但它们的产物通常需要下载客户端,或者使用 WebGL 导出,打包体积大、前端集成成本高。对于一个以数据管理为核心的业务系统来说,这个代价不太划算。
Cesium 是地理空间可视化的老牌选手,针对的是地球级场景,处理倾斜摄影、地形、经纬度数据非常强。但仓库是室内场景,空间尺度小、精度要求高,用 Cesium 反而有点“杀鸡用牛刀”,而且它的事件机制和场景管理更偏 GIS 风格,跟常规前端开发习惯有差异。
纯 2D Canvas 加 CSS 3D 的方案我也考虑过,做平面示意图可以用,但用户需要旋转视角、缩放检查货架细节、逐个拾取库位,2D 方案在交互上先天受限。
最终选了 Three.js,原因是它在“浏览器实时 3D”这个区间里,生态最成熟、社区样本最多、学习曲线最平缓。
| 方案 | 画面表现 | 前端集成难度 | 室内精细交互 | 包体/运行时成本 |
|---|---|---|---|---|
| Unity WebGL | 很强 | 高,需跨技术栈协作 | 强,但开发重 | 运行时较大,加载慢 |
| Cesium | 场景大,适合GIS | 中 | 室内精度一般 | 中等偏大 |
| 原生 WebGL | 灵活 | 高,所有底层代码自己写 | 取决于实现 | 可以很小 |
| CSS 3D / 2D Canvas | 一般 | 低 | 弱 | 很小 |
| Three.js | 强 | 低,纯 JS 生态 | 强 | 可控,按需引入 |
2.2 Three.js 对前端团队最友好的地方
如果团队里全是前端工程师,没有专门的图形学专家,Three.js 几乎是首选。它把 WebGL 里繁琐的着色器编写、缓冲区管理、矩阵运算都封装好了,你只需要用 Scene、Camera、Renderer、Mesh 这些高层概念组织场景。
依赖安装也简单,npm install three 就完事了,配合 Vite 或 Webpack 都很顺畅。跟 Vue、React 这类框架结合没有强冲突,页面里的统计面板、告警列表仍然用传统 DOM 渲染,Three.js 只管 Canvas 里的三维部分。
而且 Three.js 有非常活跃的社区,遇到问题搜索一下,基本都能找到对应的 issue 或示例。这个对项目排期来说太重要了。
2.3 这套系统里真正用到的 Three.js 核心能力
这里说说项目里实际高频用到的模块,列出来给刚入坑的人一个方向。
- WebGLRenderer:渲染器,负责把场景画到 canvas 上,需要设置抗锯齿和像素比。
- Scene:场景图,所有模型、光源、辅助对象的容器。
- PerspectiveCamera:透视相机,模拟人眼近大远小的效果,仓库这种室内场景用透视相机比正交相机自然得多。
- OrbitControls:轨道控制器,鼠标拖动旋转、滚轮缩放,这套交互对仓库查看非常顺手。
- Raycaster:射线拾取,实现鼠标点击“命中”某个货架或库位的核心。
- InstancedMesh:实例化网格,用一次绘制渲染大量相同几何体的关键,后面性能优化会重点讲。
- GLTFLoader:加载外部模型,用于 AGV 小车、叉车、机械臂等精细模型。
- TWEEN / 自写插值:相机飞行、AGV 移动路径动画,我用的是 @tweenjs/tween.js,简单场景也可以自己写 lerp。
掌握了这些,仓库可视化的主体功能基本就能搭起来了。真正拉开差距的是业务映射和性能细节,下面从源码目录开始讲。
3. 源码目录规划:一套可视化仓库系统怎么拆模块
3.1 目录结构与模块职责
拿到源码包之后,第一件事别急着跑,先看目录结构。这套系统的代码组织方式,直接决定了你二次开发的时候是“改一改就能用”还是“到处找代码”。
我给出的目录设计大致是这样:
warehouse-visual/ ├── index.html ├── package.json ├── vite.config.js ├── public/ │ ├── models/ │ │ ├── agv.glb │ │ ├── fork.glb │ │ └── shelf.glb │ ├── textures/ │ │ ├── floor.jpg │ │ └── tag.png │ └── data/ │ ├── layout.json │ └── mockStock.json └── src/ ├── main.js ├── core/ │ ├── renderer.js │ ├── scene.js │ ├── camera.js │ └── controls.js ├── config/ │ ├── warehouse.js │ └── colorMap.js ├── loaders/ │ ├── modelLoader.js │ └── dataService.js ├── business/ │ ├── rackManager.js │ ├── stockManager.js │ └── deviceManager.js ├── interaction/ │ ├── picker.js │ ├── searchFly.js │ └── tooltip.js ├── utils/ │ ├── coordinate.js │ └── logger.js └── widgets/ ├── statPanel.js └── alarmList.jscore 这层管 Three.js 基础设施:渲染器、场景、相机、控制器。business 这层管仓库业务模型:货架怎么生成、库存怎么映射成三维物体、设备怎么更新位置。interaction 这层管用户操作:点击拾取、搜索飞行、悬浮提示。widgets 是传统的 DOM 浮层,用来展示统计数据和告警列表。
这个分层有个好处:业务层不直接依赖 Three.js 的渲染细节,数据层只负责拉取和推送数据。将来哪怕你想把渲染器从 Three.js 换成别的引擎,只要 business 层暴露的接口不变,上层交互可以不动。
3.2 仓库配置驱动场景,不要硬编码货架坐标
我看到过不少项目,货架位置是直接在代码里写死的:
const rack = createRack(); rack.position.set(10, 0, 20);这样写问题很大。仓库的库区布局、货架数量、行列数,在运营过程中是会调整的。一旦调整,你就要改代码、重新打包部署,这个频率和成本完全不可接受。
我这套系统的做法是:所有布局信息放配置文件里,场景根据配置动态生成。仓库的长宽、巷道的宽度、货架的行列层数、每个库位的尺寸,全部由 layout.json 驱动。
{ "warehouse": { "name": "东部一号库", "length": 120, "width": 80, "unitScale": 1 }, "areas": [ { "id": "A", "name": "A区-常温存储区", "position": [5, 0, 60], "rotation": 0, "rows": 3, "columns": 20, "levels": 4, "laneWidth": 2.5, "rack": { "width": 2.4, "depth": 1.2, "height": 1.8, "beamThickness": 0.08 } } ], "devices": [ { "id": "AGV-001", "type": "agv", "start": [8, 0, 62] } ] }配置驱动生成场景的好处是,换一个仓库只需要换 layout 文件。我在项目里做过一个演示版,客户提供了一个新库区的平面草图,我在配置文件里把区域数据改了一遍,十分钟后浏览器里就是新仓库的完整三维场景,这个体验对交付来说是加分项。
3.3 渲染层、业务层、数据层分离
具体落地的时候,我会把“拉取数据”“计算状态”“更新渲染对象”这几件事严格分开。
数据层是最容易被忽视的。很多人在数据刷新时直接改 Three.js 模型的位置和颜色,短期内没问题,时间一长代码就会变成一团乱麻。我建议给数据层定义几个明确的接口:
// dataService.js export async function fetchWarehouseLayout() { ... } export async function fetchStockSnapshot() { ... } export function subscribeStockUpdate(callback) { ... } export function subscribeDeviceStatus(callback) { ... }业务层拿到数据之后,把库存记录转换成“库位状态对象”:
{ rackId: 'A-03', level: 2, slot: 4, status: 'occupied', // empty | occupied | frozen | abnormal sku: 'SKU10086', quantity: 120, updatedAt: '...' }渲染层只做一件事:根据状态对象更新对应三维物体的外观。这样每次数据刷新,你不需要关心数据是从 WebSocket 推的还是轮询拉的,只要状态对象没有变,渲染层就不需要做任何操作。
4. 核心功能实现拆解:从货架生成到点击拾取
4.1 程序化生成一排排货架
仓库场景里数量最多的物体就是货架。如果每个货架都单独建模再加载,一个几千库位的仓库,模型面数和文件体积都会爆炸。正确做法是用代码程序化生成,并且用 InstancedMesh 来做批渲染。
我先把货架抽象成一组几何体:立柱、横梁、层板。粗略版货架模型可以用 BoxGeometry 组合:
function createRackGeometry(rackConfig) { const group = new THREE.Group(); const columns = []; const beams = []; // 立柱 for (let i = 0; i < 4; i++) { const col = new THREE.BoxGeometry(0.06, rackConfig.height, 0.06); col.translate(...); columns.push(col); } // 层板 for (let level = 0; level < levels; level++) { const beam = new THREE.BoxGeometry(rackConfig.width, 0.04, rackConfig.depth); beam.translate(...); beams.push(beam); } const merged = BufferGeometryUtils.mergeGeometries([...columns, ...beams]); return merged; }这里把立柱和横梁合并成一个 BufferGeometry,整个货架就是一次 draw call。然后把同一个几何体放进 InstancedMesh:
const rackMesh = new THREE.InstancedMesh( mergedGeometry, sharedMaterial, totalRacks );然后用矩阵设置每个货架的位置和朝向:
const matrix = new THREE.Matrix4(); rackList.forEach((rack, index) => { matrix.setPosition(rack.x, 0, rack.z); matrix.setRotationFromEuler(new THREE.Euler(0, rack.rotation, 0)); rackMesh.setMatrixAt(index, matrix); });这样整个仓库几千排货架,draw call 只有个位数。性能差距非常明显。
4.2 库位状态颜色映射与动态刷新
货架有了,接下来把库位状态用颜色表达出来。每个库位可以对应一个小的立方体或扁平盒,放在货架层板上。这些库位小盒同样用 InstancedMesh 生成,通过 instanceColor 单独设置每个实例的颜色。
颜色映射表放配置文件里,方便业务方按自己的习惯调整:
export const STOCK_STATUS_COLOR = { empty: 0x2ecc71, // 绿色,空库位 occupied: 0xffa502, // 橙色,已占用 frozen: 0xdfe4ea, // 灰色,冻结 abnormal: 0xff4757, // 红色,异常 reserved: 0x1e90ff // 蓝色,预占 };数据刷新的逻辑是:拿到 stockSnapshot,遍历每个库位,更新 instanceColor。如果只是局部更新,不需要重建整个实例化网格,用 setColorAt 刷一遍即可。
function updateSlotColors(stockList) { stockList.forEach((item) => { const instanceIndex = getSlotInstanceIndex(item.rackId, item.level, item.slot); const color = new THREE.Color(STOCK_STATUS_COLOR[item.status] || 0xffffff); rackSlotMesh.setColorAt(instanceIndex, color); }); rackSlotMesh.instanceColor.needsUpdate = true; }更新颜色之后记得把 needsUpdate 置为 true,否则 GPU 不会重新上传颜色数据。
4.3 Raycaster 点击库位的完整链路
点击拾取是三维仓库最核心的交互。用户点击画布上的某个位置,程序需要判断点中了哪个货架、具体是哪一层哪个库位。
链路分成三步:
第一步,把鼠标的屏幕坐标归一化到 NDC 坐标:
const ndc = new THREE.Vector2(); ndc.x = (event.clientX / window.innerWidth) * 2 - 1; ndc.y = -(event.clientY / window.innerHeight) * 2 + 1;第二步,用 Raycaster 从相机发射射线:
raycaster.setFromCamera(ndc, camera); const intersects = raycaster.intersectObjects(interactiveMeshList, true);这里有个细节:intersectObjects 的第二个参数 recursive 设为 true,是因为有些模型是嵌套结构,比如货架模型里子节点很多。但递归检测会带来性能开销,所以我在项目里把需要拾取的对象单独收进一个 interactiveMeshList,不让射线去检测整个 scene。
第三步,从命中结果里取第一个有效对象,并读取业务标记:
if (intersects.length > 0) { const hit = intersects[0].object; const slotInfo = hit.userData.slotInfo; if (slotInfo) { openSlotDetailPanel(slotInfo); } }常用的做法是在创建库位小盒时,把库位编号、SKU、数量这些业务信息塞进 userData,拾取命中后直接拿出来渲染到 2D 浮层面板。
4.4 搜索定位与飞行动画
库存搜索是这个系统的另一个高频操作。输入框里输入 SKU 或批次号,系统自动定位到对应的库位。
实现上也没有那么复杂,主要分三步:
第一步,在业务层把库存表反向索引,建立 sku → slotList 的映射。
第二步,算出目标库位的世界坐标:
function getWorldPositionOfSlot(rackId, level, slot) { const rack = rackMap.get(rackId); const slotOffset = getSlotOffset(rack, level, slot); return new THREE.Vector3( rack.position.x + slotOffset.x, rack.position.y + slotOffset.y, rack.position.z + slotOffset.z ); }第三步,让相机和控制器飞到这个坐标附近,同时高亮目标库位。我用 @tweenjs/tween.js 做一个平滑过渡,动画时长控制在 600 到 800 毫秒,太短会眩晕,太长影响效率。
new TWEEN.Tween(camera.position) .to(targetPosition, 700) .easing(TWEEN.Easing.Cubic.Out) .onUpdate(() => camera.lookAt(target)) .start();对 3D 产品和 UI 动效比较敏感的人,很容易忽视这一点:三维相机移动和传统 UI 动画不太一样,镜头不是简单平移,而是要同时更新 lookAt 的视线方向。否则“飞行”过去之后,画面中心不是目标物体,体验会很别扭。
5. 大仓库不卡顿:渲染性能优化的关键动作
5.1 Draw Call 是核心瓶颈
仓库场景卡顿,十有八九是 draw call 太多了。每提交一个物体给 GPU 绘制,都是一次 draw call。一个普通的仓库场景,货架几千、库位几万、托盘几百,如果每个库位都是一个独立 Mesh,帧率直接崩。
我第一次把完整场景加载出来的时候,renderer.info.render.calls 飙到两万多,帧率不到 15。后来把所有静态几何体合并、改成 InstancedMesh,draw call 降到 200 以内,帧率一下就上去了。
优化前后效果对比如下:
| 优化项 | 优化前 | 优化后 |
|---|---|---|
| draw call | 20000+ | 180 |
| 帧率 | 15 fps | 60 fps |
| 内存占用 | 模型重复加载 | 几何体共享 |
| 加载时长 | 12s | 2s |
建议每个项目启动后,在开发阶段打开 renderer.info,盯着 draw call 数量做优化。
5.2 材质与纹理的复用策略
很多新手写 Three.js 时会犯同一个错误:每个物体都创建新材质、新纹理。比如给每个库位状态小盒单独 setColor 时,如果每个小盒都 new Material,GPU 就会为每个材质生成一个渲染批次。
正确做法是:同类型的物体共用同一个材质,颜色变化优先通过实例颜色 instanceColor 来实现,而不是创建新材质实例。
纹理也一样。仓库地面的纹理、货架贴图、标签贴图,加载一次之后缓存复用。加载模型时如果模型内含多个材质,尽量在建模阶段就合并材质,减少运行时的材质切换。
5.3 渲染细节分级与阴影取舍
实时阴影是性能的一大杀手。仓库场景里几千个货架如果都开启实时阴影,即使帧率没问题,GPU 的负担也会很大。
我的做法是:大场景俯瞰模式下关闭阴影;需要近距离查看货架细节的时候,才开启指定区域的阴影。如果客户要求始终有阴影效果,可以用一张静态的软阴影贴图贴在地面上,模拟货架底部阴影,视觉上够用,性能开销几乎为零。
LOD(Level of Detail)也可以派上用场。远景的货架可以用简化版几何体,近景再换成精细几何体。不过仓库场景结构相对规整,在 InstancedMesh 优化之后,LOD 不是必须的。我建议先做实例化,再评估是否需要 LOD,不要一上来就堆复杂度。
5.4 定时刷新与静态帧渲染
仓库可视化还有一个容易被忽视的优化点:大多数时候,场景是静止的。用户不动鼠标、数据没变化的时候,画面根本不需要每帧重绘。
Three.js 默认的渲染循环是 requestAnimationFrame 持续调用 render()。这种写法在资源充足的 PC 上没问题,但在一些配置不高的工控机上会白白消耗 CPU 和 GPU。
我后来改成“脏检查 + 按需渲染”模式。定义几个触发重绘的条件:相机位置变化、数据刷新、动画播放中。只有这些情况发生时才调用 renderer.render()。实测下来,空闲状态下 GPU 占用率从 70% 降到 10% 以下,这个优化对大屏常驻场景特别有效。
6. 坐标、拾取、字体:接入真实业务时的几个经典坑
6.1 坐标单位与 CAD 图纸的扭曲问题
这个坑几乎每个做三维仓库的人都会踩。CAD 图纸的坐标单位通常是毫米,而 Three.js 世界坐标里,1 个单位默认可视作 1 米。如果你直接把 CAD 的坐标搬进场景,模型会大一千倍,相机进到模型内部啥也看不见。
转换公式很简单:
function cadToWorld(cadPoint) { return { x: cadPoint.x / 1000, y: cadPoint.z / 1000, z: cadPoint.y / 1000 }; }但更麻烦的是坐标轴的差异。CAD 平面图是 X/Y 平面,Z 表示高度;Three.js 是 X/Z 为地面平面,Y 表示高度。所以映射的时候要把 CAD 的 Y 变成 Three.js 的 Z,CAD 的 Z 变成 Three.js 的 Y。方向搞反了,仓库就会竖起来。
我遇到过一次诡异的现象:地图导入后货架位置斜了,排查了半天发现是 CAD 里有一个旋转组件的基点没归零。建议导入数据之前先写一个校验函数,把已知点的坐标打在页面上,和图纸原始坐标比对,能省很多排错时间。
6.2 z-fighting 闪烁问题
三维仓库的场景里有很多互相贴得很近的面,比如库位小盒放在层板上,如果高度差只有 0.001,相机的深度缓冲精度不够,就会产生闪烁。
常见解决办法有三个:
一是拉开最小间距,把库位小盒放在层板表面上方 0.01 到 0.02 的高度。二是给同一位置的面设置 polygonOffset:
material.polygonOffset = true; material.polygonOffsetFactor = 1; material.polygonOffsetUnits = 1;三是调整相机的 near 和 far 值。这个容易被忽略。透视相机的深度精度分布是近密远疏,near 设得太小、far 设得太大,远处的深度精度就很差。仓库场景里,near 设 0.1、far 设 1000 基本是足够的,如果仓库特别大,再适当调整。
6.3 拾取穿透与误选问题
Raycaster 射线从鼠标位置出发,会穿过前面的物体一路往后命中所有相交物体。如果你不加筛选,取第一个结果,可能命中的是货架后面的另一个库位,或者货架立柱本身。
我的处理方式是给所有可拾取物体打上 userData.type 标记:
slotMesh.userData = { type: 'slot', slotInfo: { ... } };然后过滤命中列表:
const hit = intersects.find((item) => item.object.userData.type === 'slot');另外,当货架密度很大时,射线从很斜的角度点下去,可能命中旁边的库位,而不是正下方的库位。这需要结合鼠标点击位置和相机视角,判断命中角度。实在不行就增加一个“命中优先范围”:只取射线距离在物体包围盒半径范围内的结果。
6.4 Canvas 文字贴图模糊问题
给库位标签或者货架区域编号做文字标注时,最省事的办法是用 Canvas 画一张文字纹理贴到平面模型上。但默认画出来的字,在大屏上糊成一片。
原因很简单:Canvas 的分辨率不够,文字纹理被 GPU 拉伸了。解决办法是创建 Canvas 时就把尺寸放大两倍,并且在绘制时把字号也放大两倍:
const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 64; const ctx = canvas.getContext('2d'); ctx.font = 'bold 32px sans-serif';然后生成纹理时设置好 minFilter 和 magFilter:
const texture = new THREE.CanvasTexture(canvas); texture.minFilter = THREE.LinearFilter; texture.needsUpdate = true;如果标注数量很多而且需要实时更新,我建议考虑 CSS2DRenderer,把 HTML 标签覆盖在三维场景上。这样字体清晰、样式好调,也不会引入 Canvas 文字贴图的那些坑。唯一要注意的是,CSS2DRenderer 标签会跟随相机位置变化,需要处理好层级遮挡关系。
7. 源码包跑通与二次开发建议
7.1 从零跑到看到效果
拿到“基于 Three.js 的仓库可视化管理系统源码.zip”,第一件事是在本地把项目跑起来。解压之后,确认已经装好 Node.js 16 以上版本,然后在项目根目录执行:
npm install npm run dev等 Vite 启动,浏览器打开终端提示的本地地址,就能看到默认仓库的三维场景了。
如果接口还没有真实数据,系统会默认走 mock 数据。mock 数据放在 public/data 目录下,包含仓库布局和库存快照,你可以直接改 JSON 文件,刷新页面看效果。
生产环境打包用:
npm run build产物在 dist 目录,静态部署到 Nginx 或者对象存储上都行。打包之前记得检查 .env 文件里的接口地址配置,别把后端测试地址带到生产环境。
7.2 接入真实 WMS 的接口规范建议
源码包里默认对接的接口设计,我在这里给一个参考规范,方便你让后端按这个格式返回数据。
库存快照接口建议返回如下结构:
{ "code": 0, "data": { "stock": [ { "rackId": "A-03", "level": 2, "slot": 4, "status": "occupied", "sku": "SKU10086", "quantity": 120, "updatedAt": "2025-01-12 10:30:00" } ], "devices": [ { "id": "AGV-001", "status": "running", "position": [8.5, 0, 61.2], "heading": 90 } ] } }实时更新建议用 WebSocket 推送增量数据,推送频率不用太高,每 1 到 2 秒一次足够。数据格式里尽量带上 rackId、level、slot 这样精确定位库位的字段,否则渲染层不知道该更新哪个实例。
7.3 二次开发路线:从改配置到加功能
以我的经验,拿到这套源码做二次开发,建议按这个顺序来。
第一步,改仓库布局。打开 public/data/layout.json,对照自己仓库的平面尺寸,调整区域位置、货架行列层数。这一步不需要碰任何 Three.js 代码。
第二步,替换设备模型。把 public/models 里的 agv.glb、fork.glb 替换成实际项目需要的 3D 模型,注意 GLTF/GLB 格式。如果模型导出时带了很多材质,加载前尽量压缩一下纹理。
第三步,接真实接口。修改 src/loaders/dataService.js,把 fetchStockSnapshot 和 subscribeStockUpdate 换成调真实后端接口,mock 数据的开关在 config 里有个配置项。
第四步,新增业务功能。比如货架超期预警、库位热度分析、出入库轨迹回放。这些功能在业务层扩展状态对象,在渲染层加对应的表现即可。现有分层结构下,新增功能基本不会影响原有的场景渲染。
做完这几步,一个能交付的三维仓库可视化管理系统就出来了。
我个人在实际项目里感受最深的一点是,这类系统前期搭建不难,难的永远是数据准确性和长期维护成本。建议在代码里保留好日志和状态校验的钩子,每次数据更新打印关键变更点,排查问题的时候会非常管用。如果你也在做类似项目,欢迎交换经验,特别是在大仓库性能或者异构数据接入方面,每家遇到的情况都不太一样。
本文还有配套的精品资源,点击获取