基于formily的低代码实践

6,027 阅读6分钟

前言

初识formily是在好几年前,还为它有写过一篇文章:uform使用笔记。但遗憾的是,一直没有将它用于实际的业务。

直至去年的2月份,因缘巧合下,在业务项目中开始使用v1.x版本。好像那会儿formily作者在推v2.x的beta版本,但了解到基本上是断崖式升级,就放弃了,毕竟那会儿用1.x已经开发了很多代码了,是真的不敢动。当然,其实用微前端的话,也可以用formily2.x的beta版本,只是那会儿还没有必要用到微前端

好巧不巧,我所在的项目组在去年9月因为一些原因,被迫解散,然后去了新的团队,刚好又有了尝试formily的机会,所以果断用了beta的版本,期间也经历过一些不太稳定吧,好在后面正式升了v2的版本,好了很多。

新手期

在刚开始用的时候,也是各种查文档,向身边用过formily的同事请教写法。慢慢地,明白了其实也就两种写法:

  • json schema
  • 纯JSX

其实文档中还有第三种写法,叫markup schema,但个人觉得这个蛮鸡肋的,比如说中间想写一个div的结构,它会将这个div插入到最后(明显不满足自己的需求)

我们的业务场景是物流Sass,在详情页面会有N表单项。因为我个人比较倾向于写jsx,所以在刚开始的时候,就各种写JSX。但写着写着,发现好累啊,而且有些代码长着也差不多,于是想着再封装一层,是json,但是类型是这样的:

export interface ISchema {
  component: 'Row';
  componentProps?: RowProps;
  areaList: {
    key: string;
    colSpan?: string;
    componentProps?: any; // 可以是col的props或者div的props
    useGrid?: boolean;
    formItemProps?: IFormItemProps;
    gridProps?: {
      minWidth?: number | number[];
      maxWidth?: number | number[];
      minColumns?: number | number[];
      maxColumns?: number | number[];
      breakpoints?: number[];
      columnGap?: number;
      rowGap?: number;
      colWrap?: boolean;
    };
    children: {
      name?: string;
      title?: string;
      key: string;
      type: 'basic' | 'custom';
      component: string;
      dataSource?: {
        label: string;
        value: any;
      }[];
      rules?: Rule[];
      componentProps?: {
        [key: string]: any;
      };
      fieldProps?: {
        [key: string]: any;
      };
      formItemProps?: IFormItemProps;
      gridColumnProps?: {
        gridSpan?: number;
      };
    }[];
  }[];
}

简单说一下,为什么要那么设计。。因为我们的详情页面布局,差不多可以分成一个或者多个区块,每个区块里面就是grid布局。

然后拿它做了一些实验,确实跑的很好。

成长期

这个json写多了,也会有一些思考,就是感觉和formily本身提供的json schema格格不入。

直至后面领导让我考虑,通过低代码来配置出来这些json。主要是有几个方面的考虑:

  • 权限(按钮/表单项)
  • 校验规则(前后端尽量使用同一套)
  • 国际化

讲真,开始挠头了。然后认真去读了一下formily提供的json schema协议后,发现其实它设计的挺好的,并且有现成的designer设计器

在改回json schema的过程中,我重新审视了之前的ISchema的设计,发现我自己写的自定义组件,完全没有考虑type: void的情况,就导致了有些地方会去重置basePath,其实这是反模式的。

磐石设计

磐石是这个低代码的一个代号。下面我简单分享一下。

前置内容

这个低代码平台的产物是json schema.

业务工程里面,根据页面来获取当前页下面所有的json list(一个页面有些地方是割裂的,所以会拆成多个json,包括还可能存在弹窗里面的表单)。

对应块只要渲染匹配名称json schema即可。

页面部分

新增弹窗

image.png

需要说明的是为什么会有按钮。因为在业务工程的保存按钮点击后,后面要根据请求URL找到它对应的按钮,按钮里面会有两个东西:

  • 权限(即这个用户有没有权限点这个保存)
  • 去查找它对应的所有的json列表(从而拿到所有表单项的rule list)

tips: 按钮也会有一个专门的增删改查,这里面有一个要考虑的点是权限,譬如说权限code间的组件,譬如:

{{a}} && {{b} || {{c}

a/b/c是代表权限code,这里为什么要用插值表达式呢?因为到前端的时候,需要replacetrue or false

磐石列表

image.png

功能简单说明:

  • 编辑:弹窗里面的内容
  • JSON编辑:formily的desiger
  • 删除
  • 复制:主要是复制JSON编辑器生成出来的json schema

设计器详情

image.png

更深入的内容

权限

有两个地方的权限:

  • 按钮
  • 表单项

按钮的话,上面已经讲过了,无非就是给它一个权限表达式。前端处理一下即可。

表单的处理方式,就略巧妙了,咱们可以扩展一个x-permission。然后解析schema的时候处理一下即可。

Schema.registerPatches(schema => {
  // sole.log(schema['x-permission']);
  const permission = schema['x-permission'];
  if (permission) { // 这里做一些处理
    schema['x-display'] = 'none';
  }
  return schema;
});

校验规则

 <Button
    onClick={async () => {
      const json = transformToSchema(designer.getCurrentTree());
      await saveDetail({
        json: json.schema,
        processJson: filterVoidSchema(json.schema as ISchema),
        relaId: id,
        name,
      });
      message.success('保存成功');
    }}
 >
    <TextWidget>Save</TextWidget>
</Button>

看上面的代码,在进行保存的时候,其实是往后端传了两个json的,一个是前端需要的json,一个是后端需要的json。

我们看函数名:fiterVoidSchema,过滤掉void的schema,因为前端布局会造成很多冗余的json项,让后端去做递归也可以,但前端更方便一些吧。

image.png

后端可以快速地拿到x-rules的字段,从而做到前后端的校验统一

国际化的处理

不知道大家有没有用过kiwi。我司基于它改造了一番,因为它之前内置google翻译,然后我们改成了百度翻译,且另外加了自己的一些扩展。

话说回来,这是一个比较好的方案,但不是一个最优的方案。因为文案的增多,会造成国际化文件的不断庞大,譬如下面的两段文案:

你确定删除么?
你确定要删除?

其实它俩是一个意思。我们这个平台的国际化方案如下:

  1. label的国际化,是通过后端去处理我上传的json schema
  2. 提示类的,统一写一个方法,传入还是中文,传出的值则取决于当前的语言环境。当然页面会加载一个文件(中文为key,语言环境为value的json)
  3. 页面里面本身的中文(这一块暂时没想法,也许还是会采用kiwi)

未来要做的事

其实要做的事还有很多,譬如说引用磐石页面(类似公共池的概念,公共的地方componentProps改了,引用处也要修改掉)。

说实话,很挠头。。。愁得我头发都快掉光了,有类似需求、想法的朋友们,可以一起交流一下。。

写在最后

关于formily如何使用,其实在文中,我基本没怎么提到,大家可以自行参考文档。说实话,成本确实有,但是学过一次,受益终生(这是不可能的,但好歹能让你吃点红利)。

对比可视化引擎工具formily designer的扩展性还有待提高(这里是指我们往里面加一个组件,需要在很多地方操作),但各有各的优势。