c3846da8ae
- 新增设计器静态依赖分析方案,阐述基于 AST 的代码片段解析与依赖图谱构建 - 新增实体生命周期引擎实现规范,定义引擎驱动的事件分发与交互触发策略 - 新增悬浮组件架构设计规范,明确底层数据统一与上层视图分离的核心原则 - 新增应用运行模式架构规范,严格区分设计态、预览态与运行态的边界 - 新增设计器选中与分组交互规范,定义选中态、激活态及下钻交互的行为矩阵
79 lines
6.5 KiB
Markdown
79 lines
6.5 KiB
Markdown
# DatAlive 悬浮组件 (Overlay Components) 架构设计规范
|
||
|
||
在低代码平台(如 DatAlive)中,悬浮组件(如 `Modal` 弹窗、`Drawer` 抽屉、全局 `Toast` 等)由于其“脱离标准文档流、需要交互唤起、默认隐藏”的特性,一直是设计器架构、状态管理和交互设计的难点。
|
||
|
||
本规范基于 DatAlive 的核心设计哲学,详细界定了悬浮组件在底层数据蓝图(Schema)、物料元数据(Component Meta)、运行时状态(Runtime Store)以及设计器用户界面(UI)之间的流转与协作机制。
|
||
|
||
---
|
||
|
||
## 1. 核心原则:底层数据统一,上层视图分离
|
||
|
||
在架构设计初期,开发者极易陷入“为特殊组件定制特殊结构”的陷阱。DatAlive 坚决摒弃在 Schema 中为悬浮组件设立“特权结构”(例如在 Page 节点下新增 `modals: Component[]` 数组)的做法。
|
||
|
||
### 1.1 Schema 实例层 (Instance Schema) 的绝对纯净
|
||
在最终持久化存入数据库的 `design-mode` Schema 中,**万物皆为平等的 `Component` 节点**。
|
||
|
||
* **统一的树模型**:悬浮组件仅仅是 `Page.components` 树中的一个普通节点。它拥有与普通组件完全一致的接口:`type`, `props`, `layout`, `style`, `children`。
|
||
* **嵌套无特权**:当用户向 `Modal` 内部拖入一个 `Table` 时,底层发生的数据结构变更仅仅是:向该 `Modal` 节点的 `children` 数组中 `push` 了一个 `Table` 节点。这份包含了弹窗及其内部所有子组件的完整 JSON 树,将原封不动地被导出或保存。
|
||
* **架构优势**:
|
||
* **极简的渲染引擎**:引擎只需要一套极其简洁的递归遍历逻辑,即可完成包含弹窗在内的整棵组件树的解析,无需编写针对 `modals` 的额外逻辑。
|
||
* **交互动作泛化**:因为弹窗只是普通节点,所有的泛化 Action(如 `{"target": {"id": "modal_1"}, "method": "open"}`)可以无缝作用于它,无需修改目标选择器逻辑。
|
||
|
||
### 1.2 物料元数据层 (Component Meta) 的二元解耦
|
||
悬浮组件之所以在 UI 上表现特殊,是因为它在**平台物料库的注册表(Component Registry)**中带有明确的静态元数据标识。我们必须严格区分“组件的静态特征”与“实例的运行时状态”。
|
||
|
||
```typescript
|
||
// 伪代码:在设计器源码(或 SDK)中注册 Modal 物料
|
||
Registry.register({
|
||
type: 'Modal',
|
||
name: '对话框',
|
||
category: '反馈',
|
||
icon: 'icon-modal',
|
||
isOverlay: true, // 🚨 核心标识:告知设计器这是一个悬浮容器
|
||
isContainer: true, // 允许向其内部拖入子组件
|
||
setters: [ ... ] // 属性配置器
|
||
});
|
||
```
|
||
|
||
* **彻底的解耦机制**:像 `isOverlay` 这种描述“物料类”固有属性的标识,**绝对不能**被抽离或序列化到用户的页面 Schema JSON 中。Schema 只保存实例特有的变动数据(如 `props.title`)。设计器在运行时通过 `type` 查字典(Type Mapping),动态获知该实例属于 `isOverlay: true` 的类别。
|
||
|
||
---
|
||
|
||
## 2. 设计器 UI 层面的协作机制 (MVC 映射)
|
||
|
||
基于上述底层架构,设计器在处理悬浮组件时,展现出极其优雅的 MVC(Model-View-Controller)协作,确保“Schema”、“图层面板”和“主画布”三方的绝对同步。
|
||
|
||
### 2.1 唯一真相来源 (Single Source of Truth)
|
||
Zustand/Redux 内存中的 `Application Schema`(特别是当前 Page 的被扁平化或深层嵌套的 `components` 树)是唯一的 Model。视图层(图层面板、画布)本身不保存任何层级或显隐状态。
|
||
|
||
### 2.2 图层树面板的“动态分叉 (Forking)”
|
||
为了避免主画布被多个隐藏的弹窗遮挡,设计器的左侧【图层面板】必须与普通组件隔离展示悬浮组件。
|
||
|
||
* **纯粹的受控渲染**:图层面板订阅当前页面的 `components` 树。
|
||
* **动态过滤与分组**:遍历每个节点,通过 `type` 查验 Meta 字典。若 `Registry[type].isOverlay === true`,则将该节点分发至独立的“悬浮层(Overlays)”虚拟文件夹中进行渲染;否则留在“主图层”中。
|
||
* **所见即所得**:无论组件在图层树的哪个文件夹,它们在底层的 Schema 数组中依然是平级的。修改图层树的任何属性(如重命名、拖拽排序),都会 Dispatch Action 修改底层 Schema,触发全链路重绘。
|
||
|
||
### 2.3 画布渲染引擎的 Portal 挂载
|
||
在运行模式(`runtime`)或预览模式(`preview`)下,渲染引擎解析到 `type: 'Modal'` 时,其对应的 React 组件内部实现应利用 `ReactDOM.createPortal` 技术。
|
||
这使得 `Modal` 及其 `children` 被“传送”挂载到 `document.body` 节点下,从而脱离当前容器的 `overflow` 限制和局部的层叠上下文(Stacking Context),实现真正的全屏悬浮覆盖。
|
||
|
||
---
|
||
|
||
## 3. 设计态的交互困境与双路径解法
|
||
|
||
在设计态下,悬浮组件默认是隐藏的(通常由 `props.visible` 控制)。为了让实施人员能够向其内部拖入子组件(如表单、表格),平台提供两条并行的交互路径:
|
||
|
||
### 路径 A:画布强制唤醒 (Canvas Override) —— 面向直观操作
|
||
这是为主流用户提供的所见即所得体验:
|
||
1. **唤醒**:用户在左侧图层树的“悬浮层”目录中,点击该 `Modal` 节点旁的“眼睛(显示)”图标(修改 Schema 中的 `layer.hidden = false`)。
|
||
2. **劫持**:设计器拦截其原有的业务可见性逻辑(如忽略绑定的 `visible` 变量),强制在主画布最顶层渲染该弹窗,并赋予极高的 z-index 以防止遮挡。
|
||
3. **拖拽**:用户从物料区拖拽 `Table` 组件,直接在弹窗的实体区域(DropZone)内释放(Drop)。
|
||
4. **写入**:拖拽引擎捕获释放事件,定位到目标容器,在底层修改 Schema 树,将 `Table` 插入 `Modal` 的 `children` 数组。
|
||
|
||
### 路径 B:图层树直接盲投 (Tree Drop) —— 面向精准控制
|
||
这是为应对复杂层级遮挡或高级用户提供的极客体验:
|
||
1. **绕过画布**:用户从物料区拖拽组件,**不经过中间的主画布**。
|
||
2. **树形释放**:直接将组件拖拽至左侧【图层面板】中目标 `Modal` 所在的文件树节点上悬停并释放。
|
||
3. **写入**:图层面板组件直接捕获 Drop 事件,并派发同样的 Action,将组件精准插入底层 Schema 对应节点的 `children` 数组。
|
||
|
||
这两条路径完美互补,且底层收敛于唯一的树状结构修改代码。这种“上层交互多态,底层逻辑唯一”的设计,确保了平台在处理复杂嵌套容器时的绝对健壮性。 |