基於 element ui 的多卡片組表單元件
阿新 • • 發佈:2022-04-14
需求
設計一個基於 vue 和 element ui 的多卡片組單一表單元件,卡片組用於分類若干欄位,比如個人資訊、職業資訊、技能資訊。同時,將標籤等文字抽離 HTML,方便後續增加語言模組。
分析
原始的 <el-form>
不支援批量設定欄位,當頁面中欄位較多時,維護和修改 HTML 過於繁瑣;原始的資料物件和規則物件完全扁平化,且無法分組,不便於與頁面結構一 一對應,實現分層遍歷的效果。
設計解決方案
方案 1
- 由於 HTML 是不需要動的,因此考慮將卡片的標題、表單欄位的 label,model 和 rule 屬性從 HTML 中抽離出來,作為配置引數,並單獨建立一個結構化的物件(對應於卡片和欄位的層級關係),儲存以上 4 類資訊
- 再通過 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 。
目前尚未解決的問題
- 輸入欄位的型別
<input type="***">
不能定製
這個問題可以通過與上述類似的方式解決,即增加一個表示輸入型別的屬性並在 HTML 中繫結。但是這種實現方式會導致元件的維護越來越複雜和困難,因為標籤擁有的屬性都需要在 JS 物件中進行定義,每次擴充套件都需要直接修改 JS 物件,不符合開閉原則。
稍微好一點的方法是通過單獨定義一個物件,並注入到現有物件中,這樣避免了直接修改原 JS 物件。 - 佈局不能定製,比如需要某些特定欄位顯示在一行,某些欄位獨立成行。這些需求需要通過新增額外的
<el-row>
和<el-col>
元件來解決,因此需要同步修改 HTML 部分,現有元件不能直接複用。目前的想法是通過分離卡片<el-card>
和欄位<el-form-item>
,並在中間插入代表佈局的行和列元件,同時,單獨建立佈局物件,存放佈局相關引數並在行和列元件中繫結。同時,分離後的卡片和欄位元件在其他場景下也能單獨複用;行列元件也可以通過引數化的方式批量生成。