架构原理
架构原理
本章讲清楚一件事:平台如何把「一份工程文件」变成「一块会动的大屏」。理解这些原理后,无论手写工程文件、做二次开发,还是排查问题,你都会更有把握。
视角
这里讲的是使用者与二次开发者能观察到的运作机制(工程如何组织、如何加载渲染、数据与事件如何驱动),不深入内部实现细节。
一切都是一棵「节点树」
一个工程本质上是一棵树:工程 → 页面 → 组件 → 子元素。它们是同一种「节点」,都由三样东西描述:
type:是什么(页面screen、某种组件、地图图层 / 三维元素 / 组态图元……)uid:唯一标识(引用、联动、交互目标全靠它)options:参数(样式与行为)
- 容器型组件(分组面板、布局容器等)用
widgets[]放子组件,坐标相对父容器左上角。 - 搭建型组件(2D 地图、三维场景、组态)用
pieces[]放图元 / 图层 / 三维元素 / 连线,双击进入内部编辑。 - uid 是身份:改名、挪位置都不影响引用;删除重建则 uid 变化,绑定它的交互 / 引用会失效。
type / uid / options 之外,节点还有一组通用键,按需出现:
| 键 | 作用 |
|---|---|
name | 显示名(图层面板 / 引用提示用) |
enabled | 显隐开关(图层面板的「眼睛」);没有 hidden 键,隐藏 = enabled: false |
locked | 锁定(编辑器里不可选中拖拽) |
states / stateOverrides | 多状态定义与各状态的参数差量覆盖 |
sizeModeOverrides | 平板 / 手机的差量覆盖(响应式页面) |
conditionalStyles | 条件样式(随数据规则变化) |
widgets[] / pieces[] | 子组件 / 子图元(见上) |
unitTemplate | 数据循环容器的单元模板,运行期按数据行派生实例(派生结果不持久化) |
导出 / 导入的工程文件顶层就是四块:{ project, connections, connectionData, dataRules }——分别是节点树、数据源定义、行数据、数据规则。注意层级:页面 screens[]、窗口 windows[]、前 / 背景层、项目参数 params、工程设置 settings 都在 project 里面,不是顶层键。SDK 挂载的 body 还可以多带一个顶层 projectParams,运行时与 project.params 合并(同名键以 projectParams 为准)。
编辑态与运行态同源
平台只有一份工程数据和一套渲染逻辑。编辑器画布与发布运行时渲染的是同一棵节点树、走同一套组件实现,因此**「编辑时看到的」==「发布后呈现的」**,不存在两套实现对不齐的问题。
运行态与编辑态的区别只在「行为」,不在「长相」:
| 编辑态(画布) | 预览 / 运行态 | |
|---|---|---|
| 数据 | 已保存的数据快照 + 手动「刷新数据」 | 真实取数 / 订阅 / 轮询 |
| 交互 | 不执行(点击 = 选中组件) | 执行触发器 → 动作 |
| 组件脚本 | 不执行 | 自动实例化执行 |
| 渲染 | 同一套渲染链(所见即所得) | 同一套渲染链 + 全屏适配缩放 |
播放态开关 isPlaying
上表两列行为的分水岭是一个开关:页面状态里的 isPlaying。运行 / 预览把它置为 true,交互执行、组件二开脚本、地图 / 三维 / 组态的点击行为等都以它为门控;编辑态 = 可编辑且非播放,此时点击只是选中组件。打开它的时机有三处:SDK 挂载、运行时构建工程模型、编辑器进入预览(退出预览即还原为编辑态)。
从工程文件到大屏:加载与渲染
无论是编辑器预览、独立查看端,还是 SDK 嵌入,用的都是同一套建模与渲染,区别只在「工程数据从哪来」:
| 入口 | 工程数据来源 |
|---|---|
| SDK 嵌入 | 你直接把工程文件内容作为 body 传入 |
查看端(发布链接 /view、独立运行时、导出部署包) | 按优先级逐级回落:内置数据(部署包内 ./data/project.json)→ 发布快照(后端接口)→ 本地预览快照 → 内置示例。编辑器同源 /view 不是离线包,从「发布快照」开始找 |
| 编辑器预览 | 不重新装载,直接用编辑中的工程实例:先置播放态、再挂运行舞台 |
数据到手后的装配步骤一致:
因此三种入口呈现出来的大屏一模一样——它们用的是同一套运行时。步骤 ⑤ 的「启动窗口」由工程设置 project.settings.splashWindowUid 指定,加载即打开;运行中「哪些窗口开着、谁在最上层」是运行态的瞬时状态,不会写回工程文件(详见窗口系统)。
包与渲染链
平台的前端代码按职责分层为几个包,渲染链自下而上:
编辑器与查看端只是这条链外面的薄壳:编辑器加上画布、属性面板与历史记录,查看端加上装载与全屏。编辑画布与运行舞台共用同一套视图组件渲染页面与组件——这正是「编辑态与运行态同源」在实现上的落点。做自定义组件或用 SDK 集成时,你面对的就是这几个包的公开接口。
保存 vs 发布:两份数据
大屏在后端存的是两份互不干扰的数据:
- 保存只更新草稿,不影响线上;发布才生成运行态快照。
- 观看者始终看到最近一次发布的快照,你可以放心继续编辑草稿。
- 同一份工程数据还用于:导出工程 JSON、导出部署包、二次开发工程包。
几个值得知道的细节:
- 连接定义是双轨存储:除了随工程 body 保存,数据源定义还会在你改动后自动(防抖)同步到后端一个独立集合;加载工程时优先读这个集合,为空才回落 body 内嵌的。所以数据源的持久化不依赖整工程保存。
- 导出部署包在前端完成:编辑器拉取预构建的运行时压缩包,把工程数据注入为包内
data/project.json后重新打包下载——没有后端导出接口,产物离线自包含。 - 「待处理」暂存不进运行态:移出画布暂存的组件(
pending)运行态不实例化、不渲染,导出部署包 / 二次开发工程包时会剥离;只有草稿与工程源文件保留它。
参数(options)与默认值
options 是稀疏的:只写想改的参数,其余项运行时回落到该组件的默认值。所以工程文件里一个组件往往只有寥寥几个 options。属性面板里点「重置」即删除该键、回到默认。
参数之上还有三种「按条件变化」的机制:
- 多状态:同一组件定义多个状态,用
stateOverrides差量覆盖参数,由交互或数据驱动切换(见事件交互)。 - 设备差异:响应式页面用
sizeModeOverrides针对平板 / 手机覆盖布局与样式(见画布系统)。 - 条件样式 / 动态数据项:让样式随数据规则或字段值变化(见数据系统)。
组件如何注册成屏幕上的块
节点的 type 之所以能变成画面,是因为每种组件类型注册过一个模块:模型类 + Vue 渲染组件 + 元信息三件套({ TheWidget, component, meta })。模型类的 defineOptions() 声明一棵选项分组树,运行期被压平成「选项蓝图」——属性面板的分组控件、每个选项的默认值、options 稀疏回落,全部同源于这份蓝图。内核只认这个契约,所以注册你自己的类型即可获得与内置组件同等的待遇(拖拽、调参、绑数、交互、导出),见自定义组件开发。
工程级复用
节点树上还有三种「一处定义、多处使用」的机制:组合组件把多组件沉淀为母版,实例只存引用与差量覆盖,展开的子树运行期派生、不持久化(component-instance);画面组件把一个窗口画面作为整体嵌入页面复用(window-instance);数据循环容器按数据行批量派生 unitTemplate 模板实例,类比 v-for(data-repeater)。
数据如何驱动
组件不直接存数据,而是绑定到「连接(数据源)」里的字段。数据刷新时,绑定的组件自动重算重渲——数据是响应式的,你不需要手动通知组件。
详见数据系统。
事件如何触发
交互统一是「触发器 → 动作」:某事件发生(点击 / 悬停 / 入场 / 数据规则满足 / 页面进入……)时,执行一个或多个动作(改参数 / 切页面 / 开窗 / 发消息 / 请求接口……)。详见事件交互。
AI 与 MCP
后端内置一个 MCP 服务(默认关闭),让 Cursor / Claude 等 AI 助手按需检索平台文档与组件元数据、校验工程 JSON;另有一条实时页面控制通道,可连上正在浏览器里打开的工程,让 AI 枚举与修改元素、驱动交互(编辑态支持结构编辑与撤销,运行态只做临时改动、不写回工程)。配置与工具清单见 AI MCP。
各子系统导航
平台的原理按主题拆成以下章节,建议按需深入: