cover_image

小程序表单配置与联动实现方案

cluo 微保技术
2019年09月27日 09:00

本文基于复杂的投保场景和小程序特殊的执行环境,通过对场景进行抽象、分层,实现投保表单展示与联动的配置化。

01

背景

表单(form)在桌面应用、app、web应用、小程序等均被广泛应用,各个平台上开发表单相关的功能在实现上也会有些不同,而对于表单的配置、联动也是很多开发者需要面临并解决的难题。微保小程序为用户提供保险服务,自然也面临大量投保参数填写的表单配置和联动问题。

02

方案

在实践过程中我们也形成了一套比较高效的方案。本文主要以年金产品的表单为例介绍该方案。下图是年金险的报价表单截图:

图片

上图可部分反映但不限于如下特点:

  • 表单语义鲜明。语义鲜明是报价模块较为突出的特性。以年金产品为例,常见的报价模块语义项包含“年龄”、“性别”、“交费年限”、“投入金额”、“投入方式”等,用户的报价均基于这些语义项的值。

  • 表单行数目可变。不同的保险公司在推出产品时对于用户的要求,设置的投保条件等可能会有出入,并不是同款产品一定强制相同的投保条件。

  • 表单行多状态(preview、edit、hidden)。上图中并没有截出投保用户已经实名过的状态,比如用户A在平台上实名后,姓名、性别、年龄等信息一般可以脱敏显示,无需用户再次填写。这种场景下,表单行的右侧只需使用文本控件显示相应的内容即可。

  • 表单行并非割裂的个体,相互之间可能存在数据的联动。比如性别为男性、投保年限为5年投的情况下,年龄可选的范围为18-55周岁;而女性、投保年限为5年投的情况下,年龄可选范围为18-50周岁。这只是一个简单的联动约束,真实的场景有更为复杂的表单行相互联动规则。


那么如何支持多款年金产品的同时上线呢?毫无疑问,报价模块的表单的配置化是解决问题的关键。针对上述的复杂的场景和问题,笔者从下面3个方面进行逐步突破:

2.1、表单描述

表单描述是以“表单行”为最小单元的描述过程。整个表单的配置化与显示是数据到视图的映射,数据、模板是表单的重要组成部分。表单描述过程的本质是设计配置数据的格式和模块的结构。将投保表单的截图转换成相应的视图模板,如下图所示:

图片

A. 表单配置数据设计

表单对应的配置数据笔者使用json格式,用数组来组织整个表单的行数据,每行的数据为一个对象。下面通过截取整个表单配置数据及“年龄”行数据进行示意。

配置下发数据截图:

图片

1.    {
2. "title": "你的年龄",
3. "component": "age",
4. "options": {
5. "index": 12,
6. "range": [18, 45],
7. "status": "edit",
8. "value": 30,
9. "content": "30周岁"
10. }
11. }

使用脑图对上述配置数据进行简单说明

图片

B. 表单模板结构设计

将整个表单组件划分为3层,如果算上小程序原生控件,将多达4层。分层示意图如下:

图片

如此分层的优势是比较明显的,每个层只需关注自己的逻辑,职责明确。而我们也是基于如下考量选择分层的思路。

  1. 小程序基础组件层充分利用小程序提供的基础控件能力,如picker、input等,开发者无需再做重复的开发,善用基础组件将大大提升开发效率。同时将此层独立处理后,基础组件被抽象出来,变成可插拔、可替换,开发者可以选择自己现用的其他第三方组件库来实现基础的表单能力。

  2. 基础组件的包装层可以理解为基础组件与表单行组件的适配器。通过对基础组件的包装屏蔽事件、API的差异,使得表单行组件可以统一地编写对基础组件的控制代码。如下图所示,基础组件在事件冒泡时事件名称、上报数据格式、基础组件初始化属性设置可能存在差异,尤其是使用第三方组件库,这种差异可能会更大。


    图片

  3. 表单行层是具体捕获底层组件事件、加工业务数据的处理器。保险是一个垂直场景,每一个表单行所代表的业务数据是不一样的,表单行将用户交互产生的数据进行语义包装。如下代码截图,让表单行真正具有通用的处理能力,才能保证整个流程可配置的实现。


    图片

  4. 最外层的表单组件职责相对简化,负责外部数据的注入与内部状态的抛出。


相应的分层类图设计相对比较复杂,如下图所示:

图片

2.2、联动机制

表单联动简单理解就是不同的表单行(联动的粒度是表单行)之间存在制约关系,一个表单行的值会影响其他若干表单行的值域、状态、显示与隐藏等。配置数据通过初始化驱动表单视图显示,用户打开此视图后,会根据实际情况、个人喜好操作表单,由此导致相应的数据值发生变化。即使是同类型但不同的保险公司推出的保险产品,其受限规则可能都是不同的。但不管是哪种情况,表单联动要解决的核心问题有2个:

  • 关注的表单变化

  • 响应该变化的动作

本文使用观察者模式来解决此问题,实现的难点在于如何将整个观察与响应的行为在后台配置并且能让C端代码理解。为了将表单联动配置化,在配置中引入“触发器(trigger)”和“行为(action)”的概念。

1.    {
2. "title": "你的年龄",
3. "component": "age",
4. "options": {
5. "index": 12,
6. "range": [18, 45],
7. "status": "edit",
8. "value": 30,
9. "content": "30周岁",
10. "triggers": ["gender"],
11. "action": "var config={'F':[18,50],'M':[18,55]};var g=page.getFormValue('gender');var a=page.getFormValue('age');page.setForm({range:config[g],value:a||30});"
12. }
13. }

triggers是触发器数组,数组中的项为待监听的表单事件名字符串。action是该表单行监听到触发器中的事件列表发生时的行为,为一段可执行的js脚本。表单行组件初始化时注册监听于相应的处理代码,如下

1.    registerAction() {
2. const { triggers, action } = this.properties;
3. triggers && triggers.forEach((t) => {
4. this.subscribe(t, (data) => this.getTask(action, data));
5. });
6. }

每一个表单行组件在监听到内部子组件发生数据变更时,将会在表单内部广播一条与自己同名的事件,比如“年龄”表单行中用户选择不同的年龄,该组件会发布“age”事件。如下图所示:

图片

如何执行配置下发的js脚本?众所周知,小程序不支持通过”eval”和”new Function”执行脚本代码字符串,要打破小程序执行动态脚本的限制,必须另辟蹊径。笔者使用js来执行js脚本。这句话看上去有点绕,专业一点讲就是使用js运行“js脚本字符串”。虽然在日常的开发中,这并不常见,但它并不是一件困难的事情。所有语言的执行都遵从编译原理的过程,将“源代码”转换成“目标代码”以被执行环境所理解。过程如下图所示:

图片

得益于社区中稳定的抽象语法树(AST)生成库,如acorn、babylon等,开发者不需要从头实现代码的分析过程,引入相应的库即可快速获取AST。在此基础上实现具体版本的ES规范即可。笔者引入canjs库,该库使用acorn处理javascript代码并生成AST,目前的实现仅支持ES5规范的大部分语法。引入动态脚本的执行能力就像是一把双刃剑,它给配置策略的实现带来极大的灵活性,但同时,它的安全性、调试的便捷性也是面临的问题。基于这些考量,对下发的脚本做如下约束:

  • 限制作用域。代码执行环境的作用域限制在表单行实例内,内部无法访问小程序全局的对象和函数。

  • 逻辑与功能足够简单。代码中只处理表单的联动,这些联动方法都是封装在表单行的behavior上,对于开发者透明易用,在调试时更易发现问题。

2.3、表单更新&数据维护

表单模块与业务页面或其他外部组件交互的实质是共享表单状态。而表单模块内部自上而下通过设置子组件的属性值更新子组件状态,自下而上通过事件冒泡传递子组件状态。如下图所示:

图片

03

方案总结

回顾整个方案的设计和实现,成功解决了本文开始提到的3个问题,并且具备以下优势:

  1. 基础组件与业务解耦,整套上层设计可以轻松移植到开发者自有的组件库上,只需做少量兼容;

  2. 使用template包裹业务组件突破小程序抽象节点不能动态渲染组件的限制,由于不同的产品涉及到表单行数目不同、功能不同,使用template根据配置数据动态地渲染组件;

  3. 业务组件(表单行组件)在同类型保险产品中可枚举、可复用,比如“年龄组件”、“投保年限组件”等,同时具有很强的垂直领域迁移能力,上下层几乎可以不变,新增相应的业务组件即可;

目前,该方案已经上线并支持多款保险产品配置上线,运行稳定。


图片
图片

搜索公众号关注:微保技术

or

长按扫码可关注




请长按二维码,可加入我们哦!
图片
图片


继续滑动看下一个
微保技术
向上滑动看下一个