前言
在之前的文章中,我们介绍了form-item组件的dom结构处理以及异常情况slot的插入处理。今天,我们将要聊一聊关于form-item组件布局实现的方法和需要注意的事项。
单组件在实际开发中的重要性
在实际开发中,表单组件是非常重要的组件之一。特别是在后台系统应用中,表单组件的应用非常普遍,主要包括新增、编辑的提交表单以及查询表单。
不同展现形式的表单需要大量的样式布局开发时间,因此我们希望表单组件可以将这些功能全部整合到一起,通过简单的参数控制即可完成。这样可以提高开发效率,降低开发难度。
1. 一行多个的布局------解决方案
- 首先我们会在
form-item
遍历的最外层加上flex
的换行样式,然后设置内部的每个遍历元素的宽度百分比。这里我们采用col-24
的栅格布局。要知道每一个子组件占据的宽度,首先我们可以定义一行放多少列,比如说一行四列,那么每一个就是col-8
(25%)。所以我们定一个入参来控制:
props: {
form: { type: Object, required: true, default: () => {} }, // 传进来的共享的form表单值对象
config: {type: Array, default: () => [
// itemType值的可选项为可能出现的高频组件名
// input\select\radio\checkbox\date-picker等(组件内部需要写清楚,且需要在require中添加入参强制校验)
{itemType: "input", label: "姓名", prop: "name"},
{itemType: "select", label: "性别", prop: "sex"},
{itemType: "select", label: "身份证插槽", prop: "idCard", isSlot: true}
]},
column: {
type: [Number, String],
default: 4,
validator: value => [1, 2, 3, 4, 6].indexOf(Number(value)) !== -1
}
}
接下来我们还需要考虑到有些情况下,可能某一个元素需要占到更宽的宽度,比如说像下面图中现居住地和户籍地址的字段一样:
这样的情况我们需要能够对单独的列进行配置,所以我们可以在config中添加额外参数span:
span参数:把column设置的参数为n等份。(column=“4”,那么一共为4等份)未设置span参数则默认为1等分,后面可以跟上不同的等份值
HTML:
<el-form-item
v-for="(item, index) in realConfig"
:key="'form-item' + index"
:class="(item.span && 'col-' + colVal * item.span) || `col-${colVal}`"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot"
:is="'item-' + item.itemType || 'text'"
:form="form"
v-bind="{ ...item, noLabel }"
/>
<slot v-else :name="item.prop" />
</el-form-item>
JS
props: {
form: { type: Object, required: true, default: () => {} },
config: {type: Array, default: () => [
{itemType: "input", label: "姓名", prop: "name"},
{itemType: "input", label: "不可见元素", prop: "name", hidden: true},
// 添加了span值为2,如果等份为4,那么这个宽度占到50%,其他未设置的宽度为25%
{itemType: "select", label: "性别", prop: "sex", span: 2},
{itemType: "select", label: "身份证插槽", prop: "idCard", isSlot: true}
]},
// 每一行有几列
column: {
type: [Number, String],
default: 4,
validator: value => [1, 2, 3, 4, 6].indexOf(Number(value)) !== -1
}
},
computed: {
// 兼容处理column参数为String类型,虽然可以在props里面指定类型为Number
// 但是Number传值会比String更麻烦
// Number => :column="4" String => column="4"
realColumn() {
return Number(this.column) || 4;
},
// 整体列数对应的col
colVal() {
return 24 / this.realColumn;
},
// 可以看到html代码中遍历的是该参数,主要是用于支持config中可以添加hidden参数
// 动态的控制部分元素隐藏
realConfig() {
return this.config.filter(e => !e.hidden);
},
}
2. 全局禁用和清除功能实现
这种功能就很简单了,直接通过props进行参数接收。然后通过components进行参数的传递。让各个子组件内部处理,不过需要以config内部参数为主,下面为代码实现:
HTML:
<el-form-item
v-for="(item, index) in realConfig"
:key="'form-item' + index"
:class="(item.span && 'col-' + colVal * item.span) || `col-${colVal}`"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot"
:is="'item-' + item.itemType || 'text'"
:form="form"
:disabled="item.disabled || disabled"
:clearable="item.clearable || clearable"
v-bind="{ ...item }"
/>
<slot v-else :name="item.prop" />
</el-form-item>
JS
props: {
disabled: { type: Boolean, default: false }, // 全局禁用
clearable: { type: Boolean, default: false }, // 全组件带清除功能
},
3. 宽度设定
需要在props中添加一个width的入参,值默认为100%,该值为元素的宽度占对应的form-item子项宽度的值。(比如说column: 4,那么每一列为25%,该width值设定的是这个25%的宽度);
HTML:
<el-form-item
v-for="(item, index) in realConfig"
:key="'form-item' + index"
:class="(item.span && 'col-' + colVal * item.span) || `col-${colVal}`"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot"
:is="'item-' + item.itemType || 'text'"
:form="form"
:disabled="item.disabled || disabled"
:clearable="item.clearable || clearable"
:style="{ width: itemWidth(item, index) }"
v-bind="{ ...item }"
/>
<slot v-else :name="item.prop" />
</el-form-item>
JS
props: {
config: {type: Array, default: () => [
// 设置该项宽度为span:1所占宽度的80%
{itemType: "input", label: "姓名", prop: "name", width: 80%},
{itemType: "input", label: "不可见元素", prop: "name", hidden: true},
// 添加了span值为2,如果等份为4,那么这个宽度占到50%,其他未设置的宽度为25%
{itemType: "select", label: "性别", prop: "sex", span: 2},
{itemType: "select", label: "身份证插槽", prop: "idCard", isSlot: true}
]},
width: { type: String, default: '100%' }, // 每一项form-item项表单内容宽度
},
computed: {
itemWidth() {
return function (data, index) {
// 判定当前项是否为当前行最后一项,如果是那么需要减少各子组件中间距的大小
if (!this.lastIndex.includes(index)) {
return `calc(${data.width || this.width} - ${this.$Config.Components.formItem.space})`;
}
// 以config中的width参数为主
return data.width || this.width;
};
}
}
4. 默认插槽----按钮
在实际开发中遇到列表页面form表单查询的情况,时常需要控制按钮的位置。我们也可以通过组件层做一定的控制。
- 通过css样式控制按钮的宽度为当前行剩余宽度的100%。
- 因为操作按钮不存在label字段,所以设定该form-item的label-width宽度固定为0;
- 添加rightSide参数,默认为false,为true时设置
justify-content: flex-end;
等css属性。
代码如下:
HTML:
<div class="flex-wrap">
<el-form-item
v-for="(item, index) in realConfig"
:key="'form-item' + index"
:class="(item.span && 'col-' + colVal * item.span) || `col-${colVal}`"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot"
:is="'item-' + item.itemType || 'text'"
:form="form"
:disabled="item.disabled || disabled"
:clearable="item.clearable || clearable"
:style="{ width: itemWidth(item, index) }"
v-bind="{ ...item }"
/>
<slot v-else :name="item.prop" />
</el-form-item>
<el-form-item v-if="$slots.default" :class="{ formButtonRight: rightSide }" label-width="0px" class="flex-auto slot">
<slot />
</el-form-item>
</div>
JS
props: {
rightSide: { type: Boolean, default: false }, // 查询按钮是否放在右侧
}
CSS
.formButtonRight {
display: flex;
justify-content: flex-end;
.el-form-item__content {
padding-right: 0;
}
}
5. config中添加componets组件,支持自定义组件扩展
为了满足业务需求,有时我们需要在form-item组件中添加一些不同于input、select等子组件的公共组件,例如图片上传组件的二次封装等。为了实现这一需求,我们提供了一种方便的自定义组件嵌入方式——通过添加config参数components参数来实现。可以满足您的自定义需求。具体实现方法如下:
HTML
<div class="flex-wrap">
<el-form-item
v-for="(item, index) in realConfig"
:key="'form-item' + index"
:class="(item.span && 'col-' + colVal * item.span) || `col-${colVal}`"
:label="item.label"
:prop="item.prop">
<components
v-if="!item.isSlot && !item.components"
:is="'item-' + item.itemType || 'text'"
:form="form"
:disabled="item.disabled || disabled"
:clearable="item.clearable || clearable"
:style="{ width: itemWidth(item, index) }"
v-bind="{ ...item }"
/>
<!-- item.components如果有值,那么通过components + :is的方式进行组件的渲染。
通过v-model="form[item.prop]"进行数据绑定
并通过v-bind将config所有参数传递下去 -->
<components v-bind="item" :is="item.components" v-else-if="item.components" v-model="form[item.prop]" />
<slot v-else v-bind="item" :name="item.prop" />
</el-form-item>
<el-form-item v-if="$slots.default" :class="{ formButtonRight: rightSide }" label-width="0px" class="flex-auto slot">
<slot />
</el-form-item>
</div>
JS
props: {
config: {type: Array, default: () => [
// 设置该项宽度为span:1所占宽度的80%
{itemType: "input", label: "姓名", prop: "name", width: 80%},
{itemType: "input", label: "不可见元素", prop: "name", hidden: true},
// 添加了span值为2,如果等份为4,那么这个宽度占到50%,其他未设置的宽度为25%
{itemType: "select", label: "性别", prop: "sex", span: 2},
// com-upload这个组件需要已经注册好,全局注册或者使用层当前vue文件注册都可以
{components: 'com-upload', label: "自定义上传组件", prop: "uploadFile", ext: ".jpg,.jpeg,.png,.gif"},
{itemType: "select", label: "身份证插槽", prop: "idCard", isSlot: true}
]},
}
PS:下一遍文章会详细的讲一下form-item里面关于label-width的宽度宽度处理。
总结:这里我们就实现了form-item组件的基本开发,已经能在项目中进行使用了,关于内部的input、select等组件,大家可以通过gitee进行查看。hc-basic组件库已开源。
组件库代码地址
https://gitee.com/yangxiongasin/hc-basic