form-item组件封装细讲-中(组件库系列~五)

247 阅读4分钟

前言

上一篇文章中我们聊到了form-item组件的dom结构处理和异常情况slot的插入处理。我们今天来聊一下关于form-item组件布局的实现和注意事项

在实际的开发中,form最常见的应用场景有新增、编辑的提交表单,还有列表的查询表单。这类型的表单在后台系统开发中多如牛毛。而针对这些表单不同的展现形式,我们可能需要在其中花费掉大量的样式布局开发时间,所以组件中期望能够将这些功能全部内聚,通过参数就可以简单的控制。

1. 一行多个的布局------解决方案

  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表单查询的情况,时常需要控制按钮的位置。我们也可以通过组件层做一定的控制。

  1. 通过css样式控制按钮的宽度为当前行剩余宽度的100%。
  2. 因为操作按钮不存在label字段,所以设定该form-item的label-width宽度固定为0;
  3. 添加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等等。在业务中既有可能出现一些form-item子组件不包含的公共组件,比如说对图片upload组件的二次封装等。我们可以通过添加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组件库已开源。 组件库代码地址: gitee.com/yangxiongas… 组件库文档代码地址: gitee.com/yangxiongas…

对组件库开发有兴趣的可以进QQ群: 617330944大家一起讨论交流