Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

表单解析器 · 开发教程 #32

Open
JakHuang opened this issue May 5, 2020 · 17 comments
Open

表单解析器 · 开发教程 #32

JakHuang opened this issue May 5, 2020 · 17 comments

Comments

@JakHuang
Copy link
Owner

JakHuang commented May 5, 2020

本文描述的解析器,是一个能将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代码预览器 · 开发教程》

@heshenghao617
Copy link

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

@RenHans
Copy link

RenHans commented Jul 3, 2020

同问呢

@PedroGuo
Copy link

PedroGuo commented Jul 7, 2020

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

@zoudemin
Copy link

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

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

@liangshaofengGithub
Copy link

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

@luckyiii
Copy link

luckyiii commented Sep 9, 2020

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

@sevents
Copy link

sevents commented Oct 12, 2020

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

@konnga
Copy link

konnga commented Feb 19, 2021

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

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

@shitou13
Copy link

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

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

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

@konnga
Copy link

konnga commented Sep 30, 2021

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

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

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

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

@RenHans
Copy link

RenHans commented Dec 24, 2021 via email

@itshu
Copy link

itshu commented May 7, 2022

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

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

@RenHans
Copy link

RenHans commented May 7, 2022 via email

@jy1216
Copy link

jy1216 commented Jun 22, 2022

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

@RenHans
Copy link

RenHans commented Jun 22, 2022 via email

@DazzlingSunny
Copy link

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

@RenHans
Copy link

RenHans commented Jan 13, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests