一文介绍amis百度低代码框架

大家好,我是LEIZ。

最近公司的后台管理系统接入了amis低代码平台,就想写下对于这个低代码框架的使用心得,以及如何通过这个框架完成日常需求中的功能。

背景是项目经理希望后续在前端开发pd数比较紧张的情况下,后端也能够介入前端页面配置,也就有了低代码框架选型及后续的推进事项。(后端同学:好好好这么玩是吧)

低代码LowCode

低代码LowCode,wikipedia上是这么介绍它的,低代码开发平台LCDP(Low-code development platform),它本身是一个软件,为开发者提供了创建应用程序的开发环境。和传统代码的IDE(集成开发环境 integrated development environment)不同的是,低代码开发平台提供了更强可维护性且易用的可视化IDE。

低代码是一种开发模式,开发者通过可视化的图形界面、预构建模块以及自动化工具,来操作图形拖拽、参数配置等就可以生成页面完成开发工作,而非传统的手动编写代码的形式。

主要用途是希望能够加快应用程序的开发速度,降低开发的学习成本和难度、让非技术人员在熟悉开发方式后也能上手参与到应用程序的开发中。

amis介绍

amis 是一个低代码前端框架,主要功能就是使用JSON配置来生成页面。

官网的介绍案例中,实现一个具备以下功能的页面:

  1. 可以对数据做筛选
  2. 有按钮可以刷新数据
  3. 编辑单行数据
  4. 批量修改和删除
  5. 按某列排序
  6. 可以隐藏某些列
  7. 可以调整列顺序
  8. 自动生成顶部查询区域
  9. 可调整列宽度
  10. 开启整页内容拖拽排序
  11. 表格有分页(页数还能同步到地址栏,不过这个例子中关了)
  12. 有数据汇总
  13. 支持导出 Excel
  14. 等其他功能(其他可以查看文档)

这类我们在日常开发需求时经常写的CURD表格,在amis上只用配置157行JSON配置就能生成,代码量上比直接编写代码的模式下少了非常多。

平常我们很多基于业务功能封装的表格,亦或是基于hooks的写法编写类似功能表格,157行应该是没法完成的(狗头.jpg)。

为什么选择amis?

TL在低代码框架选型宣讲会上提到了选择amis最重要的原因,框架稳定且经历了很长时间的实战考验(百度内部最老的amis页面是6年多以前创建的)

优点

  1. 框架稳定经历过长时间的实战考验(百度6年多的时间了创建了5万多页面,最复杂的页面超过了1万行的JSON配置);
  2. 提供丰富的组件,且目前还在持续更新;
  3. 支持低代码模式+自定义组件的混合模式;
  4. 不受前端技术更新的影响(Angular/Vue/React等);
  5. 不需要会前端技术栈,掌握文档中JSON生成页面的规则即可;

在实际开发过程中,也能比较直观的感受到低代码对于开发效率的提升,尤其是在搭建页面的效率上,配合上amis的可视化编辑器,一些CURD功能的表格及表单弹窗之类的页面,基本能够很快的搭建完成。

缺点

  1. 大量定制化UI的情况不适宜使用,也是目前低代码存在的通病,尤其是toC的需求。
  2. 复杂/特殊的交互场景不适合使用。

之前介入的一个公司核心后台系统项目,涉及订单模块,页面交互复杂不说,UI组件上还需要加比较多的业务逻辑限制,以及组件间的联动,如果是那个项目接入低代码,后续的开发可能也会比较麻烦。

重要概念

官网文档里的概念、类型、高级内容有时间的话一定要全部看完,具体内容大家可以仔细查阅文档,下面针对通过amis开发必须具备的知识点做一个简介。

JSON渲染逻辑

amis 的渲染过程是将 JSON 转成对应的 React 组件。先通过 JSON 的 type 找到对应的 Component,然后把其他属性作为 props 传递过去完成渲染。

拿一个表单页面来说,如果用 React 组件开发一般长这样。

jsx 复制代码
<Page title="页面标题" subTitle="副标题">
  <Form title="用户登录">
    <InputText name="username" label="用户名" />
  </Form>
</Page>

把以上配置方式换成amis JSON,则是:

json 复制代码
{
  "type": "page",
  "title": "页面标题",
  "subTitle": "副标题",
  "body": {
    "type": "form",
    "title": "用户登录",
    "body": [
      {
        "type": "input-text",
        "name": "username",
        "label": "用户名"
      }
    ]
  }

amis中两种使用方法,JS SDK(可以使用在任何页面中,适合对前端或React不了解的开发者,它不依赖npm及webpack,可以像Vue/jQuery那样外链代码就能使用)和React。

数据域与数据链

json 复制代码
// amis配置内容
{
  "type": "page",
  "initApi": "/amis/api/mock2/page/initData",
  "body": "date is ${date}"
}

// amis/api/mock2/page/initData接口响应数据
{
  "status": 0,
  "msg": "",
  "data": {
    "title": "Test Page Component",
    "date": "2017-10-13"
  }
}

渲染后页面的展示结果如下:

上面的json配置主要做了这些事:

  1. 首先给 Page 组件配置initApi属性,该属性会在组件初始化时,请求所该属性所配置的接口。
  2. 接口请求成功后,Page 会把接口返回响应数据data存到当前的数据域中。
  3. Page 在渲染 body 所配置的文本时,会解析文本内容,当解析到模板变量${text}时,amis 会把尝试在当前组件的数据域中获取text变量值,并替换掉${text},最后渲染解析后的文本。

数据域

上面在说到初始化接口渲染动态文本时,提到了作用域,这也是amis中最重要的概念之一

json 复制代码
{
  "type": "page",
  "body": "hello ${text}"
}

在没有配置数据域之前,这里肯定是会只展示hello

配置上作用域后就会展示为hello world

json 复制代码
{
  "data": {
    "text": "World!"
  },
  "type": "page",
  "body": "Hello ${text}"
}

data属性是数据域的一种形式,当我们没有显示配置数据域时,就等同于:

json 复制代码
{
  "data": {},
  "type": "page",
  "body": "Hello ${text}"
}

数据链

数据链也是amis中的重要概念。它的特性是当前组件在遇到获取变量的场景时(例如模板渲染、展示表单数据、渲染列表等等)。

  1. 首先会先尝试在当前组件的数据域中寻找变量,当成功找到变量时,通过数据映射完成渲染,停止寻找过程;
  2. 当在当前数据域中没有找到变量时,则向上寻找,在父组件的数据域中,重复步骤12
  3. 一直寻找,直到顶级节点,也就是page节点,寻找过程结束。
  4. 但是如果 url 中有参数,还会继续向上查找这层,所以很多时候配置中可以直接 ${id} 取地址栏参数。

和JS中的作用域链类似

接下来可以看下数据链的使用案例:

json 复制代码
{
  "type": "page",
  "data": {
    "name": "zhangsan",
    "age": 20
  },
  "body": [
    {
      "type": "tpl",
      "tpl": "my name is ${name}"
    },
    {
      "type": "service",
      "data": {
        "name": "lisi"
      },
      "body": {
        "type": "tpl",
        "tpl": "my name is ${name}, I'm ${age} years old"
      }
    }
  ]
}
markdown 复制代码
- page
    - tpl
    - service
        - tpl

数据链工作过程:

  1. page组件下的tpl组件,在渲染my name is ${name}时,首先会在page的数据域中,尝试寻找name变量,在当前数据域中,name变量为zhangsan,因此寻找变量结束,通过数据映射渲染,输出:my name is zhangsan,渲染结束。
  2. service组件开始渲染,service组件内子组件tpl,它配置的模板字符串是:my name is ${name}, I'm ${age} years old,它会在service的数据域中,尝试寻找nameage变量。
  3. 由代码可以看出,service数据域中name变量为lisi,因此停止该变量的寻找,接下来寻找age变量。
  4. 很明显在service数据域中寻找age变量会失败,因此向上查找,尝试在page数据域中寻找age变量,找到为20,寻找变量结束,通过数据映射渲染,输出:my name is lisi, I'm 20 years old,渲染结束。

使用数据域的注意点

官方文档中特地提到,具备作用域的组件只有以下几种:

markdown 复制代码
- App
- Page
- Cards
- Chart
- CRUD
- CRUD2
- Dialog
- Drawer
- List
- Page
- PaginationWrapper
- Service
- Wizard
- Combo
- InputArray
- Table
- Table2

有个特殊情况是 CRUD 中 filter,实际上是个 form,所以 CRUD 中有两层数据域,第一层是 CRUD 本身,同时查询条件表单中也有一层数据域。 比如以下案例中,获取数据域里的数据是无效的:

json 复制代码
{
  "type": "page",
  "data": {
    "name": "zhangsan"
  },
  "body": [
    {
      "type": "tpl",
      "tpl": "my name is ${name}"
    },
    {
      "type": "container",
      "data": {
        "name": "lisi"
      },
      "body": {
        "type": "tpl",
        "tpl": "my name is ${name}"
      }
    }
  ]
}

正确做法是需要通过service包裹一层:

json 复制代码
{
  "type": "page",
  "data": {
    "name": "zhangsan"
  },
  "body": [
    {
      "type": "tpl",
      "tpl": "my name is ${name}"
    },
    {
      "type": "service",
      "data": {
        "name": "lisi"
      },
      "body": {
        "type": "container",
        "body": {
          "type": "tpl",
          "tpl": "my name is ${name}"
        }
      }
    }
  ]
}

这里个人也存在一个小疑问,官方文档上提到page是具备数据链,但是案例上在page上设置了数据域,按官方的说法应该是生效,但实际上需要通过service组件包裹一层才能够生效,有了解的朋友可以评论区里聊一下哦

数据映射

数据映射支持用户通过${xxx}$xxx获取当前数据链中某个变量的值,实现灵活的数据配置功能,主要用于模板字符串、 自定义 api 请求数据体格式等场景。

模板字符串的使用

json 复制代码
{
  "type": "page",
  "data": {
    "name": "rick",
    "friend": {
      "name": "xiaoming",
      "age": "18"
    }
  },
  "body": [
    {
      "type": "tpl",
      "tpl": "my name is $name / ${name}, my friend ${friend.name}"
    }
  ]
}

默认 amis 在解析模板字符串时,遇到$字符会尝试去解析该变量并替换该模板变量,如果你想输出纯文本"${xxx}""$xxx",那么需要在$前加转义字符"\",即"\${xxx}"

自定义api请求的数据格式

在日常开发中表单提交是很常见的开发需求,通过以下简单的JSON配置就能生成页面。

json 复制代码
{
  "type": "page",
  "body": {
    "type": "form",
    "api": {
      "method": "post",
      "url": "/amis/api/mock2/form/saveForm",
      // 配置`api`的`data`属性,使用数据映射实现对数据格式的自定义
      "data": {
        "userName": "${name}",
        "userEmail": "${email}"
      }
    },
    "body": [
      {
        "type": "input-text",
        "name": "name",
        "label": "姓名:"
      },
      {
        "name": "email",
        "type": "input-text",
        "label": "邮箱:"
      }
    ]
  }
}

Q:为什么这里不需要显示定义数据域,也能够回显数据? A: 上文提到了如果不显示定义数据域,其实data属性还是会隐式存在的(忘记的同学可以查看上面数据域那一小节),amis中有个小细节,form组件存在时,会将data数据和当前form组件的数据域进行合并。

此时可以理解数据域里有两个字段

json 复制代码
{
  "data": {
     "name": "",
     "email": ""
  }
}

过滤器

过滤器这个功能非常实用,相当于是将JS内置的一些方法通过过滤器的模式进行使用。举个我在项目使用的一个例子。

json 复制代码
{
  "type": "page",
  "data": {
    "info": {
      "name": "rick",
      "company": "baidu"
    }
  },
  "body": {
    "type": "tpl",
    "tpl": "my info is ${info|json}"
  }
}

// |json 过滤器的作用于等同于 JSON.stringify 方法
js 复制代码
// 展示结果
my info is { "name": "rick", "company": "baidu" }

这里在实际应用过程发现,可以直接新建对象使用过滤器。

json 复制代码
{
  "type": "page",
  "data": {
    "a": "1"
  },
  "body": {
    "type": "tpl",
    "tpl": "my info is ${{a: a, b: 2}|json}"
  }
}
js 复制代码
// 结果
my info is { "a": "1", "b": 2 }

amis中提供的过滤器还是非常丰富的,过滤器是对数据映射的一种增强,它的作用是对获取数据做一些处理,基本用法都大体相同:

json 复制代码
${xxx [ |filter1 |filter2...] }

这里截图了官网提供的部分过滤器,更新过滤器可查看文档

表达式

amis中的表达式有两种语法:

markdown 复制代码
1. 纯js表达式的写法:data.xxx === 1
2. 通过${}包裹的表达式:${xxx === 1}

在通过amis构建页面的过程,也很多场景会需要我们用到表达式:

  1. 变量取值my name is ${xxx}
  2. api地址的参数取值http://mydomain.com/api/xxx?id=${id}
  3. api发送及数据映射
json 复制代码
{
  "type": "crud",
  "api": {
    method: "post"
    url: "http://mydomain.com/api/xxx",
    data: {
      skip: "${(page - 1) * perPage}",
      take: "${perPage}"
    }
  },
  ...
}
  1. 组件的显隐控制
json 复制代码
{
  "name": "xxxText",
  "type": "input-text",
  "visibleOn": "${ xxxFeature.on }"
}
  1. 表单默认值
json 复制代码
{
  "name": "xxxText",
  "type": "input-text",
  "value": "${ TODAY() }"
}
  1. 等等...

语法

JS的大多语法在amis中基本都可以正常书写,基础数据类型、数组、对象、三元/二元表达式等。

json 复制代码
{
  "type": "page",
  "data": {
    "a": 1,
    "key": "y",
    "obj": {
      "x": 2,
      "y": 3
    },
    "arr": [
      1,
      2,
      3
    ]
  },
  "body": [
    "a is ${a} <br />",
    "a + 1 is ${a + 1} <br />",
    "obj.x is ${obj.x} <br />",
    "obj['x'] is ${obj['x']} <br />",
    "obj[key] is ${obj[key]} <br />",
    "arr[0] is ${arr[0]} <br />",
    "arr[a] is ${arr[a]} <br />",
    "arr[a + 1] is ${arr[a + 1]} <br />"
  ]
}
js 复制代码
// 结果
a is 1  
a + 1 is 2  
obj.x is 2  
obj['x'] is 2  
obj[key] is 3  
arr[0] is 1  
arr[a] is 2  
arr[a + 1] is 3

需要注意的是箭头函数只支持单表达式,不支持多条语句,举个例子现在有个() => abc的箭头函数,ARRAYMAP(arr, () => abc),这里的ARRAYMAP等同于Array.prototype.map,以及类似的想法会在下文继续介绍,这里强调的是箭头函数的应用。

特殊字符变量名的获取比较特别,需要转义,正常情况下${xxx.yyy}指的是需要取数据域中xxx变量的yyy属性,如果有个变量名就叫xxx.yyy的话,就需要通过${xxx\.yyy}来获取。(amis版本需要>=1.6.1)

公式/函数

除了简单的表达式,还集成了如上文提到的ARRAYMAP等公式/函数。下面举一些示例:

json 复制代码
{
  "type": "page",
  "body": [
    {
      "type": "form",
      "wrapWithPanel": false,
      "data": {
        "val": 3.5
      },
      "body": [
        {
          "type": "static",
          "label": "IF(true, 2, 3)",
          "tpl": "${IF(true, 2, 3)}"
        },
        {
          "type": "static",
          "label": "MAX(1, -1, 2, 3, 5, -9)",
          "tpl": "${MAX(1, -1, 2, 3, 5, -9)}"
        },
        {
          "type": "static",
          "label": "ROUND(3.5)",
          "tpl": "${ROUND(3.5)}"
        },
        {
          "type": "static",
          "label": "ROUND(val)",
          "tpl": "${ROUND(val)}"
        },
        {
          "type": "static",
          "label": "AVG(4, 6, 10, 10, 10)",
          "tpl": "${AVG(4, 6, 10, 10, 10)}"
        },
        {
          "type": "static",
          "label": "UPPERMONEY(7682.01)",
          "tpl": "${UPPERMONEY(7682.01)}"
        },
        {
          "type": "static",
          "label": "TIMESTAMP(DATE(2021, 11, 21, 0, 0, 0), 'x')",
          "tpl": "${TIMESTAMP(DATE(2021, 11, 21, 0, 0, 0), 'x')}"
        },
        {
          "type": "static",
          "label": "DATETOSTR(NOW(), 'YYYY-MM-DD')",
          "tpl": "${DATETOSTR(NOW(), 'YYYY-MM-DD')}"
        }
      ]
    }
  ]
}
js 复制代码
IF(true, 2, 3) // 2

MAX(1, -1, 2, 3, 5, -9) // 5

ROUND(3.5) // 3.5

ROUND(val) // 3.5

AVG(4, 6, 10, 10, 10) // 8

UPPERMONEY(7682.01) // 柒仟陆佰捌拾贰元壹分

TIMESTAMP(DATE(2021, 11, 21, 0, 0, 0), 'x') // 1640016000000

DATETOSTR(NOW(), 'YYYY-MM-DD') // 2024-01-22

amis提供了很多常用公式组合,有逻辑函数、数学函数、文本函数、日期函数、数组函数等,详情可移步文档

事件动作

事件动作用于解决复杂的 UI 交互场景,支持渲染器事件监听和响应设计,无需关心组件层级关系。例如:

  • http 请求:发送 http 请求
  • 弹窗提示:执行弹窗、抽屉打开和 toast 提示
  • 页面跳转:页面链接跳转
  • 浏览器相关:回退、前进、后退、刷新
  • 刷新组件:联动刷新表单数据,即数据重新加载
  • 组件状态:控制指定组件的显示/隐藏、启用/禁用、展示态/编辑态
  • 组件特性动作:执行指定组件的专有动作,例如执行表单的提交动作
  • 组件数据:更新指定组件的数据域
  • 广播:多个组件监听同一个事件做出不同响应
  • JS 脚本:通过编写 JS 代码片段实现所需逻辑,同时支持 JS 代码内执行动作
  • 逻辑编排:条件、循环、排他、并行

onEvent

通过onEvent属性实现渲染器事件与响应动作的绑定。onEvent内配置事件和动作的映射关系,actions是事件对应的响应动作的集合。

json 复制代码
{
  "type": "button",
  "label": "尝试点击、鼠标移入/移出",
  "level": "primary",
  "onEvent": {
    "click": { // 监听点击事件
      "actions": [ // 执行的动作列表
        {
          "actionType": "toast", // 执行toast提示动作
          "args": { // 动作参数
            "msgType": "info",
            "msg": "派发点击事件"
          }
        }
      ]
    },
    "mouseenter": {{ // 监听鼠标移入事件
      "actions": [
        {
          "actionType": "toast",
          "args": {
            "msgType": "info",
            "msg": "派发鼠标移入事件"
          }
        }
      ]
    },
    "mouseleave": {{ // 监听鼠标移出事件
      "actions": [
        {
          "actionType": "toast",
          "args": {
            "msgType": "info",
            "msg": "派发鼠标移出事件"
          }
        }
      ]
    }
  }
}

上下文

执行动作的时候,可以通过${event.data}获取事件对象的数据、通过${__rendererData}获取组件当前的数据域。 下面来根据示例了解它的使用吧:

json 复制代码
{
  "type": "page",
  "data": {
    "p1": "p1"
  },
  "body": {
    "type": "form",
    "debug": true,
    "api": {
      "url": "/amis/api/mock2/form/saveForm",
      "method": "post",
      "data": {
        "&": "$$",
        "job": "coder"
      }
    },
    "data": {
      "job": "hr"
    },
    "body": [
      {
        "type": "alert",
        "body": "监听姓名值变化,执行动作时读取输入的内容;监听年龄值变化,执行动作时读取input-text组件当前数据域(表单数据)",
        "level": "info",
        "className": "mb-1"
      },
      {
        "type": "input-text",
        "name": "name",
        "label": "姓名:",
        "onEvent": {
          "change": {
            "actions": [
              {
                "actionType": "toast",
                "args": {
                  "msg": "${name}"
                }
              }
            ]
          }
        }
      },
      {
        "type": "input-text",
        "name": "age",
        "label": "年龄:",
        "onEvent": {
          "change": {
            "actions": [
              {
                "actionType": "toast",
                "args": {
                  "msg": "${__rendererData|json}"
                }
              }
            ]
          }
        }
      }
    ],
    "onEvent": {
      "submitSucc": {
        "actions": [
          {
            "actionType": "toast",
            "args": {
              "msg": "${event.data|json}"
            }
          },
          {
            "actionType": "toast",
            "args": {
              "msg": "${__rendererData|json}"
            }
          }
        ]
      }
    }
  }
}

当我们修改年龄内容时,会打开弹窗。

这里的交互逻辑主要是根据以下配置出现的

json 复制代码
  {
    "type": "input-text",
    "name": "age",
    "label": "年龄:",
    "onEvent": {
      // 绑定change事件
      "change": {
        "actions": [
          // 设置响应动作
          {
            "actionType": "toast",
            "args": {
              // 这个操作其实还是挺常用的,相当于是直接格式化输入的内容
              "msg": "${__rendererData|json}"
            }
          }
        ]
      }
    }
  }

然后我们输入姓名后提交表单,可以看到提示了三个弹窗信息:

json 复制代码
"onEvent": {
  // 绑定提交成功的事件
  "submitSucc": {
    "actions": [
      // 设置弹窗toast的响应动作
      {
        "actionType": "toast",
        "args": {
          // 弹窗里的内容是接口响应的结果,并通过过滤器进行格式化处理
          "msg": "${event.data|json}"
        }
      },
      {
        "actionType": "toast",
        "args": {
          // 将当前数据域中的内容JSON化后,放置在toast中弹出
          "msg": "${__rendererData|json}"
        }
      }
    ]
  }
}

运行日志

这个功能点很实用,能够在浏览器控制台非常清晰看到事件执行顺序及相关日志内容,在我们日常调试需要自定义设置交互逻辑的时候,这个功能发挥作用就非常关键。

>>事件动作是amis开发过程中非常重要的知识点,这里我介绍了事件动作的基础使用,在开发过程中事件动作的使用十分频繁,一定要仔细阅读这块内容!

API

日常开发过程中,和后端同学联调一定是我们经常做的开发任务之一,大家通常通过代码开发时,大多情况下是使用ajaxaxiosfetch等方式发起网络请求,在amis里,我们不需要使用这些方式,同样也是使用配置项完成请求调用的任务。

格式:[<method>:]<url>

  • method:支持get、post、put、delete(默认为get)
  • url:接口地址
json 复制代码
{
  "api": "get:/amis/api/initData", // get 请求
  "api": "post:/amis/api/initData", // post 请求
  "api": "put:/amis/api/initData", // put 请求
  "api": "delete:/amis/api/initData" // delete 请求
}

接口返回格式(重要)

所有配置在 amis 组件中的接口,都要符合下面的返回格式。

json 复制代码
{
  "status": 0,
  "msg": "",
  "data": {
    ...其他字段
  }
}
  • status : 返回 0,表示当前接口正确返回,否则按错误请求处理;
  • msg : 返回接口处理信息,主要用于表单提交或请求失败时的 toast 显示;
  • data : 必须返回一个具有 key-value 结构的对象。 status msgdata 字段为接口返回的必要字段。

amis为了方便更多场景使用,还兼容了以下这些错误返回格式:

  1. errorCode 作为 status、errorMessage 作为 msg
  2. errno 作为 status、errmsg/errstr 作为 msg
  3. error 作为 status、errmsg 作为 msg
  4. error.code 作为 status、error.message 作为 msg
  5. message 作为 msg

前端可以通过响应拦截调整响应数据结构,比如axios中的AxiosInstance.interceptors.response.use

所以正确的格式需要是以下结构:

json 复制代码
{
  "status": 0,
  "msg": "",
  "data": {
    // 正确
    "text": "World!"
  }
  // "data": "some string" 是错误格式,需要使用包装
}

配置请求数据

上文也有提到,当我们数据绑定的字段值和发起请求时需要传给后端的字段值不同或者需要特殊处理时,我们可以通过data属性就行处理。

json 复制代码
{
  "type": "page",
  "body": {
    "type": "form",
    "api": {
      "method": "post",
      "url": "/amis/api/mock2/form/saveForm",
      // 设置data属性,配置自定义接口请求数据体
      "data": {
        "myName": "${name}",
        "myEmail": "${email}"
      }
    },
    "body": [
      {
        "type": "input-text",
        "name": "name",
        "label": "姓名:"
      },
      {
        "name": "email",
        "type": "input-email",
        "label": "邮箱:"
      }
    ]
  }
}

注意:当method是get时,data中的值会默认添加到请求路径中,默认如果值是undefined时也会作为空字符串发送。这个由于历史原因无法修改了,如果希望满足undefined的效果,需要进行一下的配置。

json 复制代码
"data": {
  "myName": "${name|default:undefined}",
  "myEmail": "${email|default:undefined}"
}

配置返回数据

如果接口返回的数据结构不符合预期,可以通过配置 responseData来修改.

同样支持数据映射,可用来映射的数据为接口的实际数据(接口返回的 data 部分),额外加 api 变量。其中 api.query 为接口发送的 query 参数,api.body 为接口发送的内容体原始数据。

注意:当数据域里的 key 为 & 且值为 $$ 时, 表示将所有原始数据打平设置到 data 中.

json 复制代码
{
  "type": "page",
  "initApi": {
    "method": "get",
    "url": "/amis/api/xxx",
    "responseData": {
      "&": "$$",
      "first": "${items|first}"
    }
  }
}

假如接口实际返回为:

json 复制代码
{
  "status": 0,
  "msg": "",
  "data": {
    "items": [{"a": 1}, {"a": 2}]
  }
}

经过映射,给组件的数据为:

json 复制代码
{
  "items": [{"a": 1}, {"a": 2}], // 打平到了data中
  "first": {"a": 1} // 获取items中的第一项
}

还有一种常见场景是,通过接口返回数据去组装下拉框select的数据源,这个情况相信大家开发过程中也经常遇到:

json 复制代码
// 接口响应结果
{
  "data": [
    {
      "myLabel": "lab",
      "myValue": 1
    }
  ]
}

select需要的数据格式是[{"label": "lab", "value": 1}],那么应该如何进行映射呢?

json 复制代码
{
  "type": "select",
  "source": {
    "method": "get",
    "url": "http://xxx",
    "responseData": {
      "options": "${items|pick:label~myLabel,value~myValue}"
    }
  }
}

API中还能进行请求数据格式的调整、接口缓存、拦截请求、配置请求条件 等等,所以>>>API这块内容请务必仔细阅读,上述大致介绍了API的常用知识点能够完成普遍的交互场景,当涉及特殊交互逻辑时,相信大家会需要这块知识点。

踩坑记录及一些使用心得

简单介绍下笔者开发项目的技术栈vue3+ts,此次项目中接入amis框架作为编写代码+低代码的混合开发模式。

目前amis也是将可视化编辑器作为独立的npm发布出来,所以基于此开源包做二次开发,发布到公司内部统一使用。

graph TD 可视化编辑器npm包 --> 二次开发 --> 作为内部amis编辑器后台使用 --> 记录新增组件的id --> 低代码组件id汇总页面

笔者这次开发的功能,就是点击按钮打开弹窗进行数据的新增的需求,以及还有些简单的 交互(就是这个预期是想简单了,没想到低代码配置起来还是有点麻烦的。。)

原本拿代码开发小半天就完事了,拿低代码开发从开始到完成功能愣是搞了两三天,中间也是碰到了各种奇奇怪怪的问题,大家开发前一定要给自己留出阅读文档的时间,虽然在这之中碰到了一些目前无法解决或者框架自身的bug等问题,但是绝大多数还是自己对文档的不熟悉导致的。

一开始心里或多或少存在抵触心理,代码写的好好的,拿低代码开发多麻烦还要学习新内容,未来换了家公司又换了个框架有什么意义之类的洗脑之类的想法。但是仔细想想,低代码能够在过去几年成为一个热点以及成为商用产品一定有它的理由,无论出于什么目的,大家能够在工作中实战应用新技术的机会还是需要把握的,和工作相关的内容经过长久的沉淀一定会有收获!

遗留的问题

  1. 确认弹窗的按钮颜色无法修改
json 复制代码
{
  "type": "page",
  "body": {
    "label": "ajax请求",
    "type": "button",
    "actionType": "ajax",
    "confirmText": "确认要发出这个请求?",
    "confirmTitle": "炸弹",
    "api": "/amis/api/mock2/form/saveForm"
  }
}

大概率得通过修改源码的方式处理,因为是通过confirmText属性直接配置出来的,目前没找到合适的方式处理。 2. 上传组件按钮无法禁用

本来项目中已经有现有的交互,只支持上传一个文件,且上传按钮在上传以后禁用,发现在这个上传组件中无法实现。

  1. 上传列表tooltip动态计算位置异常

在可视化编辑器平台上显示是正常的

放到项目中以后,动态计算出的left是0,top也是0

  1. radio组件的label插入图标

产品需求很简单,就是往radio单选框的文本框旁边放一个图标鼠标移入后展示文本提示效果,然后我看了下文档中关于label属性的描述。

(我。。。哎只能和产品说目前低代码应该是不支持这样,当然我也尝试了很多方法,文档上提供的属性不能满足prd的需求。)

大多碰到的还是些样式交互上的问题,后续在选用使用低代码的场景也会更加注意评估,尽可能还是在交互逻辑场景简单的情况下进行使用。(本来评估这次需求也挺简单的没想到还是有点问题,其他同学如果有碰到类似的问题,欢迎在评论区给出建议!)

使用心得

上面一直在说碰到的问题,也吐槽了开发周期变长的问题,但是我想说在相对简单的CURD的功能需求中,低代码开发模式比一般封装的组件/开源包来说还是便捷了非常多。

在修改增加一些小的功能点的时候,低代码模式没有了传统开发模式中如提交代码、构建项目这些流程,在可视化编辑器中保存完毕后,让测试同学刷新页面,前端重新获取JSON渲染即可。

amis的可视化编辑器整体使用起来也是比较简洁易懂的,推荐大家使用的时候,右侧菜单事件里的动作配置可以多看看,里面提供了比较丰富的动作配置选项。

使用不同组件,对应添加事件也会发生变化。

文本框可以添加的事件:

下拉框可以添加的事件:

事件添加完以后,点击旁边的➕就可以出现动作配置弹窗,里面提供了比较多可以配置的动作类型。

打开弹窗动作经常发生在接口请求完毕以后,需要提示接口响应结果,可以设置这个动作的执行/阻断条件,动态触发这个动作配置。

变量配置可以将请求结果/组件值改变等情况下变化的值存在特定的组件上,当然如果响应的结果刚好是数据域中已有的数据那就不需要有这一步了,很多时候接口响应的其他数据可能会作为执行其他业务逻辑的前置条件,此时就能派上用场了。

自定义JS 在需求里使用了,在弹窗发起接口成功后,需要调取刷新列表接口,这时候可以从外部把方法传入到amis渲染器中,然后通过这个动作配置进行触发,不过传入到amis渲染的这个过程需要依赖amis.embed这个api。(>>>amis.embed详情

js 复制代码
let amis = amisRequire('amis/embed');
let amisJSON = {
  type: 'page',
  body: {
    type: 'tpl',
    tpl: '${myData}'
  }
};
let amisScoped = amis.embed('#root', amisJSON, {
  data: {
    // 初始化的时候可以定义一个特殊的props对象,解构到data中使用
    ...[props],
    myData: 'amis'
  },
  context: {
    amisUser: {
      id: 1,
      name: 'test user'
    }
  }
});

新的事物总会带来利弊,尝试能够带来经验和价值的东西,总体还是值得的。

写到最后

本来想写Vue开发者重学React(虽然这个题材比较常见),但是最近正好在用amis开发业务需求,就让amis的文章插个队啦。(这也是笔者第一次写较长的技术文章,希望大家能指正写得不好的地方!)

感谢看到这里的你,如果觉得文章对你有帮助,那真的是我非常开心和荣幸的事了(顺手能点个赞就更好啦!),有问题也欢迎大家及时指正。

我是LEIZ,2024我们一起加油!

相关推荐
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
易云码2 小时前
信息安全建设方案,网络安全等保测评方案,等保技术解决方案,等保总体实施方案(Word原件)
数据库·物联网·安全·web安全·低代码
Oo_Amy_oO10 小时前
【极限编程(XP)】
低代码·极限编程
September_ning10 小时前
React.lazy() 懒加载
前端·react.js·前端框架
晴天飛 雪10 小时前
React 守卫路由
前端框架·reactjs
web行路人10 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
逆天的蝈蝈12 小时前
开源与商业的碰撞TPFLOW与Gadmin低代码的商业合作
低代码·开源
勤研科技12 小时前
低代码环境中的领域与根实体解析
低代码