动态表单设计-指导思想

3,719 阅读8分钟

为什么要动态表单?

项目中需要列表字段动态化,产生了间接需求,表单动态化(当字段同时出现在列表及其编辑表单中时,如果该字段被配置为不显示,那么列表和表单应该同时隐藏掉改字段)

动态表单存在那些问题?

1.布局笨重,不够灵活

字段间的排布方式

字段间布局,一般有两种情况

栅格布局

栅格布局的优势是布局灵活,缺点是很难做到自动换行
一般栅格会在水平方向上,将可用空间分为24个等宽的格子 当进行如下配置时:

[{    span:8  },{    span:16}]

会得到如下的结果:

当屏幕缩小或者浏览器视窗缩小时,我们希望得到如下结果

然而结果可能是这样:

或者这样:

如果想在想在他们之间插入一个纯布局的ui元素C,我们就会陷入困境:

究竟缩小A,B的宽度,以腾出一个新的区域,如下:

[{    span:7  },{    span:2  },{    span:15}]


还是不改变栅格布局,而是从A或者B中扣一块区域出来,给C呢?
下面展示了从B中抠出一块区域给C的场景:

分析

第一种方式

  • 优点:不需要组件做适配,更改下栅格布局配置即可
  • 缺点:会影响到其它组件的可用空间 第二种方式
  • 优点:不影响栅格布局配置,B组件内能实现源码级的,对C组件的填充进来的场景做适配,优化
  • 缺点:需要修改B组件源码,在其内部加一个可以填充其他组件的空间(插槽),比较笨重 这两种方式,对布局上,没有绝对的好坏,根据场景灵活选择最好

流式布局

流式布局,分为行和列方向上的流式布局,一般使用行的流式布局
当横向空间不足时,会自动换行,下图C因为空间不足,掉到了第二行:

将流式布局和栅格布局进行融合,或许才是最理想的解决方案

字段间内的布局

字段内的布局,指的是这个输入项所配套的中文字段名,可能存在的中文字段名释意,单位,校验提示以及输入项本身等之间的布局,如:
一般情况,对字段间的布局,我们常见的是设置中文字段和输入交互区的排列方式(横向,纵向)
但还是有很多场景,需要配置中文字段名、中文释义、单位、校验提示的可见性,校验结果的展示方式(如图的内联文本以及toast、dialog等)
如果要实现以上效果,我们可以增加很多配置项,然后文档一大堆吧啦吧啦

如果要支持的场景有限,虽然这样很麻烦,但的确是最灵活的方式!
但如果我们有很多特殊场景呢?比如:


此时我们就得修改代码,去加对应的配置和界面逻辑,form-item组件将变得越来越庞大,复杂,最后失去控制:

思考,如果我们将上述布局,做成主题,到时候,配置一个主题即可,是否会简化繁琐的配置项?以下是两个不同的主题:



这样,简单的配置下主题,即可简化很多繁琐过程。
但这又会引入新的问题: 不同主题间,存在差异化配置项,这回导致可视化布局系统设计会更加复杂

实际情况下,往往推荐使用主题 + 局部配置项来完成整套方案,这实际上是将单层配置变成了双层配置,一定程度上简化了心智负担,大幅度降低了单个主题内组件的复杂度

多输入结果融合

当姓和名为独立的两个input,但结果仅需要name一个字段,此时就需要用到字段融合

如何插入一个非字段的界面元素?

表单之外的界面布局,我们可以用代码去实现
但有时候,为了满足ui设计图的效果,我们往往希望能在表单内添加一些和字段无关的界面元素,如插图,文本,线条,甚至按钮,地图,自定义的组件等

那么,要如何梳理规范我们配置项,去描述一个非字段的界面元素呢?

多记录字段

部分表单,或字段,其结果为一个数组,例如:

最终表单提交的字段值为:

{
  ...
  statOrder:[{
      drugName:'1',
      count:3,
      dose:302
  },{
      drugName:'2',
      count:4,
      dose:50
  }]
  ...
}

这种类型的字段,复杂点在于我们要考虑如何做到记录的增删改,以及如何灵活布局

如何优雅实现字段校验

表单联动

选择型字段:从一组可选项中选出一个或多个的的输入类型,如:下拉框、复选框,单选框等

  • 选择型字段选项与字段的联动
    • 可用性联动
    • 可见性联动
    • 样式联动
  • 字段与选择型字段选项的联动
    • 可用性联动
    • 可见性联动
    • 样式联动
  • 选择型字段间选项的联动
    • 可用性联动
    • 可见性联动
    • 样式联动

样式联动

值联动

从变字段值根据一到多个因变字段根据某个算法自动计算而来时,叫值的联动,此时的因变字段一般为不可用
下面展示了住院天数自动计算的场景



试想下如何实现这个特性呢?

基于字符串化的函数调用

目前市面上我了解到的,基本使用的基于字符串化的函数调用,其核心特点是预置常用公式,使用eval函数以祝福穿的方式执行内置函数:

function MAX(a,b){...}
function DayCount(start,end){...}
...
{
	formula:'MAX(_$field1$_,_$field2$_)'
},
...
{
	formula:'DayCount(_$field1$_-_$field2$_)'
}
核心部分
// step-1 利用正则从options.formula中提取字段 //field1,field2
...
// step-2 从model(表单值)中获取field1,field2的值 // 2,3
...
// step-3 利用正则,将options.formula中的字段替换为对应的值
...
// step-4 执行字符串
eval(options.formula) // 以MAX公式为例,实际上这里是执行的eval(`MAX(2,3)`)
// 将执行公式获取到的结果,赋值到model中对应字段上
...

以上方式在解决实际场景时,能覆盖大部分,但仍旧不够灵活,对于特殊场景下的自动计算,特备费劲

上面的场景,使用基于字符串话的公式调用,比直接写代码都要复杂的多,也根本无法维护
于是此时,可以考虑直接配置函数的方式

显示化函数配置

{
	formula:function(model){
    	//一顿操作后
    	//...
        return ...
    }
}

这样的方式,是不是更直观,更灵活呢?
但很遗憾,这种方式有其致命缺点:json不支持函数序列化,也就是说,将配置保存到数据库中再获取下来后,会导致formula全部失效!
当然也并非没有处理方式,那就是将函数字符串化

option = {
	formula:`function(model){
    	//一顿操作后
    	//...
        return ...
    }`
}

然后

eval(`option.formula=${option.formula}`)
//这样就可以将字符串函数,转成js中标准函数了
// 然后将计算结果赋值给model指定字段即可
model[filed] = options.formula(model)

为了在开发阶段能好的体验,我们可以在开发阶段将formula配置成函数,在上传数据库时,提供转化工具,将其函数转成字符串,组件内部自动识别并转化字符串formula为函数formula

//转化工具方法核心逻辑:
option.formula = `${option.formula}`
//自动识别并转化字符串formula为函数formula
if(typeof option.formula === 'string'){
	eval(`option.formula=${option.formula}`)
}
option.formula(model)
...

字段过滤

两个字段间存在联动时,且主变字段仅仅用作控制从变字段联动时,此时主变字段不应该出现在表单的提交数据中

表单回填问题

这里要从两个方面考虑回填问题:

  • 表单变更
  • 被过滤的字段回填

表单变更

因为表单是基于配置项的,而且很可能配置是由我们内部配置或者用户自定义生成并存在于数据库的,在迭代和用户使用的过程中,难免会发生表单的变动,此时,要考虑旧数据的回填问题
这里提供两种思路:

  • 提供表单版本机制,旧数据使用对应的旧版本表单
  • 如果表单预期可控,新表可以单向下兼容旧表单值

被过滤的字段回填

在前面提到过,部分主变字段会有被过滤掉值的情况,在回填时,其值相应的也无法正确回填,这回导致致命的后果:部分因变字段可能无法显示

是否要脱离三方ui?

目前市面上的,基本是和ui深度整合的,有些实力强大的开源项目,更是对多个热门ui做了源码级适配,但这样会花费大量的人力资源,并且,由于底层架构的问题,基本很难支持移动h5端
用于商业的项目,基本都是使用了三方ui的,如果动态表单没有找到对应的ui适配版本的,则无法使用,或者必须引入动态表单绑定的ui

远程选项

部分选择型字段的选项,来自于数据库
这里得考虑如何更简单的对接,和处理token或者cookie

定位:方案VS渲染内核

在设计动态表单时,需要优先定位是开箱即用的动态表单方案,还是仅仅一个动态表单渲染内核。 其区别在于,

  • 内核:仅定义规则,和渲染规则,不做过多的内置组件,提供拓展api,开发者需要在项目中配置自己的组件池
  • 方案:开箱即用,内置丰富组件,工具,开发者开箱即用,方便,但臃肿,不灵活

附录: