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


需求

設計一個基於 vue 和 element ui 的多卡片組單一表單組件,卡片組用於分類若干字段,比如個人信息、職業信息、技能信息。同時,將標簽等文本抽離 HTML,方便后續增加語言模塊。

分析

原始的 <el-form> 不支持批量設置字段,當頁面中字段較多時,維護和修改 HTML 過於繁瑣;原始的數據對象和規則對象完全扁平化,且無法分組,不便於與頁面結構一 一對應,實現分層遍歷的效果。

注意:使用element ui的表單功能時,必須在 標簽內提供數據,且必須使用 :model="data" 的形式,而在字段組件如 中,則必須使用 v-model="value" 的形式,否則,表單組件將無法綁定model,可能導致所有字段無法輸入(對於下拉框組件可能允許選擇,但選中的內容不會同步到輸入框)

常見報錯:

錯誤1:字段未綁定model,組件加載時報錯
錯誤提示:[Vue warn]: Missing required prop: "value"

錯誤2:表單未綁定model,組件加載時無提示,但使用驗證等功能時提示警告信息
錯誤提示:[Element Warn][Form]model is required for validate to work!

設計解決方案

方案 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> ,並在中間插入代表布局的行和列組件,同時,單獨創建布局對象,存放布局相關參數並在行和列組件中綁定。同時,分離后的卡片和字段組件在其他場景下也能單獨復用;行列組件也可以通過參數化的方式批量生成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM