基于 element ui 的多卡片组表单组件
作者:互联网
需求
设计一个基于 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>
,并在中间插入代表布局的行和列组件,同时,单独创建布局对象,存放布局相关参数并在行和列组件中绑定。同时,分离后的卡片和字段组件在其他场景下也能单独复用;行列组件也可以通过参数化的方式批量生成。
标签:...,卡片,对象,表单,rules,HTML,ui,组件,element 来源: https://www.cnblogs.com/cjc917/p/16146104.html