在部分业务场景中,构建业务流程需经历立项、开发、验收等环节,即便简单场景也耗时耗力,且部分场景缺乏统一管理归档方式。
因此,基于开源项目 VueFormMaking(https://github.com/GavinZhuLei/vue-form-making) 我们开发了低代码表单生成平台,实现表单模板化与可配置化,提高业务效率,便于管理与追溯。而平台核心在于通过拖拽及简单配置生成表单模板并一键复用。
以下是其核心表单模版页面:
一个低代码表单生成工具至少包含表单项组件开发、表单模版配置页面、表单模版渲染页面三个部分。以 VueFormMaking 代码结构为例进行阐述。
总体开发思路:表单组件通过 json 描述功能、样式、事件等信息,解析 json 后根据组件类型将 json 值绑定到对应组件的 props 或 events 上,具有组件复用性强、可跨组件库开发、系统迭代时 json 可复用等优势。
组件分类开发
基础组件开发:以 input 组件为例,其 scheme 核心数据结构包括 type 与 options,type 确定渲染组件类型,options 描述组件属性并与组件 props 有对应关系。
input 组件 scheme 如下:
element-ui 中 input 组件支持的 props API 如下:
高级组件开发:如文件上传、树形选择器等高级组件,json 数据结构更复杂但核心思想相同,先确定用于渲染的组件,再依组件 API 设计 scheme 结构。
布局/容器组件开发:以栅格布局组件为例,先确定渲染目标组件作为 options 配置项,再考虑其存储子组件的结构,如 columns 字段,根据组件特性完善数据结构。
以下是一个开发栅格布局组件的流程与思路:
在 element-ui 中,其支持的API如下:
我们取其中常用的核心部分作为配置支持即可,因此我们可以先实现以下的部分:
我们随后可以进行下一步思考,这个组件作为一个布局组件,那他必定需要拥有一个结构用于存储不同的子组件,我们可以假设这个用于存储的字段为 columns。
并且:(1)基于 grid 本身支持多列的性质来说,这个 columns 应该是一个数组,其每一个数组项与组件的 Col 的理念一致;(2)数组项的每一项也应该有与 Col 一致的API,并且每个 Col 应该支持多个组件在其内部。
因此其内部应该还有一个数组的数据结构用于存放其他组件的 scheme 。
基于以上推理,我们可以完善 栅格组件 的数据结构:
核心组成部分
组件商店:存放写好的表单项组件。
表单结构配置部分:通过拖拽等方式满足表单模版结构需求。
组件/表单可配置项编辑部分:编辑每个组件的 options。
分别对应下方的红蓝绿三部分:
2. 拖拽功能实现
一维拖拽:使用 Vue.Draggable 组件,红色部分(组件模版)与蓝色部分(组件实例)的拖拽本质是 JSON 的拷贝与移动,配置相同 group 实现跨组件列表拖拽。
以下是对于这两部分的简化模型,其中Draggable1 即为红色部分,Draggable2 为蓝色部分:
多层嵌套拖拽:
以栅格布局组件为例:
其实现的核心部分,在于其 Col 对应的组件内增加 Vue.Draggable 支持并统一 group 的 props 值,实现布局组件内基础组件的拖拽。其他类似的组件也是需要在其子组件对应的容器配置 Vue.Draggable 为统一 group 即可。
单独看蓝色与绿色的组成部分,其一个简化的模型如下:
这一部分在代码实现上与步骤2的蓝色部分的差异不大,其本质就是将组件options的配置项通过v-bind的方式绑定到组件对应的props上,并且给每个表单项赋值,通过v-model绑定这个值实现数据的双向绑定修改。
并且在最后提交表单的时候,遍历所有的表单项,取其对应的双向绑定的数据即可。
在这一步还需要考虑的其他重要功能:
表单项的格式检验,数值检验;
表单项的显示/隐藏,是否作为仅展示的组件(disabed);
布局组件嵌套布局组件时,需要进行深度递归遍历最内部的所有非嵌套组件进行检验与取值
。。。
在功能的迭代过程中,必定会出现修改旧组件模版的数据结构(新增、修改字段)的情况。
假如,我们需要给 input 添加一个 prefix 配置项,用于配置组件的前缀图标。那么其迭代的过程如下:
修改组件模版文件
2. 将新的属性赋值给已有的组件实例
那么在此过程中,只要通过 Object.assign 重新赋值即可,原因在于这里的 options 里面的属性都是扁平化的一层。
但是假如有个属性是嵌套的:
例如,现在需要新增一个属性 keyInsideNew 到 keyToModify 中,其新增后的结构如下。
在这个时候,如果使用 Object.assign 处理旧实例的话,keyToModify.keyInside 已经修改过的值就会被进行覆盖式的更新。那么之前的所有模版都需要重新进行配置了。
而如果不用 Object.assign ,要一个个对新的字段单独处理的话,也会另外造成代码臃肿的问题。因此,我们需要尽量避免出现多层次的配置的出现,尽量使用扁平化的配置字段。
注:这里仅描述了新增字段的情况,实际上可能会有修改字段名、删除字段的情况,但是这样的需求应该是需要避免的,会导致系统整体的杂糅与代码结构的混乱。
过去在开发后台的时候,我们可能会更多的注意功能上的实现,而忽略了一些基本的样式问题。特别是配置表单的时候,有些表单项可能不需要标签,而有的需要,这样会使得整体的对齐变得奇奇怪怪的。
我们可以从以下几个方面去进行优化:
为所有的组件添加相同的间距(padding、margin);
为所有的组件添加间距调整的配置项;
为所有组件添加 v-bind:style 来更好地自定义样式;
在一些表单配置好后进行验收,前端同学也加入进行样式的验收工作,手动去微调页面样式;
提前为调整样式等工作增加一定的排期时间;
。。。
在开发新组件与新功能时,务必精准界定并最大程度缩减当次改动的范畴。
具体而言,每次改动应严格限定于表单项组件开发的迭代更新、表单模版配置页面渲染的迭代优化或者表单信息填写页面渲染的迭代改进这三者中的某一项。
如此一来,便能有效规避因改动范围过度宽泛,致使难以预估是否会对原有模版及页面产生不良影响,进而保障平台或服务的稳定可靠运行。
在代码侧,我们也需要注意一些与组件相关的坑点,它们可能是使用的仓库或者组件本身带来的副作用,我们需要通过自己的代码实现来修复这些问题。
比如,在用的的 Vue.Draggable 组件中,他支持的 dragEnd 等事件内对应的数据都是浅拷贝的数据,这意味着只复制对象的最外层结构。如果对象内部包含引用类型(如数组、对象),那么浅拷贝后的对象和原对象会共享这些内部引用类型的值。这可能会导致意想不到的问题,比如在数据更新过程中,原数据和拷贝后的数据相互影响。
当我们拖动组件商店的组件模版进行编辑后,我们再使用相同的一个组件,修改其中一个组件的options,另一个对应的组件的options也会同步发生更改(浅拷贝的副作用),这就导致了表单模版编辑过程中容易出现巨大的影响。而修复的逻辑比较简单,通过 loadash 的 cloneDeep 函数进行深拷贝后再赋值即可避免类似问题的发生,但是同时也需要留意 布局组件嵌套独立组件 的问题,需要进行递归的深度遍历与拷贝。
总的来说,要完成一个低代码的平台的开发,我们必须做好以下工作:
要对其开发的平台的页面构成、工作流程进行深刻的认识;
由浅入深地认识一个组件需要的功能以及开发的过程,并提前制定好后续迭代与维护的策略,提高对系统的可维护性、可迭代性的要求;
在开发时及时把控好代码影响的范围,提高系统整体的可靠性;