
摘要:机房工勘成果「对不上」的根因,通常是图与表各自独立保存。DCDesigner 把项目保存为一份结构化 JSON:毫米坐标 + 原点约定 + 显式依附关系 + 工程属性,平面图、DXF、CSV 清单、3D 模型、校验报告与平台回传全部由它派生。本文解释这份数据模型的结构、约束与派生关系,说明「改一处、处处同步」是怎么实现的。
一、一致性问题:不是工具不好,是数据有两份
一个典型的机房工勘交付物包含:平面图、CAD 图、设备清单、房间与设备参数表。如果它们分别由不同软件、不同时间点生成,就会出现典型的不一致:
- 图上 42 台机柜,清单里 40 台(两台是后加的);
- CAD 图里通道宽 1200,表格里写 1000(改了图没改表);
- 平面图某个门的位置对,但清单里的门数量对不上;
- 复盘时想知道「A 排第 3 台功率多少」,需要三份文件交叉查找。
DCDesigner 的做法是只保留一份数据:项目文件是一个 JSON,包含项目名、工勘与机房信息(meta)、对象数组(objects)。所有输出都是这份数据的「视图」,而不是独立的「副本」。
二、数据模型的结构
2.1 总体形状{ "name": "XX大楼3F机房", "meta": { "surveyor": "张三", "date": "2026-08-28" }, "objects": [ /* 墙体、门窗、机柜、UPS、桥架…… */ ]}- 单位:全部毫米;
- 原点:房间西南角内表面 (0,0),X 向东、Y 向北;
- meta 除勘测人/日期外,还可记录机房整体信息:结构层高 roomHeight、吊顶面高 ceilHeight、架空地板高 floorHeight、机房等级 tier、所属园区/楼宇 site、总供电容量 powerCap、制冷架构 coolArch、电力模块容量 powerModule、地面承重 floorLoad、温湿度要求 climate、备注 remark。
对象按类型区分,每类有各自的几何字段与 props。几个代表性的结构(摘自仓库文档的数据格式说明):
// 矩形对象(机柜):中心坐标 + 旋转 + 宽深高 + 电气参数{ "id": "o…", "type": "cabinet", "x": 1500, "y": 2100, "rotation": 0, "props": { "name": "A01", "w": 600, "d": 1200, "h": 2000, "u": 42, "power": 4, "rpdu": 2, "voltage": 220, "current": 32, "sockets": [ { "type": "IEC C13", "count": 16 }, { "type": "IEC C19", "count": 6 } ] } }// 线段对象(墙、桥架):两端点 + 属性{ "id": "o…", "type": "wall", "x1": 0, "y1": 0, "x2": 12000, "y2": 0, "props": { "name": "墙-1", "thickness": 240, "height": 3000, "material": "砖墙" } }{ "id": "o…", "type": "tray", "x1": 1000, "y1": 2100, "x2": 11300, "y2": 2100, "props": { "name": "桥架-1", "width": 300, "height": 2500, "kind": "弱电" } }// 依附对象(门窗/格栅):不存坐标,挂在墙上{ "id": "o…", "type": "door", "wallId": "o…", "t": 0.5, "props": { "name": "门-1", "width": 1000, "height": 2100, "leaf": "double", "swing": "left", "side": "out" } }从工程视角看,这份模型有几个刻意的选择:
| 设计选择 | 工程含义 |
|---|---|
| 坐标存毫米、原点在西南角内表面 | 与卷尺读数同口径,现场复核无需换算 |
| 门窗/格栅存 wallId + t 而非绝对坐标 | 依附关系显式化:墙动门动、删墙级联,不可能出现「孤儿门窗」 |
| 通道带与机柜不建立持久关联 | 通道宽联动按几何(朝向+边缘贴合)实时识别,旧项目零迁移 |
| 电气、制冷、消防等属性字段化 | 属性参与渲染、统计、自检、导出与平台提交,而不是备注文字 |
| 枚举值以中文为数据键(如 "kind":"弱电") | 任意界面语言互相打开兼容;界面只做显示翻译 |
当前建模对象覆盖机房平面记录的主要范围:墙体、机房门、窗、格栅(风墙送/回风口)、柱子、框架梁、机柜、列头柜、冷/热通道、桥架、精密空调、UPS、蓄电池、灭火器、地板出风口。
其中机柜、柱子、空调、UPS、蓄电池、灭火器、风口、列头柜被定义为实体对象——彼此不允许重叠,放置与拖动时自动推挤让位。墙体、桥架、门窗、通道带属于结构或标注对象,不参与推挤。这个边界同样是数据模型的一部分:它决定了碰撞检测、自检与联动分别在哪些对象之间生效。
三、数据进入系统时的清洗与迁移
手工编辑过的 JSON、旧版本保存的文件、从别处拼装的数据,都会在打开项目与草稿恢复时经过一次清洗:
- 剔除无 id/type 的残缺对象;
- 剔除丢失所属墙体的门窗/格栅(防止悬空对象);
- 坐标与尺寸类字段(x/y/x1…/rotation/t 及 props 中的宽高深、厚度、电气参数等)强制数值化;
- 非负物理量负值归零(功率/尺寸/制冷量等,温度除外);
- 旧版字段自动迁移(门的 side: left/right → out/in;旧版机柜单类型 socket → sockets 列表)。
这意味着两件事:
- 异常数据不会静默进入画布:手工改坏 JSON 不会让属性面板出现字符串或负数,也不会污染统计与自检;
- 旧项目零成本升级:字段演进而非格式推翻,历史项目可继续使用。
四、派生关系:谁在读这份数据
数据模型的真正价值体现在「谁消费它」。DCDesigner 内部各输出有一致的派生路径:
| 输出 | 数据来源 | 关键实现 |
|---|---|---|
| 2D 平面图 | objects 坐标与 props | public/js/render.js |
| 3D 机房预览 | 同一份 objects 派生三维模型 | public/js/3d/builders3d.js |
| DXF 图纸 | 同一坐标逐对象转图层图元(1:1 毫米) | public/js/dxf.js |
| CSV 工程量清单 | 按类别+规格聚合 + 逐台明细 | public/js/csv.js(buildBOQ 纯函数) |
| 合规校验报告 | runChecks(project, cfg) 纯函数 | public/js/checks.js |
| AI 建模/Agent 指令 | 读写同一份 state 与撤销栈 | public/js/agent.js、public/js/ai/* |
| 平台回传包 | buildSurveyPayload(深拷贝 + 与保存同清洗) | public/js/nvsubmit_core.js |
其中三个是纯函数:给定项目数据,输出确定,不依赖界面状态,因此可以直接在 Node 里单测(tests/csv.test.mjs、tests/checks.test.mjs、tests/nvsubmit.test.mjs)。这也是「一致性」可以被验证而不只是被宣称的原因。
反过来看变更成本:现场改一台机柜的功率,改的是数据;下一次导出,PNG/DXF/CSV/3D/报告/平台提交包全部自动反映这个新值。没有「同步」这个动作,因为没有第二份数据。
五、文件、草稿与工作现场:数据的三种存储层
一个容易混淆的地方是「数据存在哪」。DCDesigner 实际有三层存储,各自职责不同:
| 存储 | 键/位置 | 内容 | 生命周期 |
|---|---|---|---|
| 项目文件 | 本机 JSON(系统文件对话框保存/打开) | name + meta + objects | 长期,可备份/拷贝/入库 |
| 本地草稿 | localStorage idc2d_draft_v1 | 当前编辑中的项目 | 刷新/误关页面后自动恢复;保存后标记为与文件一致 |
| 界面状态 | sessionStorage idc2d_uistate_v1 | 向导开合与步骤、自检面板、属性面板、2D 缩放位置、3D 相机 | 跟随标签页会话,新开页回到默认布局 |
| 用户偏好 | localStorage(语言、属性面板宽度、默认值注册表等) | 与项目数据无关的习惯设置 | 跨会话保留,不参与撤销 |
这层区分解决两个常见痛点:
- 「刷新一下,辛苦摆的东西没了」——项目数据有草稿兜底;
- 「刷新一下,面板全开了、视图也回到原点」——界面状态单独记忆,刷新后回到刷新前的样子(tests/uistate.cdp.mjs 专门验证「连续刷新 3 次视野不漂移」)。
保存交互采用标准文件对话框:首次保存弹出「另存为」,默认文件名取机房名称(未填写时用时间戳);保存过一次后再次保存直接覆盖原文件(文件句柄存于浏览器 IndexedDB,刷新后仍可直接保存);文件选择框未关闭时重复触发会被拦截并提示,不把浏览器技术错误抛给使用者。
六、边界与注意事项
- JSON 不是数据库:没有多用户并发锁、没有版本历史、没有权限控制。它面向的是「一个人在现场/办公室记录一个机房」的场景;
- 服务端项目存取 API 仍保留但界面默认不使用:数据以 data/*.json 形式存放时是共享命名空间,同名项目会互相覆盖、无鉴权;多人协作建议以文件方式各自保存、通过平台回传汇总;
- 删除是级联的:删除墙体时其上门窗/格栅一并删除,这是数据模型的必然结果,操作前应有心理预期;
- 属性值以中文为数据键:跨语言兼容,但也意味着导出的 CSV 清单内容固定中文(与界面语言无关),这是为「清单作为工程文件」的一致性做的取舍。
附:事实来源与核验方式
- JSON 字段与示例:README.md「数据格式」节(含机柜/墙/门窗/通道/桥架/空调/UPS 等字段说明)
- 清洗与旧字段迁移:public/js/state.js(sanitize);项目对象定义见同文件 state 结构
- 派生纯函数:public/js/csv.js(buildBOQ)、public/js/checks.js(runChecks)、public/js/nvsubmit_core.js(buildSurveyPayload)
- 存储键:idc2d_draft_v1(public/js/state.js)、idc2d_uistate_v1(public/js/uistate.js)、idc2d_locale_v1 / idc2d_props_w_v1 / idc2d_lastprops_v1(public/js/i18n.js、public/js/props.js、public/js/state.js)
- 保存/打开与并发锁:public/js/main.js;端到端回归 node tests/qa-20260907-verify.cdp.mjs
- 可复现核验:











