【项目实战篇】Element Form表单实践(下)

3,010 阅读9分钟

作者:小土豆biubiubiu
博客园:www.cnblogs.com/HouJiao/
掘金:juejin.im/user/243617…

前言

上一篇文章 Element Form表单实践(上)参照着文档将表单部分内容实践了一下。

这篇文章将分享项目开发中的一个表单实践,最终做出来的效果大致是下面这个样子:

这个表单看似是比较简单的,但实际上比一般表单存在一些细节的东西需要设计和处理。

大佬可以绕过,文章本身没有很难的技术 接下来就来完成这个功能。

主页面

首先是主页面的实现。

主页面的逻辑非常简单,直接将代码贴出来。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Element Form表单实践</title>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    
</head>
<body>
    <div id="box">
        <el-form 
            label-suffix=":" 
            :model="form"
            label-width="80px"
            ref="form">
            <el-form-item 
                label="名称" 
                prop="name"
                :rules="[{ required:true, trigger: 'blur', message: '名称是必填项' }]">
                <el-input v-model="form.name"></el-input>
            </el-form-item>
            <el-form-item label="选项">
                <el-switch v-model="form.item"></el-switch>
                <el-button :disabled="!form.item" type="primary" size="small">详细配置</el-button>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" size="small" @click="saveInfo">保存</el-button>
            </el-form-item>
        </el-form>   
    </div>
    <script>
        var vm = new Vue({
            el: '#box',
            data: {
                form: {
                    name: "",
                    item: false
                }
            },
            methods: {
                saveInfo() {
                    this.$refs['form'].validate((valid,failedInfo) => {
                        if(valid){
                            // 提示用户
                            this.$message({
                                message: '保存成功',
                                type: "success",
                                center: true
                            });
                        }else{
                            return false;
                       }
                    })
                }
            }
        })
    </script>
</body>
</html>

这段代码中的内容都是上一篇文章中实践过的,没有什么特别需要说明的点。

详细配置页面

简单实现

详细配置页面实际上也是一个表单,我们先来把界面中需要展示的组件画出来。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Element Form表单实践</title>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    
</head>
<body>
    <div id="box">
        <el-form 
            label-suffix=":" 
            :model="form"
            label-width="80px"
            ref="form">
            <el-form-item 
                label="名称" 
                prop="name"
                :rules="[{ required:true, trigger: 'blur', message: '名称是必填项' }]">
                <el-input v-model="form.name"></el-input>
            </el-form-item>
            <el-form-item label="选项">
                <el-switch v-model="form.item"></el-switch>
                <el-button :disabled="!form.item" type="primary" size="small"  @click="modelVisible=true">详细配置</el-button>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" size="small" @click="saveInfo">保存</el-button>
            </el-form-item>
        </el-form> 
         <!-- 详细配置 -->
        <el-dialog 
            title="详细配置"
            :visible.sync="modelVisible">
            <el-form ref="detailForm">
                <el-form-item>
                    <el-checkbox label="personalInfo">个人信息</el-checkbox>
                </el-form-item>
                <el-form-item label="年龄">
                    <el-input></el-input>
                </el-form-item>
                <el-form-item label="身高">
                    <el-input></el-input>
                </el-form-item>              
                <el-form-item>
                    <el-checkbox label="addressInfo">住址信息</el-checkbox>
                </el-form-item>
                <el-form-item label="省份">
                    <el-input></el-input>
                </el-form-item>
                <el-form-item label="城市">
                    <el-input></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary">保存</el-button>
                    <el-button type="primary">重置</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>  
    </div>
    <script>
        var vm = new Vue({
            el: '#box',
            data: {
                modelVisible: false,
                form: {
                    name: "",
                    item: false
                }
            },
            methods: {
                saveInfo() {
                    this.$refs['form'].validate((valid,failedInfo) => {
                        if(valid){
                            // 提示用户
                            this.$message({
                                message: '保存成功',
                                type: "success",
                                center: true
                            });
                        }else{
                            return false;
                       }
                    })
                }
            }
        })
    </script>
</body>
</html>

上面这段代码主要添加了两个逻辑:弹窗组件和弹窗内部的表单组件

弹窗组件

弹窗组件使用的是elementdialog来实现。

主要的逻辑包含定义弹窗是否显示的data数据modelVisible、点击详细配置设置弹框可见以及弹窗组件的使用。

定义弹窗是否显示的data数据modelVisible:

var vm = new Vue({
    data: {
        // 弹窗是否显示
        modelVisible: false
    }
})

点击详细配置设置弹框可见:

<!-- 点击按钮设置弹窗可见 -->
 <el-button :disabled="!form.item" type="primary" size="small"  @click="modelVisible=true">详细配置</el-button>

弹框组件使用:

<el-dialog 
        title="详细配置"
        :visible.sync="modelVisible">
        <!-- 省略表单代码 -->
</el-dialog>        

弹窗组件的实现和使用非常简单,没有特别需要说明的点。

最后在看一下效果。

表单组件

表单组件的代码如下:

<el-form
    ref="detailForm">
    <el-form-item>
        <el-checkbox label="personalInfo">个人信息</el-checkbox>
    </el-form-item>
    <el-form-item label="年龄">
        <el-input></el-input>
    </el-form-item>
    <el-form-item label="身高">
        <el-input></el-input>
    </el-form-item>              
    <el-form-item>
        <el-checkbox label="addressInfo">住址信息</el-checkbox>
    </el-form-item>
    <el-form-item label="省份">
        <el-input></el-input>
    </el-form-item>
    <el-form-item label="城市">
        <el-input></el-input>
    </el-form-item>
    <el-form-item>
        <el-button type="primary">保存</el-button>
        <el-button type="primary">重置</el-button>
    </el-form-item>
</el-form>

可以看到,表单组件的代码非常的简单,前一篇文章中实践的内容这里都还没有添加。

那接下来结合本节需要实现的这个功能添加上一节中实践过的内容。

表单添加model属性

首先第一个最重要的就是表单的model属性,也就是表单绑定的数据。

这里我们先定义一个简单的表单数据

detailConfigForm: {
    personalInfo: false, // 个人信息
    age:'',              // 年龄
    height: '',          // 身高
    addressInfo: true,  // 住址信息
    province: '',        // 省份
    city: ''             // 城市
}

然后将该数据绑定到表单上,同时为表单项(el-form-item)添加modelprop属性。

 <el-form
    ref="detailForm"
    label-width="80px"
    :model="detailConfigForm">
    <el-form-item prop="personalInfo" >
        <el-checkbox
        label="personalInfo"
        v-model="detailConfigForm.personalInfo">个人信息</el-checkbox>
    </el-form-item>
    <el-form-item label="年龄" prop="age">
        <el-input v-model="detailConfigForm.age"></el-input>
    </el-form-item>
    <el-form-item label="身高" prop="height">
        <el-input v-model="detailConfigForm.height"></el-input>
    </el-form-item>              
    <el-form-item prop="addressInfo">
        <el-checkbox
        label="addressInfo" 
        v-model="detailConfigForm.addressInfo">住址信息</el-checkbox>
    </el-form-item>
    <el-form-item label="省份" prop="province">
        <el-input v-model="detailConfigForm.province"></el-input>
    </el-form-item>
    <el-form-item label="城市" prop="city">
        <el-input v-model="detailConfigForm.city"></el-input>
    </el-form-item>
    <el-form-item>
        <el-button type="primary">保存</el-button>
        <el-button type="primary">重置</el-button>
    </el-form-item>
</el-form>

完成后,此时在表单中填写内容已经没有问题了。

个人信息/住址信息启用禁用

接着需要实现的功能是:将个人信息/住址信息当做一个开关选中时对应模块的控件启动,可以正常填写内容;取消选中时,对应的模块控件禁用,且清空上一次填写的内容。

那我们知道表单项设置disabled值就可以实现。

根据前面描述禁用启用的逻辑,可以发现个人信息/住址信息启用禁用表单禁用启用刚好是相反的逻辑。

所以目前实现的思路就是:将detailConfigForm.personalInfo的值取反绑定在年龄身高控件的disabled属性上;将detailConfigForm.addressInfo的值取反绑定在省份城市控件的disabled属性上。

这里我们只将个人信息部分的逻辑实现代码贴出来

<el-form-item label="年龄" prop="age">
    <el-input v-model="detailConfigForm.age" :disabled="!detailConfigForm.personalInfo"></el-input>
</el-form-item>
<el-form-item label="身高" prop="height">
    <el-input v-model="detailConfigForm.height" :disabled="!detailConfigForm.personalInfo"></el-input>
</el-form-item>

在来看下效果。

地址信息这部分的禁用启用逻辑和个人信息是相同的,这里不在多说

取消启用清空对应控件中填写的内容

那接下来要实现的功能就是取消启用清空对应控件中填写的内容

方式一:手动赋空值

上一节的 Element Form表单实践(上) 中说过表单的resetFileds方法可以重置表单。

this.refs['formName'].resetFields()

不过该方法会重置表单中的所有属性,所以说不太符合我们的要求。我们只需要重置部分表单:即个人信息取消启用时,只需要清空年龄身高这两个内容即可。

解决这个问题的思路之一就是放弃使用resetFields方法,直接给表单数据赋空值从而清空表单内容

清空表单内容这个操作是在个人信息启用和禁用的时候执行的,即在detailConfigForm.personalInfo值发生变化时执行的,那这个很自然的就会想到使用vue watch 属性监听detailConfigForm.personalInfo的变化,在该值为false的时候,给表单数据赋值为空,实现清空表单内容

watch: {
    'detailConfigForm.personalInfo': function(val){
        if(val == false){
            this.detailConfigForm.age = "";
            this.detailConfigForm.height = "";
        }
    }
},

然而在真正的项目实践中,当取消启用个人信息时,需要清空的表单数量不止两个,而是有多个,所以作者就放弃了这种手动赋值清空的方式。

放弃这种方式的原因还有一个,就是表单的验证也会存在问题。

表单填写完成后,点击提交,假如个人信息没有启用,那验证时就不需要对年龄和身高进行验证,而表单的验证方法validate是对整个表单进行校验的方法。

这几个因素是我放弃手动赋值清空方式的重要原因。

方式二:resetFileds

放弃手动赋值清空表单的这种方式后,我又回归到了表单的resetFields方法。既然还想使用resetFields方法,唯一的办法就是做一个表单嵌套

这样当个人信息取消启用时,就可以调用this.refs['personalInfoForm'].resetFields()重置个人信息这部分的表单内容。而在整个表单提交验证的时候,也可以分别调用this.refs['personalInfoForm'].validate()this.refs['addressInfoForm'].validate()分开进行验证。

修改数据结构

那这种实现思路的第一步就是将表单的数据结构进行修改。

detailConfig: {
    personalInfoConfig:{
        personalInfo: false,       // 个人信息
        age: '',                   // 个人信息-年龄
        height: '',                // 个人信息-年龄
    },
    addressInfoConfig:{
        addressInfo: false,        // 地址信息
        province: '',              // 地址信息-省份
        city: ''                   // 地址信息-城市
    }
}
重写表单代码

接下来就需要根据这样的数据结构将el-form表单的代码进行重写。

<el-form
    :model="detailConfig"
    ref="detailForm">
    <!-- 个人信息 -->
    <!-- el-form的model绑定detailConfig.personalInfoConfig -->
    <el-form
    :model="detailConfig.personalInfoConfig"
    label-width="80px"
    lable-suffix=":"
    ref="personalInfoForm">
        <el-form-item prop="personalInfo">
            <el-checkbox label="personalInfo" v-model="detailConfig.personalInfoConfig.personalInfo">个人信息</el-checkbox>
        </el-form-item>
        <el-form-item label="年龄" prop="age">
            <el-input 
                v-model.number="detailConfig.personalInfoConfig.age"
                :disabled="!detailConfig.personalInfoConfig.personalInfo"></el-input>
        </el-form-item>
        <el-form-item label="身高" prop="height">
            <el-input 
                v-model.number="detailConfig.personalInfoConfig.height"
                :disabled="!detailConfig.personalInfoConfig.personalInfo"></el-input>
        </el-form-item>
    </el-form>
    
    <!-- 住址信息 -->
    <!-- el-form的model绑定detailConfig.addressInfoConfig -->
    <el-form
    :model="detailConfig.addressInfoConfig"
    label-width="80px"
    lable-suffix=":"
    ref="addressInfoForm">
    <el-form-item prop="addressInfo">
        <el-checkbox 
            v-model="detailConfig.addressInfoConfig.addressInfo" 
            label="addressInfo">住址信息</el-checkbox>
    </el-form-item>
    <el-form-item label="省份" prop="province">
        <el-input 
            v-model="detailConfig.addressInfoConfig.province"
            :disabled="!detailConfig.addressInfoConfig.addressInfo"></el-input>
    </el-form-item>
    <el-form-item label="城市" prop="city">
        <el-input 
            v-model="detailConfig.addressInfoConfig.city"
            :disabled="!detailConfig.addressInfoConfig.addressInfo"></el-input>
    </el-form-item>
    <el-form-item>
        <el-button type="primary">保存</el-button>
        <el-button type="primary">重置</el-button>
    </el-form-item>
</el-form>
使用resetFileds

接着在修改一下watch代码。

watch:{
    "detailConfig.personalInfoConfig.personalInfo": function(personalInfo){
        if(personalInfo == false){
            this.$refs['personalInfoForm'].resetFields()
        }
    },
    "detailConfig.addressInfoConfig.addressInfo": function(addressInfo){
        if(addressInfo == false){
            this.$refs['addressInfoForm'].resetFields()
        }
    }
},

可以看到watch代码内部就可以直接使用表单的resetFields方法,对个人信息住址信息分开进行清空。

最终的结果和手动赋值清空是一样的,这里不在演示。

表单验证

最后一个就是表单的验证了。

rules

首先我们需要编写表单的rules验证规则。

detailConfig: {
    personalInfoConfig:{
        personalInfo: false,
        age: '',
        height: '',
        rules: {
            age: [{
                type: 'number',
                message: '年龄必须为数字值'
            }],
            height: [{
                type: 'number',
                message: '身高必须为数字值'
            }]
        }
    },
    addressInfoConfig:{
        addressInfo: false,
        province: '',
        city: '',
        rules: {
            province: [
                { min: 2, max: 10, message: '长度必须在2-10个字符'}
            ],
            city: [
                { min: 2, max: 10, message: '长度必须在2-10个字符' }
            ]
        }
    }
}

新增的验证规则如下:

年龄和身高:必须为数值;  
省份和城市:长度必须在2-10个字符。

使用validate

接着在保存按钮的click事件上绑定saveConfig方法。

<el-button type="primary" @click="saveConfig">保存</el-button>

接着编写saveConfig的逻辑。

需要说明的是,只有对应的按钮启用了,才会对对应启用的表单做验证

saveConfig(){
    // 如果个人信息启用,则需要对个人信息下的年龄、身高字段进行验证。
    if(this.detailConfig.personalInfoConfig.personalInfo){
        this.$refs['personalInfoForm'].validate((valid,failedInfo) => {
             // 个人信息下的年龄、身高字段进行验证通过。
            if(valid){
                // 判断地址信息是否启用,启用的话需要对地址信息下的城市、省份进行验证
                if(this.detailConfig.personalInfoConfig.personalInfo){
                    this.$refs['addressInfoForm'].validate((valid,failedInfo) => {
                        // 地址信息下的城市、省份验证成功。关闭dialog
                        if(valid){
                            this.modelVisible = false;
                        }else{
                            return false;
                        }
                    })
                }else{
                    this.modelVisible = false;
                }
            }else{
                return false;
            }
        })
    // 如果地址信息启用,则需要对地址信息下的省份、城市字段进行验证。    
    }else if(this.detailConfig.addressInfoConfig.addressInfo){
        this.$refs['addressInfoForm'].validate((valid,failedInfo) => {
            if(valid){
                this.modelVisible = false;
            }else{
                return false;
            }
        })  
    // 个人信息和地址信息均没有启用,直接关闭dialog          
    }else{
        this.modelVisible = false;
    }                    
},

这部分的逻辑比较繁琐,因为存在启用验证不启用就不验证的逻辑判断

完成后,最终的效果我们再来看一下。

重置表单

首先在页面上添加重置按钮,绑定事件。

<el-button type="primary" @click='resetForm'>重置</el-button>

接着就来使用表单的重置方法resetFileds来重置表单的内容。

那这里需要注意的一点就是我们的表单是嵌套表单。

直接调用外层表单resetFileds方法没有办法去重置表单内容。因此这里必须调用内层表单的resetFileds方法。

 resetForm(formName){
    this.$refs['personalInfoForm'].resetFields();
    this.$refs['addressInfoForm'].resetFields();
    // 调用外层表单的`resetFileds`方法没有办法去重置表单内容
    // this.$refs['detailForm'].resetFields();
}

表单重置这里就不贴演示结果了

功能优化和bug修复

到这里我们表单的大部分功能已经实现了:表单禁用启用表单禁用时清空表单内容表单验证表单重置

那接下来就需要对实现的这个功能进行在思考。

功能优化

第一个是功能优化。

回头看所有实现的功能,唯一觉得不太合适的地方就是表单的验证逻辑。

saveConfig(){
    // 如果个人信息启用,则需要对个人信息下的年龄、身高字段进行验证。
    if(this.detailConfig.personalInfoConfig.personalInfo){
        this.$refs['personalInfoForm'].validate((valid,failedInfo) => {
            // 个人信息下的年龄、身高字段进行验证通过。
            if(valid){
                // 判断地址信息是否启用,启用的话需要对地址信息下的城市、省份进行验证
                if(this.detailConfig.addressInfoConfig.addressInfo){
                   this.$refs['addressInfoForm'].validate((valid,failedInfo) => {
                        // 地址信息下的城市、省份验证成功。关闭dialog
                        if(valid){
                            this.modelVisible = false;
                        }else{
                            return false;
                        }
                    })
                }else{
                    this.modelVisible = false;
                }
            }else{
                return false;
            }
        })
    // 如果地址信息启用,则需要对地址信息下的省份、城市字段进行验证。    
    }else if(this.detailConfig.addressInfoConfig.addressInfo){
        this.$refs['addressInfoForm'].validate((valid,failedInfo) => {
            if(valid){
                this.modelVisible = false;
            }else{
                return false;
            }
        })  
    // 个人信息和地址信息均没有启用,直接关闭dialog          
    }else{
        this.modelVisible = false;
    }                    
}

可以看到这里有多层嵌套的逻辑判断。

那这个验证功能无非就是希望当前不选中那一项就不验证那一项,那能不能将校验规则定义为动态的,不选中时移除校验规则,选中时添加上校验规则。

那么答案是可以的,所以接下来就来实现一下。

首先我们在data数据中定义多个规则。

personalInfoConfig:{
    personalInfo: false,
    age: '',
    height: '',
    rules: {
        age: [{
            type: 'number',
            message: '年龄必须为数字值'
        }],
        height: [{
            type: 'number',
            message: '身高必须为数字值'
        }]
    },
    // 定义空的验证规则
    emptyRules: {}
},
addressInfoConfig:{
    addressInfo: false,
    province: '',
    city: '',
    rules: {
        province: [
            { min: 2, max: 10, message: '长度必须在2-10个字符'}
        ],
        city: [
            { min: 2, max: 10, message: '长度必须在2-10个字符' }
        ]
    },
    // 定义空的验证规则
    emptyRules: {}
}

即一个正常的验证规则,对应复选框启用时的验证;还要一个空的验证规则,对应复选框取消启用时的验证。

然后我们将规则定义到计算属性中。

computed:{
    personalInfoRules: function(){
        if(this.detailConfig.personalInfoConfig.personalInfo == true){
            return this.detailConfig.personalInfoConfig.rules;
        }else{
            return this.detailConfig.personalInfoConfig.emptyRules;
        }
    },
    addressInfoRules: function(){
        if(this.detailConfig.addressInfoConfig.addressInfo == true){
            return this.detailConfig.addressInfoConfig.rules;
        }else{
            return this.detailConfig.addressInfoConfig.emptyRules;
        }
    }
},

接着就是将计算属性绑定到对应表单的rules属性上。

<!-- 个人信息 -->
<el-form
    :model="detailConfig.personalInfoConfig"
    label-width="80px"
    lable-suffix=":"
    ref="personalInfoForm"
    :rules="personalInfoRules">
    <!-- 省略 -->
</el-form>

<!-- 住址信息 -->
<el-form
    :model="detailConfig.personalInfoConfig"
    label-width="80px"
    lable-suffix=":"
    ref="personalInfoForm"
    :rules="personalInfoRules">
    <!-- 省略 -->
</el-form>

可以看到el-form上绑定的rules已经修改为computed中定义的属性了。

这样的改动完成之后,最后一步就是重写校验逻辑了。

 saveConfig(){
    this.$refs['personalInfoForm'].validate((valid,failedInfo) => {
        // 个人信息下的年龄、身高字段进行验证通过。
        if(valid){   
            this.$refs['addressInfoForm'].validate((valid,failedInfo) => {
                // 地址信息下的城市、省份验证成功,关闭dialog
                if(valid){
                    this.modelVisible = false;
                }else{
                    return false;
                }
            })   
        }else{
            return false;
        }
    })        
},

因为规则在动态的变化,而验证的逻辑就不需要复选框的启用禁用进行判断,直接使用规则进行验证即可。所以的验证逻辑是不是就清爽了很多。

那这个就是针对表单验证做的一个小小的优化。 如果大家有更好的方法可以分享给我

bug修复

在功能测试的过程中,我还发现一个问题。

当我在表单中填写了错误格式的数据后,直接通过点击弹窗上方的叉号按钮来关闭dialog(不点击保存按钮),那此时detailConfig中的字段值已经是那个错误格式的数据(双向数据绑定原理),如果直接将最终的detailConfig发送到后端显然是不对的。

目前暂时还没有一个好的解决思路,正在思考中,欢迎大家和我交流。

完整代码

最后我将本次实践的完整代码贴在这里。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Element Form表单实践</title>
    <!-- 开发环境版本,包含了有帮助的命令行警告 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    
</head>
<body>
    <div id="box">
        <el-form 
            label-suffix=":" 
            :model="form"
            label-width="80px"
            ref="form">
            <el-form-item 
                label="名称" 
                prop="name"
                :rules="[{ required:true, trigger: 'blur', message: '名称是必填项' }]">
                <el-input v-model="form.name"></el-input>
            </el-form-item>
            <el-form-item label="选项">
                <el-switch v-model="form.item"></el-switch>
                <el-button :disabled="!form.item" type="primary" size="small" @click="modelVisible=true">详细配置</el-button>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" size="small" @click="saveInfo">保存</el-button>
            </el-form-item>
        </el-form>   
        
        <!-- 详细配置 -->
        <el-dialog 
            title="详细配置"
            :visible.sync="modelVisible">
            <el-form
                :model="detailConfig"
                ref="detailForm">
                <el-form
                    :model="detailConfig.personalInfoConfig"
                    label-width="80px"
                    lable-suffix=":"
                    ref="personalInfoForm"
                    :rules="personalInfoRules">
                    <el-form-item prop="personalInfo">
                        <el-checkbox label="personalInfo" v-model="detailConfig.personalInfoConfig.personalInfo">个人信息</el-checkbox>
                    </el-form-item>
                    <el-form-item label="年龄" prop="age">
                        <el-input 
                            v-model.number="detailConfig.personalInfoConfig.age"
                            :disabled="!detailConfig.personalInfoConfig.personalInfo"></el-input>
                    </el-form-item>
                    <el-form-item label="身高" prop="height">
                        <el-input 
                            v-model.number="detailConfig.personalInfoConfig.height"
                            :disabled="!detailConfig.personalInfoConfig.personalInfo"></el-input>
                    </el-form-item>
                </el-form>
                <el-form
                    :model="detailConfig.addressInfoConfig"
                    label-width="80px"
                    lable-suffix=":"
                    ref="addressInfoForm"
                    :rules="addressInfoRules">
                    <el-form-item prop="addressInfo">
                        <el-checkbox 
                            v-model="detailConfig.addressInfoConfig.addressInfo" 
                            label="addressInfo">住址信息</el-checkbox>
                    </el-form-item>
                    <el-form-item label="省份" prop="province">
                        <el-input 
                            v-model="detailConfig.addressInfoConfig.province"
                            :disabled="!detailConfig.addressInfoConfig.addressInfo"></el-input>
                    </el-form-item>
                    <el-form-item label="城市" prop="city">
                        <el-input 
                            v-model="detailConfig.addressInfoConfig.city"
                            :disabled="!detailConfig.addressInfoConfig.addressInfo"></el-input>
                    </el-form-item>
                </el-form>
                <el-form-item>
                    <el-button type="primary" @click="saveConfig">保存</el-button>
                    <el-button type="primary" @click='resetForm'>重置</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>
    </div>
    <script>
        var vm = new Vue({
            el: '#box',
            computed:{
                personalInfoRules: function(){
                    if(this.detailConfig.personalInfoConfig.personalInfo == true){
                        return this.detailConfig.personalInfoConfig.rules;
                    }else{
                        return this.detailConfig.personalInfoConfig.emptyRules;
                    }
                },
                addressInfoRules: function(){
                    if(this.detailConfig.addressInfoConfig.addressInfo == true){
                        return this.detailConfig.addressInfoConfig.rules;
                    }else{
                        return this.detailConfig.addressInfoConfig.emptyRules;
                    }
                }
            },
            watch:{
                "detailConfig.personalInfoConfig.personalInfo": function(personalInfo){
                    if(personalInfo == false){
                        this.$refs['personalInfoForm'].resetFields()
                    }
                },
                "detailConfig.addressInfoConfig.addressInfo": function(addressInfo){
                    if(addressInfo == false){
                        this.$refs['addressInfoForm'].resetFields()
                    }
                }
            },
            data: {
                form: {
                    name: "",
                    item: false
                },
                modelVisible: false,
                detailConfig: {
                    personalInfoConfig:{
                        personalInfo: false,
                        age: '',
                        height: '',
                        rules: {
                            age: [{
                                type: 'number',
                                message: '年龄必须为数字值'
                            }],
                            height: [{
                                type: 'number',
                                message: '身高必须为数字值'
                            }]
                        },
                        // 定义空的验证规则
                        emptyRules: {}
                    },
                    addressInfoConfig:{
                        addressInfo: false,
                        province: '',
                        city: '',
                        rules: {
                            province: [
                                { min: 2, max: 10, message: '长度必须在2-10个字符'}
                            ],
                            city: [
                                { min: 2, max: 10, message: '长度必须在2-10个字符' }
                            ]
                        },
                        // 定义空的验证规则
                        emptyRules: {}
                    }
                }
            },
            methods: {
                saveInfo() {
                    this.$refs['form'].validate((valid,failedInfo) => {
                        if(valid){

                            // 将表单数据组合到一起
                            // 这样方式比较简单,不过会将数据中的rules传递到后端
                            let data = {
                                ...this.detailConfig,
                                ...this.form
                            }

                            // 将数据发送到后端

                            // 代码省略......

                            // 数据保存成功提示用户
                            this.$message({
                                message: '保存成功',
                                type: "success",
                                center: true
                            });

                        }else{
                            return false;
                       }
                    })
                },
                saveConfig(){
                    this.$refs['personalInfoForm'].validate((valid,failedInfo) => {
                        // 个人信息下的年龄、身高字段进行验证通过。
                        if(valid){   
                            this.$refs['addressInfoForm'].validate((valid,failedInfo) => {
                                // 地址信息下的城市、省份验证成功。关闭dialog
                                if(valid){
                                    this.modelVisible = false;
                                }else{
                                    return false;
                                }
                            })   
                        }else{
                            return false;
                        }
                    })        
                },
                resetForm(formName){
                    this.$refs['personalInfoForm'].resetFields();
                    this.$refs['addressInfoForm'].resetFields();
                    // 调用外层表单的`resetFileds`方法没有办法去重置表单内容
                    // this.$refs['detailForm'].resetFields();
                }
            }
        })
    </script>
</body>
</html>

注意在主页面保存整个表单内容是,我使用ES6的展开运算符将主页面的表单数据和弹框组件内的表单数据合并到了一起,这样就可以直接将合并后的数据发送到后端。 使用展开运算符合并数据虽然比较方便,但是定义的验证规则数据也会包含在最终的结果中。

写在最后

作者实现的这个功能是在一个本来完整的表单提交功能上新增的一个小功能。当时已经完成的表单的数据结构是根据业务和逻辑设计的多层嵌套字典,所以后面我新增的这个数据结构也是嵌套在字典里层的。

// 这个数据是根据业务逻辑设计的多层嵌套字典
people{
    // 这里还有别的表单的数据
    
    // config是我新增的表单数据
    config:{
        icmpconfig:{
            time:10
        },
        tcpconfig:{
            time:10
        }
    }
}

但是刚一开始我并没有做表单嵌套,而是在外层使用单个的el-form实现。

到后面做验证添加rules的时候,prop的值就得写成people.icmpconfig.time,但实际是prop是不能写成这样.的形式,写了之后会报错说time没有定义。

介于这个原因,在综合前面的说法:

是一整个使用表单嵌套的原因。

使用表单嵌套感觉有利也有弊,方便了一些逻辑,也带来了一些问题。

所以一定要提前设计好,选择一个合理的实现方式。

作者寄语

小小总结,欢迎大家指导~

作者:小土豆biubiubiu
博客园:www.cnblogs.com/HouJiao/
掘金:juejin.im/user/243617…