基于form-generator的复杂表单引擎设计挑战

Situation(情境)

在为我们“产品规划部门”开发市场调研分析平台时,业务方要求实现动态配置1000+问题项数据申报表单,需满足:
业务人员通过可视化界面自主搭建表单(类似form-generator设计理念)

  • 支持跨表单级联校验​(如A字段值影响B字段的可视化规则)
  • 实现毫秒级实时预览,配置变更后立即渲染效果
  • 实现计算功能,某项可通过其他几项配置公式实时计算
  • 兼容移动端对问卷提交

原form-generator开源方案存在以下局限性:

  • 无逻辑控制功能,无法关联字段控制显示
  • 无公式配置功能,无法实现通过关联字段实现自动计算结果
  • 只能编辑表单,生成json sechme,无法预览

Task(任务)

作为前端负责人,需要完成:

  • 设计跨表单通信协议,实现字段级联动
  • 开发沙箱预览系统,隔离配置与运行环境差异
  • 开发公式配置功能,实现关联字段自动计算功能

Action(行动)

  • 通过开发逻辑配置界面,实现给某些题型增加显示控制逻辑,在渲染表单时,通过遍历表单item项,找出itemId所控制的logic
    // 逻辑控制函数
    logicShowHandle(value, field, item) {
      seqNo = this.formConfCopy.startSeqNo || 0
      // 值被包装过 需要取出原始值
      const { logicShowRule } = this.formConfCopy
      if (!logicShowRule) {
        return
      }
      // 找到该问题需要触发显示的问题 判断逻辑是否成立
      const rules = _.get(logicShowRule, field)
      if (rules && Array.isArray(rules)) {
        rules.forEach((r) => {
          // 成立让该对应的问题显示出来
          const flag = evalExpression(this.formModel, r.logicExpression)
          if (flag) {
            // 显示加上值必填等校验
            let triggerFormItem = this.formConfCopy.fields[this.fieldIdIndex[r.triggerFormItemId]]
            // 重新渲染组件 不然必填红点不显示
            if (triggerFormItem) {
              triggerFormItem.key = new Date().getTime()
              this.buildRule(triggerFormItem, this.formRules)
              // 设置默认值
              this.$set(this.formModel, r.triggerFormItemId, triggerFormItem.config.defaultValue)
            }
            // 防止表单重新渲染 display被刷新
            this.logicTriggerItemList.push(r.triggerFormItemId)
            let triggerEle = document.querySelector(`div[cid="${r.triggerFormItemId}"]`)
            if (triggerEle) {
              triggerEle.style.display = ''
            }
          } else {
            _.remove(this.logicTriggerItemList, (n) => n === r.triggerFormItemId)
            // 移除表单的必填校验
            let triggerFormItem = this.formConfCopy.fields[this.fieldIdIndex[r.triggerFormItemId]]
            if (triggerFormItem) {
              this.removeRule(triggerFormItem)
            }
            // 移除已经填写的值
            delete this.formModel[r.triggerFormItemId]
            let triggerEle = document.querySelector(`div[cid="${r.triggerFormItemId}"]`)
            if (triggerEle) {
              triggerEle.style.display = 'none'
            }
          }
        })
      }
    }
    /**
    * 执行表达式是否成立
    */
    export function evalExpression(context, expression) {
      const exArray = expression.split(/and|or/)
      // 获取是& 还是|
      const and = expression.indexOf('and') > -1
      let flag = false
      // console.log(exArray)
      for (let i = 0; i < exArray.length; i++) {
        const itemExpArr = exArray[i].trim().split(' ')
        // console.log(itemExpArr)
        // 截取字段名
        const varName = itemExpArr[0]
        // 条件 等于 不等于
        const sp = itemExpArr[1].trim()
        // 值
        const value = itemExpArr[2]
        // 比较是否成立
        // console.log(varName)
        const fieldValue = _.get(context, varName)
        // console.log(fieldValue)
        flag = expressionOperator[sp](fieldValue, value)
        // console.log(flag)
        // & 一个不成立直接调出循环 返回失败
        if (and && !flag) {
          break
          // | 一个成立直接调出循环 返回成功
        } else if (!and && flag) {
          break
        }
      }
      return flag
    }
  • 通过Proxy + iframe实现隔离环境,实现编辑完成即可预览
    const sandbox = new Proxy(window, {
    get(target, key) {
    return key in target ? target[key] : previewWindow[key];
    }
    });
  • 通过给item设置公式表达式,在初始化表单时,遍历找出绑定了公式及公式计算所需要值的item项,watch所有的form value如果匹配到计算项的值发生变化,使用正则替换掉公式对应的插值,使用用户填入的值,然后在利用mathjs的数学计算功能,将表达式提交置入mathjs evaluate方法中,从而得到正确的值,并赋值对应的item项,完成自动计算功能

Result(结果)

业务价值

  • 配置人员效率提升300%,新建表单平均耗时从3天缩短至4小时
  • 业务部门的市场调研报告生成效率从原来的 收集数据 -> 建立表单 -> 手动计算关联项 -> 手动撰写生成报告,转变配置表单项、现场调研问答、提交数据、一键生成报告(结合大模型总结调研的数据的趋势信息),报告生成周期由原来的15-20天提升至5天。
最后修改日期: 2025年 3月 26日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。