系列文章:
- 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('校验未通过')
}
}
此时试验下运行效果,当有必填项未填写时
当全部校验通过时,打印出数据
到现在为止,一切顺利,也没什么新奇的地方,只是简单地对多个表单进行校验,当全部通过时,打印数据,允许提交,否则提示校验失败。
抽取子表单组件
既然方向可行,下面开始搭建子表单组件,分割代码。下面是其中一个子表单的代码
<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
数据)来调用方法和获取数据。
测试改造后的效果如下:
校验未通过
校验通过,打印数据
增强可扩展性
上面的代码中,每个form都要定义一个validResult
变量记录表单校验结果,显然这是没有可扩展性的。鉴于各个子表单的接口是设计成一致的,那就可以采用遍历的方式进行无差别扩展。那么遍历的key值怎么维护呢?而且上面的代码中,子表单组件并没有接受初始数据进行回显的能力,自然而然的想到使用一个formDataMap
对象将各子表单的数据进行统一管理,并可以使用formDataMap
的属性作为key值。
改造index.vue文件的data部分,新增如下代码:
formDataMap: {
form1: {
// 如果有初始值则单独罗列出来,否则空着即可
},
form2: {}
}
由于formDataMap
里的属性名form1
和form2
分别用在子表单组件的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('校验未通过')
}
}
}
测试下效果如下:
校验未通过
校验通过,打印数据
子表单接受初始数据
在上面总表单中已经定义了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里的props
、watch
、methods
很多都要在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' }
]
}
}
}
}
测试后一切正常,到此为止,表单拆分的基本方向和实现已经出来了,后面就是填充复杂表单逻辑的工作了。下面要面对的难题是,怎么实现大表单的辅助工具锚点呢?请继续关注。
谢谢您的阅读,欢迎提出指正意见!