vue+element大型表单解决方案(2)–表单拆分

系列文章:

  • vue+element大型表单解决方案(1)–概览

前言

从这篇开始,我就逐步去实现概览中演示的表单效果。平时我们写表单时,都是一个表单写一个组件,保存时使用el-form提供的validate方法进行校验,当任何一个表单项校验失败时,阻止表单提交;但是当表单项足够多时,一个表单文件显然是有问题的:首先是单个文件的代码量会很庞大,不利于后期维护;更严重的问题是这个任务很难同时安排多人并行开发,当多人写一个文件时,很容易产生冲突,从而无法达到加人缩短工期的目标。基于这些问题,我尝试了下面的表单分解方案。

整体思路

我的思路是,将表单的内容分解成多个子表单组件,最后在总表单组件里对这些子表单进行组装;提交的时候,依次对子表单进行校验,全部通过校验后,在总表单组装数据进行最后的提交。

一次提交两个表单

首先我要尝试下点击保存按钮时怎么提交两个el-form,代码如下:
template部分

<el-button type="primary" @click="handleSave">保存</el-button>
<el-form ref="form1" :model="form1" :rules="rules1" label-width="80px" size="small">
  <el-form-item label="姓名" prop="name">
    <el-input v-model="form1.name" />
  </el-form-item>
  <el-form-item label="年龄">
    <el-input v-model="form1.age" />
  </el-form-item>
</el-form>
<el-form ref="form2" :model="form2" :rules="rules2" label-width="80px" size="small">
  <el-form-item label="公司" prop="company">
    <el-input v-model="form2.company" />
  </el-form-item>
</el-form>
 

data部分

data() {
    return {
      form1: {
        name: '',
        age: ''
      },
      form2: {
        company: ''
      },
      rules1: {
        name: [
          { required: true, message: '请输入姓名', trigger: 'blur' }
        ]
      },
      rules2: {
        company: [
          { required: true, message: '请输入公司', trigger: 'blur' }
        ]
      }
    }
},
 

点击保存时,对两个表单分别进行验证,都通过时,才打印数据。代码如下:

handleSave() {
  let validResult1
  let validResult2
  this.$refs['form1'].validate(valid => { validResult1 = valid })
  this.$refs['form2'].validate(valid => { validResult2 = valid })
  if (validResult1 && validResult2) {
    // 校验通过,打印表单数据
    console.log(this.form1, this.form2)
  } else {
    this.$message.warning('校验未通过')
  }
}
 

此时试验下运行效果,当有必填项未填写时

image.png

当全部校验通过时,打印出数据

image.png

到现在为止,一切顺利,也没什么新奇的地方,只是简单地对多个表单进行校验,当全部通过时,打印数据,允许提交,否则提示校验失败。

抽取子表单组件

既然方向可行,下面开始搭建子表单组件,分割代码。下面是其中一个子表单的代码

<template>
    <el-form ref="form" :model="formData" :rules="rules" label-width="80px" size="small">
        <el-form-item label="姓名" prop="name">
          <el-input v-model="formData.name" />
        </el-form-item>
        <el-form-item label="年龄">
          <el-input v-model="formData.age" />
        </el-form-item>
    </el-form>
</template>
 

js部分代码如下:

export default {
  name: 'Form1',
  data() {
    return {
      formData: {},
      rules: {
        name: [
          { required: true, message: '请输入姓名', trigger: 'blur' }
        ]
      }
    }
  },
  methods: {
    validForm() {
      let result = false
      this.$refs['form'].validate((valid) => { result = valid })
      return result
    }
  }
}
 

同理实现form2.vue文件。
此时修改主表单文件index.vue

<template>
  <div class="form-wrapper">
    <el-button type="primary" @click="handleSave">保存</el-button>
    <form1 ref="form1" />
    <form2 ref="form2" />
  </div>
</template>
 

js部分

import Form1 from './form1'
import Form2 from './form2'
export default {
  name: 'TheForm',
  components: {
    Form1,
    Form2
  },
  data() {
      return {
      }
  },
  methods: {
    handleSave() {
      const validResult1 = this.$refs['form1'].validForm()
      const validResult2 = this.$refs['form2'].validForm()
      if (validResult1 && validResult2) {
        // 校验通过,打印表单数据
        console.log(this.$refs['form1'].formData)
        console.log(this.$refs['form2'].formData)
      } else {
        this.$message.warning('校验未通过')
      }
    }
  }
}
 

上面的代码较之前的一个变化是不再直接操作el-form,而是通过自定义表单组件提供的接口(validForm方法和formData数据)来调用方法和获取数据。

测试改造后的效果如下:

校验未通过

image.png

校验通过,打印数据

image.png

增强可扩展性

上面的代码中,每个form都要定义一个validResult变量记录表单校验结果,显然这是没有可扩展性的。鉴于各个子表单的接口是设计成一致的,那就可以采用遍历的方式进行无差别扩展。那么遍历的key值怎么维护呢?而且上面的代码中,子表单组件并没有接受初始数据进行回显的能力,自然而然的想到使用一个formDataMap对象将各子表单的数据进行统一管理,并可以使用formDataMap的属性作为key值。

改造index.vue文件的data部分,新增如下代码:

formDataMap: {
    form1: {
          // 如果有初始值则单独罗列出来,否则空着即可
    },
    form2: {}
}
 

由于formDataMap里的属性名form1form2分别用在子表单组件的ref上,因此可以在遍历时依次找到他们,修改保存函数,代码如下:

handleSave() {
      // 解析出全部表单key值,通过key值获取组件ref
      const formKeys = Object.keys(this.formDataMap)
      const validResults = formKeys.map(formKey => this.$refs[formKey].validForm())
      // 如果所有校验通过
      if (validResults.every(r => r)) {
        // 校验通过,打印表单数据,这里不再单个输出子表单里的数据,
        // 而是组装成完正的表单数据
        const formData = {}
        formKeys.map(formKey => {
          const partFormData = this.$refs[formKey].formData
          Object.assign(formData, partFormData)
        })
        console.log(formData)
      } else {
        this.$message.warning('校验未通过')
      }
    }
  }
 

测试下效果如下:

校验未通过

image.png

校验通过,打印数据

image.png

子表单接受初始数据

在上面总表单中已经定义了formDataMap,但是并没有传递给子表单,下面开始传参

 <form1 ref="form1" :data="formDataMap.form1" />
 <form2 ref="form2" :data="formDataMap.form2" />
 
formDataMap: {
    form1: {
      name: 'wyh',
      age: 30
    },
    form2: {}
}
 

进入form1.vue文件中,接受props

props: {
    data: {
      type: Object,
      default: () => ({})
    }
},
 

将传入的data属性本地化成formData
需要使用监听,并且使用immediate属性,能立刻将传入的data本地化成formData

watch: {
   data: {
      handler(newValue) {
        this.formData = easyClone(newValue) || {}
      },
      immediate: true
    }
},
 

测试后一切正常。

抽取mixin

鉴于各个子表单组件的逻辑都差不多,form1.vue里的propswatchmethods很多都要在form2.vue文件中写一遍,而且今后还有无数的这种表单要去写,显然要使用mixin进行代码抽取
抽取的mixin代码如下:

import { easyClone } from '@/utils'
export default {
  props: {
    data: {
      type: Object,
      default: () => ({})
    }
  },
  data() {
    return {
      formData: {}
    }
  },
  watch: {
    data: {
      handler(newValue) {
        this.formData = easyClone(newValue) || {}
      },
      immediate: true
    }
  },
  methods: {
    validForm() {
      let result = false
      this.$refs['form'].validate((valid) => { result = valid })
      return result
    }
  }
}
 

抽取后,form1.vue变得极其简单,只保留了rules

import SuperFormMixin from '@/mixins/super-form-mixin'
export default {
  name: 'Form1',
  mixins: [SuperFormMixin],
  data() {
    return {
      rules: {
        name: [
          { required: true, message: '请输入姓名', trigger: 'blur' }
        ]
      }
    }
  }
}
 

测试后一切正常,到此为止,表单拆分的基本方向和实现已经出来了,后面就是填充复杂表单逻辑的工作了。下面要面对的难题是,怎么实现大表单的辅助工具锚点呢?请继续关注。
谢谢您的阅读,欢迎提出指正意见!

(3)
上一篇 2021年5月26日 下午6:00
下一篇 2021年5月26日 下午6:15

相关推荐

发表回复

登录后才能评论