TRAE+Three.js开发3D地图数据可视化大屏:写代码只花20%时间,真正成本在需求对齐

453 阅读8分钟

参赛赛道:全链路开发实战

选题方向:从 0 到 1 的全链路开发

360截图20261016105613346.jpg

##项目概述

开发一个省域数据可视化大屏,1920×1080固定分辨率。中间是Three.js渲染的3D地图:16个地市的立体柱块、城市间飞线、点击城市弹出详情卡片;左右两列数据面板,底部一条滚动消息栏。核心约束是全屏显示不允许出现滚动条,整体视觉需要形成统一风格。

项目从空目录开始,借助TRAE完成。技术栈不复杂:Three.js(GeoJSON挤出地图、自定义着色器飞线、Raycaster拾取)+ECharts5+jQuery/layui,模拟数据集中在一个对象里,字段按真实接口的结构预留,后续接入只改一处。

选这个项目做全链路实战,基于大屏是典型的"需求模糊、视觉主观、细节密集"的场景,恰好是检验人机协作效率的试金石。结果也确实如此——项目做完我复盘时间分配,发现了一个此前没预料到的比例。

##先算一笔时间账

整个项目粗略统计(按工作日折算),时间大致这样分布:

-向AI描述需求及其返工:约五成。包括位置类调整的多轮往返、把"好看点"翻译成可执行条件的时间; -问题根因排查:约两成。参数正确但不生效、看得见却查不出的bug; -验证与回归:约两成。确认每轮修改没有破坏其他部分; -编写核心功能:不足两成。3D场景搭建、着色器、交互链路,这部分反而顺畅。

image.png

也就是说,真正"写代码"的时间只占两成不到,剩下八成都在让AI理解我想要什么、以及确认它理解对了没有。下面把这个八成拆开来看,每一类都有具体案例。

##三类典型的协作失败模式

###模式一:形容词指令→各自合理的解释

KPI指标条第一次做好后,我觉得位置不对,说的是"移到3D地图区域显著位置"。TRAE移到了头部正下方——逻辑上完全成立:那里确实属于地图区域上方。但我要的是它脱离头部、悬浮在地图上空。改。第二次放到半空,又觉得太靠上。最终第三轮才定在与左右面板顶部对齐的位置。

类似地,一个视图控制按钮前后迁移了四次:页面左下角→地图右上角→右下角→最终定在右下角并微调了间距。每次AI的落点都是"一个合理解释",但合理和符合预期是两回事。

统计下来,凡是包含"合适""显著""协调"这类词的需求,平均需要2~3轮往返;而翻译成可验收条件的需求,基本一轮到位。"威海在右上角""全屏不能出现滚动条""单趟飞线10~20秒""按钮不得与任何面板重叠"——这类话AI一次就能做对。

后来我把需求翻译固定成三个要素:位置基准(相对什么对齐)、几何约束(不能碰到什么)、验收标准(怎么算对)。下达前先自查一遍,缺一项就补。

###模式二:AI的"看起来正确"陷阱

有一个bug让我印象最深。地图右下角的设置按钮某次调整后消失了。我让AI排查,它检查了DOM存在性、检查了样式计算值、还截了图,三层结论都正常。截图上那块区域看起来也确实"正常"——就是空的。

最后定位方式很笨但有效:让AI列出页面主要元素的横向坐标区间。按钮在right:500px,右侧面板左缘恰好是55+445=500。按钮被面板完整压住,一个像素不差。截图看一百遍也看不出这种一像素级的关系,但坐标一算就撞出来了。

image.png

这类问题暴露的是验证手段的错位:AI默认的验证路径是"元素在不在、样式对不对、截图像不像",而布局bug往往藏在元素之间的相对关系里,单看任何一个元素都正常。此后凡布局异常,我直接跳过前两层,先要坐标区间表做碰撞计算。这个习惯后来至少帮我抓出三个同类问题。

###模式三:单点正确,全局破坏

一次CSS整理后,整个页面版面错乱。回查原因:重写过程中AI删除了一组"看似无用"的隐藏规则——那些规则承载着旧样式的层叠关系和元素占位,删掉后连锁反应全屏开花。有意思的是,被删除的每一条规则单独看都是冗余的,AI的判断在单点层面无可指摘。

这次之后我调整了协作方式:结构性大改拆成小步,每步只验证有限的关键元素(display、position、z-index三个属性断言)。乱了你立刻知道是哪一步弄的,回滚成本从"整个重构"降到"一步"。

顺带一提,还有一类相似问题:"放大1.5倍"改了三次没生效——地图网格与相机同步缩放,屏幕上大小不变。AI一直沿着"调整地图"的思路走,三轮之后才意识到问题出在两个对象的相对关系上。"参数正确却不生效"时,先查同步变化量,而不是继续调参数,这也是从失败里换来的检查项。

##技术坑记录(相对顺利的部分)

这部分反而没什么可展开的,挑四个记录备查:

高度预算。1080−头部93−底部栏36=可用944px。最初没算账,面板堆到980px,全屏滚动条出现。固定分辨率大屏第一步永远是列高度预算表。另外Three.js脚本放head中时容器尺寸为0,地图不渲染,必须放body末尾。

飞线静止。第一版飞线视觉上完全不动,代参数一算:单趟28分钟。数值在动,远超人的感知范围。重写着色器:明暗对比拉开、亮段28减到7、速度按"单趟10~20秒"标定。经验是动效参数必须换算成用户可感知的物理量。

06.gif

高亮守卫。点击第二个城市后第一个的高亮无法取消,根因是重置函数内的if(mesh===selectedMesh)return与选中态更新顺序矛盾。借此把3D场景与页面UI解耦成事件通信,后续改UI不再触碰Three.js代码。

视觉统一。绿色卫星纹理与科技蓝UI冲突,写了纹理处理函数做像素级色相映射(绿色地形→青蓝区间,保留明暗细节),加上一套四色规范(科技蓝为体、金色核心指标、红色功能警示、青绿层级标记),整屏才真正统一。

image.png

image.png ##协作方法的沉淀

把上面的失败模式收敛成五条工作规则,后续项目直接复用:

1.需求下达前先翻译:形容词全部换算成位置基准+几何约束+验收标准,缺一项不提交; 2.布局问题跳过截图,直接算坐标:让AI输出元素坐标区间表做碰撞检查,对一像素级冲突比任何目测可靠; 3.大改拆小步+关键属性断言:每步验证display/position/z-index,出错立即定位,回滚以步为单位; 4."不生效"先查相对关系:参数正确但没效果时,优先排查是否有两个量在同步变化; 5.架构上给迭代留缝:3D与UI事件解耦、数据源单点收敛,为后续真实接口接入留好替换位。

最后一类值得单独说的收获是验证方式的升级。无头浏览器截图验证存在天然盲区(视口受限、渲染时机),这个项目里我逐步转向"计算样式断言+几何推算"为主、截图为辅的验证组合。前者确定、可重复、能发现关系型bug,后者只用来确认最终观感——这个主次关系恰好和直觉相反。

/* 动画循环 */
function animate(){
  requestAnimationFrame(animate);
  var t=clock.getElapsedTime();
  controls.autoRotate=settings.autoRotate&&t>=autoRotateBegin;
  controls.update();
  halos.forEach(function(h){var s=h.base+Math.sin(t*0.5+h.phase)*0.08;h.sprite.scale.set(s,s,1);h.sprite.material.opacity=Math.min(1,(0.3+Math.sin(t*0.5+h.phase)*0.08)*glowScale);});
  pulseRings.forEach(function(r){var ph=(t*r.userData.speed*0.3+r.userData.phase)%3;var sc=1+ph*0.9;r.scale.set(sc,sc,1);r.material.opacity=Math.min(1,Math.max(0,0.32-ph*0.1)*glowScale);});
  rotRings.forEach(function(r){r.rotation.z+=r.userData.speed*0.5;});
  var drawP=Math.min(1,(t-flyDrawStart)*0.4);
  flyLineMats.forEach(function(m){m.uniforms.uTime.value=t;m.uniforms.uDraw.value=drawP;});
  flyParticles.forEach(function(p){
    if(!p.visible){p.material.opacity=0;return;}
    var ud=p.userData,tt=(t*ud.speed+ud.phase)%1,pos=ud.curve.getPoint(tt);
    p.position.copy(pos);var env=Math.sin(tt*Math.PI);
    if(ud.isHead){p.material.opacity=env*0.95;var bs=ud.baseScale*(1+Math.sin(t*6)*0.15);p.scale.set(bs,bs,1);}
    else p.material.opacity=env*0.55;
  });
  destPulses.forEach(function(r){
    if(!r.visible){r.material.opacity=0;return;}
    var cyc=(t*r.userData.speed+r.userData.phase)%2;
    if(cyc<1.2){var sc=0.5+cyc*3.5;r.scale.set(sc,sc,1);r.material.opacity=Math.min(1,Math.max(0,0.5*(1-cyc/1.2))*glowScale);}
    else r.material.opacity=0;
  });
  if(transparentBg){renderer.render(scene,camera);}else{composer.render();}
  css2d.render(scene,camera);
}

##结语

项目仍在迭代,后续接入真实数据接口。

如果从这次全链路实战里只带走一件事,那就是:人机协作的效率瓶颈不在AI的代码能力,而在需求从人脑到指令的翻译损耗。翻译得越精确,八成的沟通消耗就越接近零;这一分钟的翻译时间,换的是一下午的往返修改。


话题:#TRAE双端融合