DSL 交互与联动

CRUD 谁都会做。低代码平台真正的墙,是交互:

  • 录入地址时,选了省份,城市下拉要跟着变;
  • 「折扣」字段只在订单类型是「批发」时才出现、才必填;
  • 销售订单详情页,「提交」按钮只在草稿态可见,「审核」只在待审态可见;
  • 点一次「发布」,要弹确认 → 调接口 → 弹成功提示 → 刷新菜单——一串后续动作。

手写时,这些逻辑散落在前端的 onChangeuseEffectif (status === ...) 里。改一条规则要翻好几个组件,而且测不全。AuraBoot 把整个交互层收进 DSL,用三套声明式机制覆盖:条件表达式LinkageEngine 联动规则Action 三层架构。这一页用销售订单 / CRM 线索这类真实场景,逐个讲透。

前置:DSL 引擎原理 · Command。本页所有机制/数字来自 live 源码:business-functions.tsLinkageEngine.tsActionRegistry.ts

1. 条件表达式 —— 字段与按钮的「看情况」

最轻量的交互:某个东西要不要显示、能不能编辑、是不是必填,取决于当前记录或表单的其它值。DSL 用表达式声明,而不是写事件回调。

1.1 三个上下文前缀

表达式里能读什么,取决于页面类型:

页面类型前缀示例
列表(行操作)row.row.status === 'draft'
详情record.record.pe_so_status === 'submitted'
表单form.form.order_type === 'BULK'

1.2 字段级:visibleWhen / disableWhen / 条件必填

{ "field": "discount", "visibleWhen": "form.order_type === 'BULK'" }

字段还支持运行时条件控制 visible / readonly / required:

{
  "conditional": {
    "visible": "#orderType == 'ONLINE'",
    "readonly": "#status == 'completed'",
    "required": "#amount > 10000"
  }
}

「折扣字段只在批发单出现」「金额过万才必填审批意见」——都是一行声明,不是一段 JS。

1.3 fn.* —— 72 个业务函数

表达式不止能做比较,还能调一整套 fn.* 业务函数(共 72 个,来自 business-functions.ts),让声明式表达式有接近 Excel 公式的表达力:

分类数量代表函数
逻辑7IF IFS CASE SWITCH AND OR NOT
文本16CONCAT LEFT MID LEN REPLACE CONTAINS SPLIT
数值13ROUND FLOOR CEIL ABS MOD CLAMP PERCENT
日期14NOW TODAY DATEADD DATEDIFF DATEFORMAT WEEKDAY
类型10ISBLANK ISNUMBER COALESCE DEFAULT TONUMBER
集合12COUNTIF SUMIF PLUCK UNIQUE GROUPBY SORTBY JOIN
${fn.IF(form.amount > 1000, 'VIP', 'Standard')}
${fn.ROUND(form.price * form.qty, 2)}
${fn.COALESCE(form.nickname, form.name, 'Anonymous')}
${fn.DATEDIFF(form.startDate, form.endDate, 'day')}

2. LinkageEngine —— 字段间的连锁反应

条件表达式是「字段看自己周围」;联动规则是「一个字段变了,主动去改别的字段」。这正是省/市级联、按选择动态加载选项、自动算总价这类需求的归宿。可视化配置在 Page Designer 右侧「联动」面板,落到 DSL 是 linkageRules

2.1 8 种联动 action

一条规则 = 触发器(fieldCode + change/blur/focus + 可选 condition)+ 一组 action。8 种 action 类型(来自 LinkageActionType):

action作用
show / hide显示 / 隐藏目标字段
enable / disable启用 / 禁用目标字段
setRequired动态设必填
setValue给目标字段赋值(支持表达式)
setOptions动态加载选项(dict / api / parent 数据源)
validate触发目标字段的校验规则

2.2 三个真实场景

{
  "linkageRules": [
    {
      "id": "rule-show-subcategory",
      "trigger": { "fieldCode": "category", "event": "change" },
      "condition": "form.category === 'other'",
      "actions": [{ "type": "show", "targets": ["subcategory"] }],
      "enabled": true
    },
    {
      "id": "rule-cascade-city",
      "trigger": { "fieldCode": "province", "event": "change" },
      "actions": [{
        "type": "setOptions",
        "target": "city",
        "dataSource": { "type": "api", "url": "/api/cities?province=${form.province}" }
      }],
      "enabled": true
    },
    {
      "id": "rule-compute-total",
      "trigger": { "fieldCode": "qty", "event": "change" },
      "actions": [{ "type": "setValue", "target": "total", "value": "form.qty * form.unit_price" }],
      "enabled": true
    }
  ]
}

分类选「其它」才显示子类;选省份动态拉城市;改数量自动算总价——全是配置。

2.3 多级级联(v2)

当一个 setValue 改了的字段自己也挂了联动规则,引擎会自动继续触发,形成级联链:

province(change) → setValue city="default"
  → city(change) → show district, setRequired zipcode
    → district(change) → setValue address="…"
  • 最大深度:maxDepth(默认 5),防无限循环;到顶会 console 告警。
  • 每级新上下文:每层级联拿到反映最新表单值的全新 ExpressionContext
  • 关闭级联:设 maxDepth: 1 退回单级行为。

联动产生的 FieldMeta 覆盖优先于静态的 visible/readonly/required 表达式。

3. Action 三层架构 —— 从一次点击到一串编排

按钮被点之后发生什么,由 Action 系统统一处理。它是三层互补结构(useActionHandlerActionRegistryFlowRunner),不是冗余:

职责触发
useActionHandler入口,normalizeAction 兼容旧格式,按 type 分发按钮点击
ActionRegistry约 30 个原子操作(导航 / 删除 / 提示 / API 请求 / 数据源刷新…)builtin 或 flow 步骤
SchemaRuntime + FlowRunner多步编排(含条件分支)flow

3.1 一个按钮,一种 action(5 形态)

ActionDeftype 区分行为,一个按钮只挂一种:

type行为
command执行后端命令(走命令管道)
navigate页面导航(可携带 command,用于新建/编辑)
builtin内置 UI 操作(search/reset/refresh/export/new/edit/view/delete/back/noop)
flow(inline steps)1–3 步简单流程,一次性
flow(handler 引用)复杂流程(5+ 步 / 条件分支)或多按钮复用,定义在 schema handlers
// 执行命令 + 危险确认
{ "code": "delete", "label": "delete",
  "action": { "type": "command", "command": "crm:delete_lead" },
  "confirm": "delete.confirm", "danger": true }

// 导航到表单 + 携带命令
{ "code": "edit", "label": "edit",
  "action": { "type": "navigate", "to": "crm_lead_form", "command": "crm:update_lead" } }

3.2 多步 flow —— 点一次,跑一串

{ "code": "publish", "label": "publish",
  "action": { "type": "flow", "steps": [
    { "action": "dialog.confirm", "args": { "message": "Publish?" } },
    { "action": "api.request", "endpoint": "/api/dashboards/{pid}/publish", "method": "POST" },
    { "action": "toast.success", "args": { "message": "Published" } },
    { "action": "dataSource.reload", "args": { "target": "list" } }
  ] } }

每一步都是 ActionRegistry 里的一个原子 action。复用度高的流程抽成 handlers 里的命名 handler,按钮只写 { "type": "flow", "handler": "publishDashboard" }——本质是命名函数 vs 匿名函数,底层都走 FlowRunner.run(steps, context)

3.3 原子 action 速览(ActionRegistry)

分类action
导航navigate new edit view router.push router.back
数据操作delete search reset refresh export
数据源reloadDataSource dataSource.fetch dataSource.reload
状态setState state.set
UI 交互toast.show/success/error dialog.confirm dialog.form ui.openContainer/closeContainer notify
表单form.validate form.reset
其它api.request event.emit noop

4. 综合实战 —— 状态感知工具栏

把上面三套机制拼起来:销售订单详情页的工具栏,按钮按记录状态显隐,点击走命令管道(带权限 + 审计 + 状态机)。visibleWhen 决定可见性,action.command 决定行为:

{
  "blockType": "toolbar",
  "buttons": [
    { "code": "edit", "label": "edit", "icon": "Edit",
      "action": { "type": "navigate", "to": "pe_sales_order_form", "command": "pe:update_sales_order" },
      "visibleWhen": "record.pe_so_status === 'draft'" },
    { "code": "submit", "icon": "Send",
      "action": { "type": "command", "command": "pe:submit_sales_order" },
      "visibleWhen": "record.pe_so_status === 'draft'",
      "label": { "zh-CN": "提交", "en": "Submit" } },
    { "code": "approve", "icon": "CheckCircle",
      "action": { "type": "command", "command": "pe:approve_sales_order" },
      "visibleWhen": "record.pe_so_status === 'pending'",
      "label": { "zh-CN": "审核", "en": "Approve" } },
    { "code": "cancel", "icon": "XCircle", "danger": true,
      "action": { "type": "command", "command": "pe:cancel_sales_order" },
      "visibleWhen": "['draft','pending','approved'].includes(record.pe_so_status)",
      "label": { "zh-CN": "取消", "en": "Cancel" } }
  ]
}

草稿态看到「编辑 / 提交」,待审态看到「审核」,全程没写一行前端事件代码。按钮接的是命令码,所以 UI、自动化、AI agent 走的是同一条被授权、被审计的路径。

典型错误

  • 用前端事件兜底联动:能用 linkageRules / visibleWhen 表达的,不要退回 custom block 写 onChange
  • 按钮直接调接口:写操作一律 action.command 走命令管道,别用 flow 里裸 api.request 绕过权限/审计。
  • 级联死循环:setValue 链路成环时靠 maxDepth 兜底,但应从规则设计上避免互相赋值。
  • 混淆 disableWhenenableWhen:enableWhen 已废弃,统一用 disableWhen(取反逻辑)。

下一步