Element Form表單實踐(下)

  • 2020 年 5 月 28 日
  • 筆記

作者:小土豆biubiubiu

部落格園://www.cnblogs.com/HouJiao/

掘金://juejin.im/user/58c61b4361ff4b005d9e894d

微信公眾號:土豆媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術)

碼字不易,點贊鼓勵喲~

前言

上一篇文章 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="//cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入樣式 -->
    <link rel="stylesheet" href="//unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入組件庫 -->
    <script src="//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="//cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入樣式 -->
    <link rel="stylesheet" href="//unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入組件庫 -->
    <script src="//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="//cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <!-- 引入樣式 -->
    <link rel="stylesheet" href="//unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入組件庫 -->
    <script src="//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/58c61b4361ff4b005d9e894d

微信公眾號

土豆媽的碎碎念

微信公眾號的初衷是記錄自己和身邊的一些故事,同時會不定期更新一些技術文章

歡迎大家掃碼關注,一起吸貓,一起聽故事,一起學習前端技術

作者寄語

小小總結,歡迎大家指導~