基于json-schema的vue、element-ui动态表单 - 第一版

4,441 阅读3分钟

开发过程中常常遇到各种各样的表单,有的重复度很高,就产生了自己封装一个动态form表单组件的想法

灵感来源ncform和阿里巴巴的formilyjs地址为https://formilyjs.org/#/0yTeT0/VEt5tQHbh2

代码写的比较烂,希望大家多提提意见和建议

留下您的表单使用场景,我们把他考虑进去

核心

递归

目前只在渲染组件提供了 props为value  @input事件

例如

<el-input value="xxx" @input="xxxx">

自定义组件必须提供v-model

为什么用template而不是render

使用render时, formData改变会导致整个表单重绘。有些时候我们只是向去更新对应的组件而已。

依赖

vue element-ui lodash(get, set, debounce)

能做什么

通过书写json schema来控制表单生成

接下来的考虑

  • 接收大家的意见和建议
  • 如何更加自由的UI布局

示例

点我查看示例

Attributes

参数 说明 类型 可选值 默认值
value/v-model 绑定值 object - {}
schema schema用来渲染表单 object - {}

Events

事件名称 说明 回调参数
input 当绑定值变化时触发的事件 (value)
onFormMount 当表单初始化完毕时出发 (formData)
onFieldChange 表单的某个字段发生变化 (idxChain, value, schema) schema当前组件的schema

schema

未来计划支持表达式(disabled, hidden)

{
    type: 'object|string|number|array|array-card|array-tabs|array-table',
    // 目前array只支持 array和array-table
    //当type为 object时 properties必须
    properties: {},
    //当type为 array时 items必须
    items: {},
    ui: {
        // 同element
        column: 'default - 24',
        default: '默认值',
        labelWidth: 'el-form-item label-width',
        label: 'el-form-item label',
        widget: 'el-input等等,接受一切v-model的组件',
        render: (h, context) => { // h同vue, context 同vue functional }
        hidden: 'boolean|function', // (formData) => {}
        rules: 'array|function', // (formData) => {}
        disabled: 'boolean|function' // (formData) => {}
    }
}

如果要设置formData,请使用this.$refs.debbyForm.setFormData

setFormData(idxChain, value) {
  utils.set(this.dataForm, idxChain, value)
}

Examples

一个比较复杂点的场景

vue template

<debby-form
  v-model="dataForm"
  label-width="140px"
  label-position="top"
  :schema="formSchema"
/>

schema

{
    type: 'object',
    'ui:label': '班级',
    'ui:showLabel': true,
    properties: {
      grade: {
        type: 'string',
        default: '3',
        ui: {
          label: '年级',
          column: 12
        },
        rules: [
          {
            required: true, message: 'custom rule', trigger: 'blur'
          }
        ]
      },
      class: {
        type: 'string',
        ui: {
          label: '班级',
          column: 12,
          disabled: (formData) => {
            return formData.grade === '3'
          }
        },
        /**
         * 验证器同el-form的验证器 方法里面还支持this哦
         * @param formData
         * @returns {[{validator: validGrade, trigger: string, required: boolean}]}
         */
        rules: function(formData) {
          const validGrade = (rule, value, callback) => {
            if (formData.grade < 10) {
              callback(new Error('年级不能大于10'))
            }
          }
          return [
            {
              required: true, validator: validGrade, trigger: 'blur'
            }
          ]
        }
      },
      other: {
        type: 'object',
        ui: {
          label: '属性'
        },
        properties: {
          stuNum: {
            type: 'string',
            ui: {
              label: '学生数量'
            }
          },
          teaNum: {
            type: 'string',
            ui: {
              label: '教师数量'
            }
          }
        }
      },
      students: {
        type: 'array',
        ui: {
          label: '学生信息'
        },
        items: {
          name: {
            type: 'string',
            ui: {
              label: '姓名'
            },
            rules: function() {
              const validName = (rule, value, callback) => {
                if (!isNaN(value)) {
                  callback(new Error('不能为空并且不能输入数字哦'))
                }
              }
              return [
                {
                  required: true, validator: validName, trigger: 'blur'
                }
              ]
            }
          },
          sex: {
            type: 'boolean',
            default: true,
            ui: {
              label: '性别',
              widget: 'el-checkbox'
            }
          }
        }
      }
    }
    }

一个支持 vue render的场景

schema

{
    type: 'object',
    'ui:labelWidth': 0,
    properties: {
      name: {
        'ui:label': '步骤名称'
      },
      filename: {
        ui: {
          column: 12,
          label: '文件名称'
        }
      },
      test: {
        'ui:label': 'render by jsx',
        default: true,
        ui: {
          column: 12,
          labelWidth: '100px',
          render: (h, context) => {
            const props = context.props || {}
            const listeners = context.listeners || {}
            return (
              <el-checkbox value={props.value} vOn:input={value => listeners.input(value)} />
            )
          }
        }
      }
    }
  }
}