ElementUI多個子組件表單的校驗管理

背景

公司項目中所用到的前端框架是Vue.js + ElementUI,因爲項目的業務場景中有很多的大表單,但是ElementUI的表單寫法對於表單的拆分和校驗其實並不是很友好。最初的項目爲了方便,常常把多個表單寫在一個.vue組件中,這導致單文件的代碼量巨大,邏輯十分複雜。目前爲了維護方便,表單的拆分就變得十分重要。

現在做了以下的Demo說明我們的業務場景,父組件是App.vue,該組件中包含了PersonForm.vueAdsForm.vue這兩個子組件(在實際的業務場景中,可能多達10+表單)。【提交】按鈕在父組件App.vue中,當點擊【提交】按鈕後,應該分別校驗各個子組件,如果每個子組件都校驗成功後再進行提交。

PersonForm.vue文件

下面的代碼是PersonForm.vue組件,該表單包括姓名、年齡、性別。我們使用了PersonForm這個類去實例化組件中的personForm的值。在PersonForm中有個static方法getRule去獲取校驗方法去獲取校驗對象,該校驗對象是ElementUI要求的寫法,會在<el-form>rules中定義。

<template>
  <div class="person-form">
    <h2>PersonForm.vue</h2>
    <el-form :model="personForm" ref="personForm" :rules="personFormRules">
      <!-- 姓名 -->
      <el-form-item label="姓名" prop="name">
        <el-input v-model="personForm.name"></el-input>
      </el-form-item>
      <!-- 年齡 -->
      <el-form-item label="年齡" prop="age">
        <el-input v-model="personForm.age"></el-input>
      </el-form-item>
      <!-- 性別 -->
      <el-form-item label="性別" prop="sex">
        <el-radio-group v-model="personForm.sex">
          <el-radio label="0">男</el-radio>
          <el-radio label="1">女</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import {validateName, validateAge, validateSex } from '@/lib/validator.js';

// PersonForm的類
class PersonForm {
  constructor() {
    this.name = '';
    this.age = null;
    this.sex = null;
  }

  static getRule() {
    return {
      name: [{ validator: validateName, trigger: 'blur' }],
      age: [{ validator: validateAge, trigger: 'blur' }],
      sex: [{validator: validateSex, trigger: 'blur'}],
    }
  }
}

export default {
  data() {
    return {
      personForm: new PersonForm(),
      personFormRules: PersonForm.getRule()
    }
  }
}
</script>

<style>
  .person-form {
    width: 400px;
    height: 350px;
    padding: 20px;
    border: 1px solid #ccc;
  }
</style>

AdsForm.vue文件

下面的代碼是AdsForm.vue組件,該表單包括廣告名和廣告位置。我們使用了AdsForm這個類去實例化組件中的adsForm的值。在AdsForm中有個static方法getRule去獲取校驗方法去獲取校驗對象。

<template>
  <div class="ads-form">
      <h2>AdsForm.vue</h2>
      <el-form :model="adsForm" ref="adsForm" :rules="adsFormRules">
      <!-- 廣告名 -->
      <el-form-item label="廣告名" prop="name">
        <el-input v-model="adsForm.name"></el-input>
      </el-form-item>
      <!-- 廣告位置 -->
      <el-form-item label="廣告位置" prop="position">
        <el-select v-model="adsForm.position">
          <el-option value="1" label="左上"></el-option>
          <el-option value="2" label="右上"></el-option>
          <el-option value="3" label="左下"></el-option>
          <el-option value="4" label="右下"></el-option>
        </el-select>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import { notEmpty, validateName } from '@/lib/validator.js';

class AdsForm {
  constructor() {
    this.name = '';
    this.position = null;
  }

  static getRule() {
    return {
      name: [{ validator: validateName, trigger: 'blur' }],
      position: [{ validator: notEmpty, trigger: 'blur' }],
    }
  }
}

export default {
  data() {
    return {
      adsForm: new AdsForm(),
      adsFormRules: AdsForm.getRule()
    }
  }
}
</script>

<style>
  .ads-form {
    width: 400px;
    height: 350px;
    padding: 20px;
    border: 1px solid #ccc;
    margin-left: 30px;
  }
</style>

validator.js文件

PersonForm.vueAdsForm.vue中我們導入了validator.js中的校驗方法,這些校驗方法中封裝了對錶單屬性值的校驗規則。該文件中的方法在實際項目中,應該使用策略模式再封裝一下。Demo中只有4個方法,就沒有再封裝來干擾讀者理解代碼。

// 驗證名字
var validateName = (rule, value, callback) => {
  if(!value) {
    callback(new Error('名字不能爲空'));
  } else if(/[a-zA-Z]/.test(value)) {
    callback(new Error('請填寫中文名字!'));
  } else {
    callback();
  }
};

// 驗證年齡
var validateAge = (rule, value, callback) => {
  const toNumberVal = Number(value);
  if ((typeof value === 'string' && value === '') || (value === null)) {
    callback(new Error('年齡不允許爲空'));
  } else if (isNaN(toNumberVal)) {
    callback(new Error('年齡爲數值類型'));
  } else if(!(toNumberVal > 0 && toNumberVal <= 120)) {
    callback(new Error('年齡範圍應該大於一歲且小於等於120歲'));
  } else {
    callback();
  }
}

// 驗證性別
var validateSex = (rule, value, callback) => {
  if (value === null) {
    callback(new Error('性別不允許爲空'));
  } {
    callback();
  }
}

// 驗證不爲空
var notEmpty = (rule, value, callback) => {
  if (value === '' || value === null || value === undefined) {
    callback(new Error('不允許爲空'));
  } else {
    callback();
  }
}

export { 
    validateName, 
    validateAge, 
    validateSex,
    notEmpty,
}

App.vue

App.vue是父組件,當點擊【提交】按鈕時,應該調用其ElmentUI的this.$refs[formName].validate方法去驗證各個子組件中的表單。但是需要注意的是,該方法是一個異步方法。
所以這裏封裝了一個getFormPromise去生成Promise對象,並使用Promise.all去並行調用返回最終的校驗結果數組。

<template>
  <div class="app">
    <h1>App.vue</h1>

    <div class="forms-container">
      <!-- PersonForm.vue -->
      <person-form ref="personFormComp"/>
      <!-- AdsForm.vue -->
      <ads-form ref="adsFormComp"/>
    </div>

    <el-button 
      class="submit-btn" 
      @click="submitForm"
      type="primary">
      提交
    </el-button>
  </div>
</template>


<script>
import PersonForm from '@/components/PersonForm';
import AdsForm from '@/components/AdsForm.vue';

export default {
  components: {
    'person-form': PersonForm,
    'ads-form': AdsForm,
  },
  methods: {
    submitForm() {
      // 獲取到組件中的form
      const personForm = this.$refs.personFormComp.$refs.personForm;
      const adsForm = this.$refs.adsFormComp.$refs.adsForm;
      // 使用Promise.all去校驗結果
      Promise.all([personForm, adsForm].map(this.getFormPromise)).then(res => {
        const validateResult = res.every(item => !!item);
        if (validateResult) {
          console.log('兩個表單都校驗通過');
        } else {
          console.log('兩個表單未校驗通過');
        }
      })
    },
    getFormPromise(form) {
      return new Promise(resolve => {
        form.validate(res => {
          resolve(res);
        })
      })
    }
  }
}
</script>

<style>
.app {
  border: 1px solid #ccc;
  padding: 20px;
  width: 900px;
}
.app .submit-btn {
  margin-top: 40px;
}
.forms-container {
  display: flex;
}
</style>

如果大家有更好的關於ElementUI的表單封裝方法,請不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章