VUE + ElementUI 复杂表单的验证、数据提交方案

背景

当我们在做后台管理系统时,经常会遇到非常复杂的表单:

  • 表单项非常多
  • 在各种表单类型下,显示不同的表单项
  • 在某些条件下,某些表单项会关闭验证
  • 每个表单项还会有其他自定义逻辑,比如输入框可以插入模板变量、输入字符数量显示、图片上传并显示、富文本。。。
  • 在这种错综复杂的情况下,完成表单的验证和提交
  • 可以查看具体例子:例子中省略了很多琐碎的功能,只保留整体的复杂表单框架,用于展示解决方案

方案1:在一个 vue 文件中

所有的表单项显示隐藏、验证、数据获取、提交、自定义等逻辑放在一起;

  • 根据表单类型,使用 v-if/v-show 处理表单项显示隐藏
  • elementui 自定义验证中,根据表单类型,判断表单项是否验证
  • 根据表单类型,获取不同的数据,并提交到不同的接口
  • 其余所有的自定义逻辑

缺点:

乱!乱!还是乱!

一个 vue 文件,轻轻松松上2000行,在我尝试加入一种新的表单类型时,我发现我已经无。从。下。手。

方案2:分离组件

其实很容易想到根据不同的表单类型,分离出多个相应类型的子表单。但我在实践时还是遇到了很多问题:父子表单验证、整体提交数据的获取等等,并总结出一套解决方案:

子组件

所有的子组件中都需要包含两个方法 validategetData 供父组件调用。

  • validate 方法

用于验证本身组件的表单项,并返回一个 promise 对象:

validate() {
    // 返回 ElementUI 表单验证的结果(为 promise 对象)
    return this.$refs['ruleForm'].validate();
},
  • getData 方法

提供子组件中的数据

getData() {
    // 返回子组件的 form
    return this.ruleForm;
},

父组件

  • 策略模式

使用策略模式存储并获取子表单的 ref (用于获取子表单的方法)和提交函数,省略了大量的 if-else 判断。

data:{
    // type 和 ref 名称的映射
    typeMap: {
        1: 'message',
        2: 'mail',
        3: 'apppush'
    },
    // type 和提交函数的映射;不同类型,接口可能不同
    fakeSubmit: {
        1: data => alert(`短信模板创建成功${JSON.stringify(data)}`),
        2: data => alert(`邮件模板创建成功${JSON.stringify(data)}`),
        3: data => alert(`push模板创建成功${JSON.stringify(data)}`)
    },
}
  • submit 方法

用于父子组件表单验证、获取整体数据、调用当前类型提交函数提交数据。

因为 ElementUI 表单验证的 validate 方法可以返回 promise 结果,可以利用 promise 的特性来处理父子表单的验证。

// 验证方法一:父表单验证通过才会验证子表单,存在先后顺序
submitForm() {
    const templateType = this.typeMap[this.ruleForm.type];

    this.$refs['ruleForm'].validate().then(res => {
        // 父表单验证成功后,验证子表单
        return this.$refs[templateType].validate();
    }).then(res => {
        // 全部验证通过
        // 获取整体数据
        const reqData = {
            // 获取子组件数据
            ...this.$refs[templateType].getData(),
            ...this.ruleForm
        };

        // 获取当前表单类型的提交函数,并提交
        this.fakeSubmit[this.ruleForm.type](reqData);
    }).catch(err => {
        console.log(err);
    });
},

// 验证方法二:父表单,子表单一起验证
submitForm1() {
    const templateType = this.typeMap[this.ruleForm.type];
    const validate1 = this.$refs['ruleForm'].validate();
    const validate2 = this.$refs[templateType].validate();

    // 父子表单一起验证
    Promise.all([validate1, validate2]).then(res => {
        // 都通过时,发送请求
        const reqData = {
            ...this.$refs[templateType].getData(),
            ...this.ruleForm
        };

        this.fakeSubmit[this.ruleForm.type](reqData);
    }).catch(err => {
        console.log(err);
    });
}

总结

很多项目我都遇到这种复杂的表单,也用了很多种解决方案,在此总结出了一种比较整洁简便的方案。当然还有其他很多方案,比如可以把数据提交的方法放在每一个子组件中,公共的表单项数据通过props传递给子组件用于提交。有其他更加简洁的方案,欢迎评论,或者 github 上提 issue

查看在线项目项目 Github组件代码

[原文]:https://www.jianshu.com/p/2e054d13a1e2

发表评论
* 昵称
* Email
* 网址
* 评论