1. 程式人生 > 其它 >基於 element ui 的多卡片組表單元件

基於 element ui 的多卡片組表單元件

需求

設計一個基於 vue 和 element ui 的多卡片組單一表單元件,卡片組用於分類若干欄位,比如個人資訊、職業資訊、技能資訊。同時,將標籤等文字抽離 HTML,方便後續增加語言模組。

分析

原始的 <el-form> 不支援批量設定欄位,當頁面中欄位較多時,維護和修改 HTML 過於繁瑣;原始的資料物件和規則物件完全扁平化,且無法分組,不便於與頁面結構一 一對應,實現分層遍歷的效果。

設計解決方案

方案 1

  1. 由於 HTML 是不需要動的,因此考慮將卡片的標題、表單欄位的 label,model 和 rule 屬性從 HTML 中抽離出來,作為配置引數,並單獨建立一個結構化的物件(對應於卡片和欄位的層級關係),儲存以上 4 類資訊
  2. 再通過 v-for 遍歷結構化物件,並逐層生成卡片 <el-card> 和欄位 <el-form-item>

相比於直接維護 HTML,維護 JS 物件需要的程式碼量少很多,且沒有無關資訊的干擾(屬性繫結以及巢狀標籤等,重複的任務很容易出錯,且不易被發現)。

JS 物件結構:

// fields 下面的每個物件代表一個卡片,屬性 label 是一個物件,用於存放各欄位的標籤名
fields {
  personalInfo {
    title: '',
    lable: {
      name: '',
      ...
    }
  ...
  }
},
// datas 物件用於存放欄位繫結的資料
datas {
  personalInfo {
      name: '',
      ...
  }
},
// rules 物件用於存放欄位繫結的資料
rules {
  personalInfo {
      name: '',
      ...
  }
}

存在的問題

方案 1 建立的結構化物件參考了 element ui ,將各資訊分開儲存,導致每次修改都要改多處,並且由於使用了巢狀結構,導致定位繁瑣;且部分屬性使用的資料型別不規範,如卡片組和標籤組物件應該使用陣列型別。

方案 2

統一成一個結構化物件:每個欄位物件儲存 label、model 和 rule 三個資訊,欄位外層再巢狀卡片物件

改進後的 JS 物件結構:

// fieldGroupList 作為整個結構化物件,存放所有資料,並根據所在卡片進行分組
fieldGroupList: [
 {
    fieldTitle: '個人基本資訊',
    // fieldList 存放一個卡片內的多個欄位,每個欄位物件包含 label、val 和 rule 三個屬性,對應標籤名、資料和校驗規則
    fieldList: [
      {
        key: 'name', // 用於標記一個欄位
        label: '姓名',
        val: '',
        rule: [{ required: true, message: `請輸入姓名`, trigger: 'blur' },],
      },
      ... // 其他欄位
    ],
  },
  ... // 其他卡片
]

對應的 HTML:

<el-form label-width="100px">
  <div class="el-card-group">
    <el-card v-for="(fieldGroup, i) in fieldGroupList" :key="i">
      <div slot="header">
        <span>{{ fieldGroup.fieldTitle }}</span>
      </div>
      <el-form-item
        :label="fieldItem.label"
        :rules="fieldItem.rule"
        v-for="(fieldItem, i) in fieldGroup.fieldList"
        :key="i"
      >
        <el-input v-model="fieldItem.val"></el-input>
      </el-form-item>
    </el-card>
  </div>
  <div class="el-form-btn-group">
    <el-button type="primary" @click="submitForm('fieldGroupList')"
      >提交</el-button
    >
    <el-button @click="resetForm('fieldGroupList')">重置</el-button>
  </div>
</el-form>

存在的問題

由於不是直接通過原始的扁平化資料物件和規則物件進行驗證,無法使用框架內建的驗證、重置等方法,需要重寫,因此還需要改進

方案3

在方案2的基礎上:當初始化元件時,將結構化物件中 對應於各欄位的資料變數和規則變數 還原成 element ui 可接受的扁平化資料物件和規則物件,以便直接使用框架內建的表單驗證等方法。

data() {
  return {
    // 初始化資料物件和規則物件為空物件,並在 created 鉤子方法中賦予其對應的資料
    ruleForm: {
      datas: {},
      rules: {},
    },
    fieldGroupList: [
      // 與方案2相同的結構化物件
    ],
  };
},
created() {
  this.setRuleForm(this.fieldGroupList, this.ruleForm); // 在初始化階段呼叫轉換方法,避免獲取到空物件
},
methods: {
  // 定義轉換方法
  setRuleForm(fieldGroupList, ruleForm) {
    let datas = {},
      rules = {};
    if (fieldGroupList) {
      fieldGroupList.map((fieldGroup) => {
        fieldGroup.fieldList.map((field) => {
          datas[field.key] = field.val;
          rules[field.key] = field.rule;
        });
      });
      ruleForm.datas = datas;
      ruleForm.rules = rules;
    } else {
      throw new Error('解析失敗,無法顯示錶單專案');
    }
  },
  ... // 其他方法
}

對應的 HTML:

<el-form
  ref="ruleForm"
  :model="ruleForm.datas"
  :rules="ruleForm.rules"
  label-width="100px"
>
  <div class="el-card-group">
    <el-card v-for="(fieldGroup, i) in fieldGroupList" :key="i">
      <div slot="header">
        <span>{{ fieldGroup.fieldTitle }}</span>
      </div>
      <el-form-item
        :label="fieldItem.label"
        :rules="ruleForm.rules[fieldItem.key]"
        :prop="fieldItem.key"
        v-for="(fieldItem, i) in fieldGroup.fieldList"
        :key="i"
      >
        <el-input v-model="ruleForm.datas[fieldItem.key]"></el-input>
      </el-form-item>
    </el-card>
  </div>
  <div class="el-form-btn-group">
    <el-button type="primary" @click="submitForm('ruleForm')"
      >提交</el-button
    >
    <el-button @click="resetForm('ruleForm')">重置</el-button>
  </div>
</el-form>

至此,已經基本滿足了需求。在保留了原元件方法的基礎上,允許自定義多個卡片和多個欄位,而且後續增刪改欄位也不需要動 HTML 。

目前尚未解決的問題

  1. 輸入欄位的型別 <input type="***"> 不能定製
    這個問題可以通過與上述類似的方式解決,即增加一個表示輸入型別的屬性並在 HTML 中繫結。但是這種實現方式會導致元件的維護越來越複雜和困難,因為標籤擁有的屬性都需要在 JS 物件中進行定義,每次擴充套件都需要直接修改 JS 物件,不符合開閉原則。
    稍微好一點的方法是通過單獨定義一個物件,並注入到現有物件中,這樣避免了直接修改原 JS 物件。
  2. 佈局不能定製,比如需要某些特定欄位顯示在一行,某些欄位獨立成行。這些需求需要通過新增額外的 <el-row><el-col> 元件來解決,因此需要同步修改 HTML 部分,現有元件不能直接複用。目前的想法是通過分離卡片 <el-card> 和欄位 <el-form-item> ,並在中間插入代表佈局的行和列元件,同時,單獨建立佈局物件,存放佈局相關引數並在行和列元件中繫結。同時,分離後的卡片和欄位元件在其他場景下也能單獨複用;行列元件也可以通過引數化的方式批量生成。