JakHuang/form-generator

表单解析器 · 开发教程

JakHuang opened this issue · 17 comments

本文描述的解析器,是一个能将form-generator导出的json表单,解析为一个真实表单的程序。
接下来的行文中使用【json表单】表示form-generator导出的json表单。

剧透:本文其实就是带大家阅读parser.vue源码,哈哈。

布局

json表单目前支持两种布局:
colFormItem和rowFormItem

1.1 colFormItem布局

colFormItem布局(以el-input为例)对应的json形式如下:

{
    "__config__": {
      "label": "单行文本",
      "labelWidth": null,
      "showLabel": true,
      "changeTag": true,
      "tag": "el-input",
      "tagIcon": "input",
      "required": true,
      "layout": "colFormItem",
      "span": 12,
      "document": "https://element.eleme.cn/#/zh-CN/component/input",
      "regList": [{
        "pattern": "/^1(3|4|5|7|8|9)\\d{9}$/",
        "message": "手机号格式错误"
      }]
    },
    "__slot__": {
      "prepend": "",
      "append": ""
    },
    "__vModel__": "mobile",
    "placeholder": "请输入手机号",
    "style": {
      "width": "100%"
    },
    "clearable": true,
    "prefix-icon": "el-icon-mobile",
    "suffix-icon": "",
    "maxlength": 11,
    "show-word-limit": true,
    "readonly": false,
    "disabled": false
  }

colFormItem布局对应的目标实际代码如下 :

    <el-col :span="12">
      <el-form-item label="单行文本" prop="mobile">
        <el-input v-model="formData.mobile" placeholder="请输入手机号" :maxlength="11" show-word-limit clearable
          prefix-icon='el-icon-mobile' :style="{width: '100%'}"></el-input>
      </el-form-item>
    </el-col>

在这个json到xml的解析过程中,form-generator的parser使用jsx来完成

...
const layouts = {
  colFormItem(h, scheme) {
    const config = scheme.__config__
    const listeners = buildListeners.call(this, scheme)
    let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
    if (config.showLabel === false) labelWidth = '0'
    return (
      <el-col span={config.span}>
        <el-form-item label-width={labelWidth} prop={scheme.__vModel__}
          label={config.showLabel ? config.label : ''}>
          <render conf={scheme} {...{ on: listeners }} />
        </el-form-item>
      </el-col>
    )
  },
...
}

1.2 rowFormItem布局

rowFormItem布局对应的json形式如下:

  {
    "__config__": {
      "layout": "rowFormItem",
      "tagIcon": "row",
      "layoutTree": true,
      "document": "https://element.eleme.cn/#/zh-CN/component/layout#row-attributes",
      "span": 12,
      "formId": 104,
      "renderKey": 1594570310282,
      "componentName": "row104",
      "children": []
    },
    "type": "default",
    "justify": "start",
    "align": "top"
  }

对应的目标代码如下:

    <el-col :span="12">
      <el-row>
      </el-row>
    </el-col>

同样使用jsx来完成布局解析:

  rowFormItem(h, scheme) {
    let child = renderChildren.apply(this, arguments)
    if (scheme.type === 'flex') {
      child = <el-row type={scheme.type} justify={scheme.justify} align={scheme.align}>
              {child}
            </el-row>
    }
    return (
      <el-col span={scheme.span}>
        <el-row gutter={scheme.gutter}>
          {child}
        </el-row>
      </el-col>
    )
  }

值得注意的是,json表单支持嵌套; 通过__config__.children记录嵌套关系。使用renderChildren递归解析。(目前仅对rowFormItem布局的children做解析)

function renderChildren(h, scheme) {
  const config = scheme.__config__
  if (!Array.isArray(config.children)) return null
  return renderFormItem.call(this, h, config.children)
}

完整的代码,请阅读parse源码,此链接中的版本并不算复杂。

数据和逻辑

传统的vue格式表单,我们可能需要写如下格式的js,完成element UI表单的数据和逻辑。

export default {
  data() {
    return {
      formData: {
        mobile: undefined,
        field103: undefined,
      },
      rules: {
        mobile: [{
          required: true,
          message: '请输入手机号',
          trigger: 'blur'
        }, {
          pattern: /^1(3|4|5|7|8|9)\d{9}$/,
          message: '手机号格式错误',
          trigger: 'blur'
        }],
        field103: [{
          required: true,
          message: '请输入密码',
          trigger: 'blur'
        }],
      },
    }
  },
  methods: {
    submitForm() {
      this.$refs['elForm'].validate(valid => {
        if (!valid) return
        // TODO 提交表单
      })
    },
    resetForm() {
      this.$refs['elForm'].resetFields()
    },
  }
}

对于解析器来说,这是一个抽象的过程:

  • 数据部分:
    构建表单数据实现如下:
  data() {
    const data = {
      formConfCopy: deepClone(this.formConf),
      [this.formConf.formModel]: {},
      [this.formConf.formRules]: {}
    }
    this.initFormData(data.formConfCopy.fields, data[this.formConf.formModel])
    this.buildRules(data.formConfCopy.fields, data[this.formConf.formRules])
    return data
  },
  • 逻辑部分:
    请阅读,源码 methods 部分。这块和你日常vue编程差不多,只不过属性都是抽象的。

JSON表单结构说明

上边的一系列操作,都是建立在理解json表单都有哪些内容的基础上的。详细请参阅JSON参数对照表

form-generator中的render.js

render.js就是对vue的render函数的简单定制封装。如果你还不理解vue的render函数,请移步至:渲染函数 & JSX
render.js实现的功能是将json表单中的__config__.tag解析为具体的vue组件; 其工作过程可以理解为以下3个部分:

  render(h) {
    const dataObject = makeDataObject()
    const confClone = deepClone(this.conf)
    const children = []

    // 1 如果slots文件夹存在与当前tag同名的文件,则执行文件中的代码
    mountSlotFlies.call(this, h, confClone, children)

    // 2 将字符串类型的事件,发送为消息
    emitEvents.call(this, confClone)

    // 3 将json表单配置转化为vue render可以识别的 “数据对象(dataObject)”
    buildDataObject.call(this, confClone, dataObject)

    return h(this.conf.__config__.tag, dataObject, children)
  }

更多细节:源码render/render.js

关于拓展和讨论

本项目仅仅是开了个表单渲染头;实际要根据需求的差异,要做不一样的定制。之所以在issue写文章,是希望各位能充分利用好下边的评论功能,大家友好探讨。

系列教程:
《表单设计器 · 开发教程》
《表单解析器 · 开发教程》
《vue代码生成器 · 开发教程》
《vue代码预览器 · 开发教程》

怎么没有看到表单解析器的开发教程呢

同问呢

说好的表单解析开发教程呢, 大佬是不是忘记弄了

怎么没有看到表单解析器的开发教程呢

parser啊大哥 大佬开始就写了 这是parser的解说 看下router路由 可以直接用的 扩展的话稍微看下你就懂了不用人家说 并且 编辑的时候运行有单独的iframe引入 已经和这功能解耦了

我在parser上通过异步下载json 回来进行解析成页面,所有的表单项都能出来,唯独是表单的校验功能不正常,有大佬能正常校验的吗

请问如果设计一个标签组件,内容是一个行容器,可行吗?想了挺久,没想到合适的解决方案,恳请各位大佬指点一下

动态数据,除了在formConf 内直接构造外(即在formConf数据未到 parser 解析渲染前),还有其它方式可以动态构造吗

我在parser上通过异步下载json 回来进行解析成页面,所有的表单项都能出来,唯独是表单的校验功能不正常,有大佬能正常校验的吗

请问你的表单校验问题解决了吗?

我在parser上通过异步下载json 回来进行解析成页面,所有的表单项都能出来,唯独是表单的校验功能不正常,有大佬能正常校验的吗

请问你的表单校验问题解决了吗?

同问,为什么我的表单本地数据可以服务端异步传来的解析不成功?

我在parser上通过异步下载json 回来进行解析成页面,所有的表单项都能出来,唯独是表单的校验功能不正常,有大佬能正常校验的吗

请问你的表单校验问题解决了吗?

同问,为什么我的表单本地数据可以服务端异步传来的解析不成功?

表单校验的初始化应该是在获取到数据后,可以watch数据,拿到后再构建表单校验规则

itshu commented

我在parser上通过异步下载json 回来进行解析成页面,所有的表单项都能出来,唯独是表单的校验功能不正常,有大佬能正常校验的吗

怎么异步解析数据呢?望指教

请问 解析时候 我加了一个题目逻辑的设置,把不需要的题目隐藏,用display:none; 然后radio的选种样式就没有了,这个有遇到吗 怎么处理

大侠们,自定义组件应该在哪里获取__config__的数据