Skip to content

ast-schema Schema 编辑

页面结构编辑插件,以 JSON 代码形式直接编辑页面的 DSL(Domain Specific Language) Schema,适合需要精确控制页面结构的场景。

插件功能

  • 基于 Monaco Editor 的 JSON 编辑器,支持语法高亮、自动补全和错误提示
  • 实时同步画布变更,订阅 schemaChange 事件自动更新编辑器内容
  • 保存时自动校验 JSON 格式,解析异常时阻止保存并提示错误
  • 禁止修改 component 字段(修改 component 等同于修改物料类型)

页面 DSL 结构

页面 DSL 是一棵以 Page 为根节点的组件树,用 JSON 描述页面的完整结构,包括组件层级、状态、数据源、云函数等。

顶层结构(RootNode)

json
{
  "id": "page_001",
  "props": {},
  "state": {},
  "cloudFunction": {},
  "dataSource": {},
  "children": []
}
字段类型说明
idstring页面唯一标识
propsobject页面级属性
stateobject页面响应式状态,可在组件中通过 this.state.xxx 访问
cloudFunctionobject云函数定义,可在组件中通过 this.cloudFuns.xxx() 调用
dataSourceobject数据源配置,可在组件中通过 this.dataSource.xxx 访问
childrenarray子组件节点数组

组件节点结构(NodeSchema)

每个组件节点描述一个 UI 元素,递归嵌套形成组件树:

json
{
  "id": "abc12345",
  "component": "Button",
  "props": { "type": "primary" },
  "children": "点击按钮",
  "invisible": false,
  "slot": "default",
  "directives": [],
  "events": []
}
字段类型说明
idstring节点唯一标识(8 位随机字符)
componentstring组件名称,必须在可用组件列表中
propsobject组件属性,键值对形式,值支持静态值或 JSExpression
childrenstring | JSExpression | NodeSchema[]子内容:纯文本、表达式或子组件数组
invisibleboolean | JSExpression是否隐藏,类似 Vue 的 v-if / v-show
slotstring插槽名称,不指定时默认为 "default"
directivesNodeDirective[]指令列表,用于 vFor 循环渲染等
eventsNodeEvent[]事件绑定列表,通过工作流引擎处理

JSExpression 表达式

DSL 中的动态值统一使用 JSExpression 格式表示,类似于 Vue 模板中的 插值:

json
{ "type": "JSExpression", "value": "this.state.count" }
字段类型说明
typestring固定为 "JSExpression"
valuestringJavaScript 表达式字符串
idstring可选,表达式唯一标识

数据访问路径

访问方式说明示例
this.state.xxx页面状态this.state.userName
this.context.itemvFor 循环当前项this.context.item.name
this.context.indexvFor 循环当前索引this.context.index
this.dataSource.xxx数据源this.dataSource.userList
this.cloudFuns.xxx()云函数调用this.cloudFuns.fetchData()

使用场景

JSExpression 可用于以下位置:

  • props 属性值{ "type": "JSExpression", "value": "this.state.title" }
  • children 文本{ "type": "JSExpression", "value": "'¥' + this.state.price" }
  • invisible 条件{ "type": "JSExpression", "value": "!this.state.isLoggedIn" }
  • directives 值{ "type": "JSExpression", "value": "this.state.products" }
  • events handler{ "type": "JSExpression", "value": "this.__workflow__.execute('node-xxx', {...})" }

指令系统(Directives)

vFor 循环渲染

vFor 指令用于列表循环渲染,等价于 Vue 的 v-for

json
{
  "id": "product_item",
  "component": "View",
  "props": {
    "key": { "type": "JSExpression", "value": "this.context.item.id" }
  },
  "directives": [
    {
      "id": "vfor_001",
      "name": "vFor",
      "value": { "type": "JSExpression", "value": "this.state.products" },
      "iterator": { "item": "item", "index": "index" }
    }
  ],
  "children": [
    {
      "id": "product_name",
      "component": "Text",
      "children": { "type": "JSExpression", "value": "this.context.item.name" }
    }
  ]
}
字段类型说明
idstring指令唯一标识
namestring指令名称,循环渲染为 "vFor"
valueJSExpression循环数据源,指向 this.state 中的数组
iteratorobject迭代变量命名:item 为当前项变量名,index 为索引变量名

要点

  • 循环子节点中通过 this.context.item 访问当前项,this.context.index 访问当前索引
  • 循环项必须设置 key 属性,推荐使用 this.context.item.id
  • iterator 的变量名可自定义,如 { "item": "product", "index": "prodIndex" }

嵌套 vFor

多层循环时,每层使用不同的 iterator 变量名,内层可访问所有外层 context:

json
{
  "directives": [
    {
      "id": "vfor_category",
      "name": "vFor",
      "value": { "type": "JSExpression", "value": "this.state.categories" },
      "iterator": { "item": "category", "index": "catIndex" }
    }
  ],
  "children": [
    {
      "id": "product_item",
      "component": "View",
      "directives": [
        {
          "id": "vfor_product",
          "name": "vFor",
          "value": { "type": "JSExpression", "value": "this.context.category.products" },
          "iterator": { "item": "product", "index": "prodIndex" }
        }
      ],
      "children": [
        {
          "component": "Text",
          "children": { "type": "JSExpression", "value": "this.context.product.name" }
        }
      ]
    }
  ]
}

事件绑定(Events)

事件绑定通过工作流引擎(Workflow)处理,使用 events 字段配置:

json
{
  "id": "section_header",
  "component": "View",
  "events": [
    {
      "id": "evt_click_001",
      "name": "onClick",
      "handler": {
        "type": "JSExpression",
        "value": "this.__workflow__.execute('node-xxx', { eventType: 'click', eventData: event })"
      }
    }
  ]
}
字段类型说明
idstring事件唯一标识
namestring事件名称,如 "onClick""onInput"
handlerJSExpression事件处理器,调用工作流引擎的 execute 方法

handler 参数说明

  • 第一个参数:工作流节点 ID,对应 NodeComponent 节点的 id
  • 第二个参数:事件数据对象,包含 eventType(事件类型)和 eventData(原生事件对象)

常用事件名clickdblclickinputchangesubmitscroll

插槽(Slots)

子节点通过 slot 字段指定渲染到父组件的哪个插槽位置:

json
{
  "id": "input_with_icon",
  "component": "UniEasyinput",
  "props": { "placeholder": "请输入搜索内容" },
  "children": [
    {
      "id": "prefix_icon",
      "component": "Icon",
      "slot": "prefixIcon",
      "props": { "name": "search" }
    },
    {
      "id": "suffix_text",
      "component": "Text",
      "slot": "suffixIcon",
      "children": "搜索"
    }
  ]
}
  • 不指定 slot 时默认渲染到 "default" 插槽
  • 可用的插槽名称由物料 Meta 中的 configure.slots 定义

完整示例

以下是一个包含状态、vFor 循环、事件绑定的完整页面 DSL:

json
{
  "id": "travel_home",
  "state": {
    "banners": [
      { "image": "https://example.com/banner1.jpg" },
      { "image": "https://example.com/banner2.jpg" }
    ],
    "hotList": [
      { "id": 1, "title": "故宫博物院", "price": 60 },
      { "id": 2, "title": "长城一日游", "price": 120 }
    ]
  },
  "children": [
    {
      "id": "banner_swiper",
      "component": "Swiper",
      "props": {
        "autoplay": { "type": "JSExpression", "value": "true" },
        "circular": { "type": "JSExpression", "value": "true" }
      },
      "children": [
        {
          "id": "swiper_item",
          "component": "SwiperItem",
          "props": {
            "key": { "type": "JSExpression", "value": "this.context.index" }
          },
          "directives": [
            {
              "id": "vfor_banner",
              "name": "vFor",
              "value": { "type": "JSExpression", "value": "this.state.banners" },
              "iterator": { "item": "item", "index": "index" }
            }
          ],
          "children": [
            {
              "id": "banner_image",
              "component": "Image",
              "props": {
                "src": { "type": "JSExpression", "value": "this.context.item.image" },
                "mode": "aspectFill"
              }
            }
          ]
        }
      ]
    },
    {
      "id": "section_header",
      "component": "View",
      "events": [
        {
          "id": "evt_click_001",
          "name": "onClick",
          "handler": {
            "type": "JSExpression",
            "value": "this.__workflow__.execute('node-1i6g7fbo', { eventType: 'click', eventData: event })"
          }
        }
      ],
      "children": [
        {
          "id": "section_title",
          "component": "Text",
          "children": "热门推荐"
        }
      ]
    }
  ]
}