低代码属性面板-Setter体系与高级配置

这篇文章,我想讲清楚一件事:如何把低代码平台的属性面板,一步步设计成"类型感知 Setter + 高级配置面板。

从架构决策、实现链路、设计态/预览态行为分离,以及一个具有代表性的 Bug 排查这几方面展开。


一、问题:属性面板能做哪些事

属性面板的能力,总而言之,就是编辑 Schema 上的字段

这里面包括两类字段:

第一类:schema.props ------ 组件属性

每个组件都有自己的 props,比如 Text 有 textfontSizecolor,Button 有 texttype。这些字段决定了组件"长什么样"。面板需要为不同类型的 prop 提供合适的编辑控件(文本框、数字滑块、颜色选择器、下拉选择等)。

scss 复制代码
schema.props 示例(Text 组件):

{ text: '标题', fontSize: 24, color: '#111827' }
   ↑ 文本         ↑ 数字          ↑ 颜色
   StringSetter   NumberSetter    ColorSetter

第二类:Schema 顶层字段 ------ 运行时行为控制

Schema 上还有一些顶层字段,不属于 props,而是控制节点的运行时行为:

arduino 复制代码
schema.condition    → 条件渲染(是否显示)
schema.loop         → 循环数据源(绑定数组,展开为多个实例)
schema.loopArgs     → 循环变量名(如 item / index)
schema.events       → 事件绑定(如 onClick → loadList)

所以我的目标很明确:把这两类字段都纳入属性面板------props 通过类型感知的 Setter 体系来编辑,而 condition、events、loop 等顶层字段则通过专门的高级配置面板来配置。


二、技术方案设计

在动手前,我研究了阿里低代码引擎的设置器架构。它的核心分三层:

markdown 复制代码
configure(描述层)
  → SettingField(代理层)
    → Setter(视图层)
职责 阿里实现
configure 声明每个 prop 对应什么 Setter、有什么约束 MaterialMeta.configure.props
SettingField 代理读写,管理联动/校验/autorun SettingTopEntry → SettingField
Setter 纯 UI 控件,只关心渲染和回调 StringSetter、NumberSetter...

我的决策:对齐 configure 描述层,跳过 SettingField 代理层。

V1 版本的目标是降低复杂度,先把核心框架和核心能力跑通,同时为后续扩展预留空间------跳过中间层不影响后续演进

当前架构是两层直连:

bash 复制代码
configure(描述)→ Setter(视图)→ command(写入 Schema)

后续需要联动、校验、autorun 等高级能力时,再插入 SettingField 代理层:

bash 复制代码
configure(描述)→ SettingField(代理)→ Setter(视图)
                     ↓
                  command(写入 Schema)

configure 的接口不变,Setter 的接口也不变,中间层只是新增的一层代理。这种"先薄后厚"的演进策略,保证了当前能跑通、后续能扩展。


三、基础属性 ------ Setter 体系

核心思路:每个物料在元数据中声明 configure,描述自己有哪些属性、每个属性用什么 Setter 编辑。面板读取 configure,渲染对应的控件,用户操作后通过 command 写回 Schema。

以 Text 组件为例,走一遍完整链路:

1. 物料声明 configure

json 复制代码
{
  "title": "文本",
  "componentName": "Text",
  "configure": [
    {
      "name": "text",
      "title": "文本内容",
      "setter": "StringSetter"
    },
    {
      "name": "fontSize",
      "title": "字号",
      "setter": "NumberSetter",
      "props": {
        "min": 12,
        "max": 100
      }
    },
    {
      "name": "color",
      "title": "颜色",
      "setter": "ColorSetter"
    }
  ]
}

2. 面板按 configure 渲染 Setter

用户选中一个 Text 节点后,PropertyPanel 读取 Text 的 configure,遍历每一项,通过 setter 字段的字符串名称(如 'StringSetter')从注册表中查找对应的 React 组件,然后渲染:

ini 复制代码
┌─────────────────────────┐
│ 文本内容  [___标题____]  │  ← StringSetter(文本框)
│ 字号      [  24  ↕]     │  ← NumberSetter(数字输入,min=12 max=100)
│ 颜色      [■] #111827   │  ← ColorSetter(取色器 + 色值预览)
└─────────────────────────┘

如图:

3. 用户修改 → Setter → command → Schema 更新

比如用户把字号从 30 改成 40:

php 复制代码
用户滑动 NumberSetter 到 40
  → Setter 调用 onChange(40)
    → PropertyPanel 执行 engine.command.execute('updateProps', {
        nodeId: 'page-title',
        props: { fontSize: 40 }
      })
      → DocumentModel 合并写入 schema.props.fontSize = 40
        → notify() → 画布重新渲染

整条链路复用了现有的 command 体系,Setter 本身只负责渲染和回调,不关心数据怎么写入schema------onChange 是它与外部的唯一连接点。


四、高级配置面板 ------ 编辑 Schema 顶层字段

4.1 接入 updateSchemaAdvanced 专门处理 Schema 顶层字段

基础属性 Tab 编辑的是 schema.props,用 updateProps command 就够了。但高级 Tab 要编辑的是 Schema 顶层字段:

arduino 复制代码
schema.condition   → 条件渲染
schema.loop        → 循环数据源
schema.loopArgs    → 循环变量名
schema.events      → 事件绑定

updatePropsmerge 语义 (合并写入 schema.props),但这些字段需要 直赋值语义 (直接写入 schemafield)。

所以新增了 updateSchemaAdvanced command,基本逻辑如下:

arduino 复制代码
execute(nodeId, field, value):
  prevValue = schema[field]          // 保存旧值(用于撤销)
  if value 为空 → delete schema[field]  // 清空时删除字段,保持 Schema 干净
  else         → schema[field] = value  // 直接赋值
  notify()                           // 通知画布重新渲染
  return prevValue              // 返回旧值用于 undo,command 体系的撤销/重做

4.2 三个高级配置项

配置项 控件形态 候选数据来源
条件渲染 复选框:勾选 = 隐藏该节点 ---
循环数据源 文本框 + datalist 候选提示,绑定后显示循环变量名(item / index)编辑 自动扫描 Schema 中值为数组的 state / data 字段
事件绑定 事件名下拉选择(onClick、onChange...) → 方法名文本框,支持多条 Schema.methods 中已声明的方法

图示:

五、设计态 vs 预览态的边界

之前在设计 Runtime 的 DataSource 和 Events 时就讨论过设计态与预览态的边界问题------设计态的核心目标是设计体验

属性面板引入 condition 和 loop 编辑后,这个问题再次浮出水面:

在设计态,如果 condition='false',节点会从画布上消失

在设计态,如果绑定了 loop,列表为空时节点同样会消失

设计态的核心需求是"能看到、能选中、能编辑",节点不能因为 condition 或 loop 的配置而消失或变形。

因此在 DesignerRuntime 的 resolveSchemaNode 中直接跳过 condition 和 loop 的语义解析。

而 PreviewRuntime 则完整执行 condition 判断和 loop 展开逻辑。

同一个 Renderer,注入不同的 Runtime 就能得到不同的行为------这正是 Runtime 层在设计之初就具备的解耦能力。

如下:

vbnet 复制代码
              DesignerRuntime
                    ↓
同一个 Renderer ← resolveSchemaNode → 始终 single,跳过 condition/loop
                    ↓
              PreviewRuntime
                    ↓
同一个 Renderer ← resolveSchemaNode → 可能 hidden / 可能展开为 list

这个设计保证了:

  • 设计态永远能看到所有节点
  • 预览态完整执行运行时语义
  • Renderer 代码零侵入

六、典型的一个问题分享:循环渲染在预览态不生效

6.1 现象

在属性面板配置了 loop: 'data.list' 后,预览页面中循环容器没有展开。但Network显示接口已经请求成功,数据源已经写入了 data.list

6.2 分析

Schema 结构如下:

scss 复制代码
Page (data.list 在这里)
├── Container (外层容器)
  ├── Container (loop: 'data.list')
    ├── Text ({{index}})      ← loop 展开后可用的 index 变量
    ├── Text ({{item.name}})  ← loop 展开后可用的 item 变量

循环渲染的执行链路:resolveSchemaNode 解析到内层 Container 时,发现它声明了 loop: 'data.list',于是交给表达式引擎求值,期望拿到一个数组,再将该节点展开为多个实例。

问题出在表达式引擎对 data.list 的求值过程。引擎先解析标识符 data,直接返回当前节点作用域data 对象。但内层 Container 是子节点,它自身的作用域里 data 是空的 {},真正的数据存在根节点 Page 的作用域上。于是 {}.list 得到 undefined,循环无从展开。

根因:表达式引擎在取命名空间(state/data/props 等)时,没有沿作用域链向上查找。

引擎的 RuntimeContext 本身设计了父链机制------get('data.list') 会在当前 scope 找不到时自动往父级查找。但表达式引擎绕过了这个方法,直接读取当前作用域的属性,导致父链断裂。

6.3 修复

修复思路很直接:让表达式引擎在遇到命名空间路径(如 data.liststate.count)时,统一走 RuntimeContext 的 get() 方法,而不是直接访问当前作用域的属性。这样求值过程就能自动沿父链查找,子节点也能正确访问到根节点上的数据。

这个问题的代表性在于:作用域链是引擎的基础设施,它的任何断裂都会在上层功能(loop、condition、表达式绑定)中以难以预料的方式暴露出来。


七、总结

属性面板其实串联了引擎的多个核心能力:物料描述、command 体系、Runtime 分层、表达式引擎与作用域链。回顾整个实现过程,有几条设计原则贯穿始终:

1. Schema 驱动一切

属性面板所做的事情,本质上就是"编辑 Schema"。无论是基础属性通过 Setter 写入 schema.props,还是高级配置写入 schema.condition/schema.loop/schema.events,最终都归结为对 Schema 的读写。

2. 描述与渲染分离

物料通过 configure 声明"我有什么属性、该用什么控件编辑",面板只负责按描述渲染。新增物料或新增 Setter,都只需要扩展配置和组件,不需要改动引擎核心代码。

3. Runtime 层承担行为差异

设计态和预览态对 condition、loop 的处理截然不同,但这个差异完全由 Runtime 层消化,Renderer 对此无感知。这保证了渲染层就是纯粹渲染。

4. 表达式引擎是连接 Schema 与运行时的桥梁

loop 绑定的 data.list、props 中的 {{item.name}},这些写在 Schema 里的字符串,都需要表达式引擎在运行时求值。它依赖作用域链正确传递上下文,任何断裂都会在上层功能中以难以预料的方式暴露出来------这也是本文 Bug 排查环节的核心教训。

属性面板是用户与 Schema 之间的交互界面,也是引擎各层能力的集中体现。 当前 V1 版本覆盖了核心链路,后续在此基础上引入属性之间联动、跨字段校验、动态枚举、表达式编辑器、以及更丰富的 Setter 类型等,这些都是增量扩展,不需要推翻现有设计。


附上在线体验地址:lowcodeapp.blinkblink.top/

相关推荐
葡萄城技术团队3 小时前
活字格:打通 ERP 与车间执行数据,实现计划与生产协同
低代码
SL_staff3 小时前
《如何用规则引擎替代if-else?JVS-Rules可视化编排比硬编码强在哪里?》
java·低代码·架构
AI行业学习5 小时前
CC‑Switch v3.16.1 免费下载(Windows+macOS+Linux)、使用方法【2026.6.11】
linux·开发语言·windows·python·macos·前端框架·html
ZKNOW甄知科技6 小时前
燕千云AI-ITR系列:三线分层机制的标准化解决方案
大数据·运维·人工智能·低代码·自然语言处理·自动化·敏捷流程
摘笑6 小时前
我删了800行动画代码,因为View Transitions API只用了两行就搞定了多页面切换
前端框架
API开发平台7 小时前
开源 API 开发平台 5.2.0 发布
低代码·开源
UXbot8 小时前
移动端UI设计工具选型指南:iOS与Android设计标准支持对比
android·前端·低代码·ios·交互·团队开发·ui设计
放下华子我只抽RuiKe58 小时前
FastAPI 全栈后端(五):后台任务与消息队列
前端·javascript·react.js·ai·前端框架·fastapi·ai编程