目前在编写我的项目,有一个是管理平台,基本每一个页面都有el-from,因此对el-form作了二次封装, 组件在我的开发使用不错,但不肯定能知足各类业务需求,因此这里主要和你们分享一下设计思路。
el-form是element-ui库的表单组件,若是咱们要将其进行二次封装,那么须要考虑几个问题:vue
下面经过这些点,来实现封装一个二次的el-form组件。git
拿业务用到的表单来举例,在这个表单中的需求分析。github
首先是el-form-item的类型:element-ui
而后再分析每一个节点:微信
初步分析,大概会有这些需求,接下来对单个问题一一来分析解决。函数
正常状况下,咱们使用form,它的子项会是这样的,好比有input和select:组件化
// input类型 <el-form-item label="活动名称" prop="name"> <el-input v-model="ruleForm.name"></el-input> </el-form-item> // select类型 <el-form-item label="活动区域" prop="region"> <el-select v-model="ruleForm.region" placeholder="请选择活动区域"> <el-option label="区域一" value="shanghai"></el-option> <el-option label="区域二" value="beijing"></el-option> </el-select> </el-form-item>
仔细一看,外面那一层壳是能够拿掉的,好比长这样:post
<el-form-item label="label" prop="prop"> // 若是是input类型 <el-input v-if="input" v-model="ruleForm.name"></el-input> // 若是是select类型 <el-select v-if="select" v-model="ruleForm.region" placeholder="请选择活动区域"> <el-option label="区域一" value="shanghai"></el-option> <el-option label="区域二" value="beijing"></el-option> </el-select> </el-form-item>
那由此咱们能够设计出循环form的字段列表:ui
而后除了上面的例子咱们还能够本身扩展一些字段:this
而后完整的字段配置列表大概是这样的(我的使用场景,不须要使用到全部的设计字段):
fieldList: [ { label: '帐号', value: 'account', type: 'input', required: true, validator: checkAccount }, { label: '密码', value: 'password', type: 'password', required: true, validator: checkPwd }, { label: '昵称', value: 'name', type: 'input', required: true }, { label: '性别', value: 'sex', type: 'select', list: 'sexList', required: true }, { label: '头像', value: 'avatar', type: 'slot', className: 'el-form-block' }, { label: '手机号码', value: 'phone', type: 'input', validator: checkPhone }, { label: '微信', value: 'wechat', type: 'input', validator: checkWechat }, { label: 'QQ', value: 'qq', type: 'input', validator: checkQQ }, { label: '邮箱', value: 'email', type: 'input', validator: checkEmail }, { label: '描述', value: 'desc', type: 'textarea', className: 'el-form-block' }, { label: '状态', value: 'status', type: 'select', list: 'statusList', required: true } ]
组件内部怎么操做,很简单,根据规则,一一对应循环字段,绑定属性就ok了,因此组件内部template就是这么点代码:
<el-form ref="form" class="page-form" :class="className" :model="data" :rules="rules" :label-width="labelWidth" > <el-form-item v-for="(item, index) in getConfigList()" :key="index" :prop="item.value" :label="item.label" :class="item.className" > <!-- solt --> <template v-if="item.type === 'slot'"> <slot :name="'form-' + item.value" /> </template> <!-- 普通输入框 --> <el-input v-if="item.type === 'input' || item.type === 'password'" v-model="data[item.value]" :type="item.type" :disabled="item.disabled" :placeholder="getPlaceholder(item)" @focus="handleEvent(item.event)" /> <!-- 文本输入框 --> <el-input v-if="item.type === 'textarea'" v-model.trim="data[item.value]" :type="item.type" :disabled="item.disabled" :placeholder="getPlaceholder(item)" :autosize="{minRows: 2, maxRows: 10}" @focus="handleEvent(item.event)" /> <!-- 计数器 --> <el-input-number v-if="item.type === 'inputNumber'" v-model="data[item.value]" size="small" :min="item.min" :max="item.max" @change="handleEvent(item.event)" /> <!-- 选择框 --> <el-select v-if="item.type === 'select'" v-model="data[item.value]" :disabled="item.disabled" :clearable="item.clearable" :filterable="item.filterable" :placeholder="getPlaceholder(item)" @change="handleEvent(item.event, data[item.value])" > <el-option v-for="(childItem, childIndex) in listTypeInfo[item.list]" :key="childIndex" :label="childItem.key" :value="childItem.value" /> </el-select> <!-- 日期选择框 --> <el-date-picker v-if="item.type === 'date'" v-model="data[item.value]" :type="item.dateType" :picker-options="item.TimePickerOptions" :clearable="item.clearable" :disabled="item.disabled" :placeholder="getPlaceholder(item)" @focus="handleEvent(item.event)" /> <!-- 信息展现框 --> <el-tag v-if="item.type === 'tag'"> {{ $fn.getDataName({dataList: listTypeInfo[item.list], value: 'value', label: 'key', data: data[item.value]}) || '-' }} </el-tag> </el-form-item> </el-form>
经过上面的操做,咱们完成了基本的表单动态渲染,可是只是针对于模版型的场景,举个例子,若是表单中我要显示选择头像,或者须要增长一个第三方的控件,这个时候,上面的渲染规则就有些无能威力了,因此咱们须要表单组件支持自定义渲染。
不了解的同窗请戳这个vue中的slot属性
在上面,组件的代码中有这样一段:
<!-- solt --> <template v-if="item.type === 'slot'"> <slot :name="'form-' + item.value" /> </template>
这段代码的意思是:渲染类型为slot,插槽的名称为‘form-’前缀加上字段的值。
有什么用呢?咱们回到使用组件的页面。
在form中,这里有一个子项,须要显示图片和按钮,这个时候组件内部已经定义了插槽,而且有对于的名字,咱们只须要在外部将对应的插槽传入到组件中,便可实现自定义内容,请看对应代码:
<!-- 自定义插槽-选择头像 --> <template v-slot:form-avatar> <div class="slot-avatar"> <img v-imgAlart :src="formInfo.data.avatar" style="height: 30px;" > <el-button v-waves type="primary" icon="el-icon-picture" size="mini" @click="handleClick('selectFile')" > {{ formInfo.data.avatar ? '更换头像' : '选择头像' }} </el-button> </div> </template> </page-form>
组件内插槽除了能够接收对应名字的内容外,还能够将组件中的数据经过插槽传输到外部,在外部使用组件数据渲染内容,自定义组件神器,请务必了解
表单联动,推荐阅读element文章---不再想写表单了
表单组件,重要点之一就是表单联动了,咱们来分析一下联动的场景:
1.字段定义为布尔值时
在字段定义的时候,有定义一个字段,show,咱们能够将show字段定义为布尔值,而后在页面中watch定义数据的是否显示,好比这段代码:
// 根据弹窗类型作数据联动 'dialogInfo.type' (val) { const fieldList = this.formInfo.fieldList switch (val) { case 'userInfo': fieldList.forEach(item => { if (['user_old_pwd', 'user_new_pwd', 'user_new1_pwd'].includes(item.value)) { item.show = true } else { item.show = false } }) break case 'password': fieldList.forEach(item => { if (!['user_old_pwd', 'user_new_pwd', 'user_new1_pwd'].includes(item.value)) { item.show = true } else { item.show = false } }) break } }
2.字段定义为函数时
若是不想再组件外部操做,想把显示隐藏的渲染逻辑交给组件内部处理,那咱们能够定义show字段为函数,好比这段代码:
{label: '名称', value: 'name', show: data => { return data.type === 'userInfo' }}
这两种方式是我目前自定义组件场景中有用到的,根据需求,使用不一样的方法
1. 字段定义为函数时
逻辑联动表示form中某一项发生改变,其余一项或者多项会根据对应的数据发生相对的改变,好比下面这段代码(由于我的项目目前没有这种业务,因此并无相关示例,代码来自element博客):
[ { title: '活动类型', key: 'act_type', type: 'radio', props: { options: { 1: '拉新', 2: '冲单', 3: '回馈' } } }, { title: '生效方式', key: 'effect_type', type: 'radio', props(form) { const value; const map = { 1: '当即', 2: '按时间', 3: '按条件' }; if (form.act_type === 3) { value = 1; } return { value: value, options: map }; } } ]
将须要联动的字段定义为方法,方法接收表单数据,方法根据数据进行对应的联动,这种方法能够基本完美解决各类问题,在组件内部则须要对属性添加一层判断,普通类型只须要进行绑定,typeof为function须要绑定运行的函数。
2. 定义事件字段,经过中间件派发到外部处理
定义事件字段,定义的字段列表中,咱们能够设计一个event字段,当事件上绑定方法的时候,字段设计为这样:
{ label: '性别', value: 'sex', type: 'select', list: 'sexList', event: 'changeName', required: true }
示例代码为select类型,组件的input绑定代码上也须要绑定相关事件:
<!-- 选择框 --> <el-select v-if="item.type === 'select'" v-model="data[item.value]" :disabled="item.disabled" :clearable="item.clearable" :filterable="item.filterable" :placeholder="getPlaceholder(item)" @change="handleEvent(item.event, data[item.value])" > <el-option v-for="(childItem, childIndex) in listTypeInfo[item.list]" :key="childIndex" :label="childItem.key" :value="childItem.value" /> </el-select>
经过handleEvent中间件,绑定对应类型和对应数据,中间件函数的用处就负责进行页面和组件的通信,组件内部穿出事件类型和数据,页面接收而且处理,不论多少的事件,只有一个中间件便可以解决。
组件内部中间件处理:
// 绑定的相关事件 handleEvent (evnet) { this.$emit('handleEvent', evnet) }
组件使用代码:
<!-- form --> <page-form v-if="dialogInfo.type !== 'userTransfer'" :ref-obj.sync="formInfo.ref" :data="formInfo.data" :field-list="formInfo.fieldList" :rules="formInfo.rules" :label-width="formInfo.labelWidth" :list-type-info="listTypeInfo" @handleClick="handleClick" @handleEvent="handleEvent" > <!-- 自定义插槽-选择头像 --> <template v-slot:form-avatar> <div class="slot-avatar"> <img v-imgAlart :src="formInfo.data.avatar" style="height: 30px;" > <el-button v-waves type="primary" icon="el-icon-picture" size="mini" @click="handleClick('selectFile')" > {{ formInfo.data.avatar ? '更换头像' : '选择头像' }} </el-button> </div> </template> </page-form>
页面中处理事件:
// 触发事件 handleEvent (event, data) { switch (event) { case 'changeName': console.log(data) // 触发相关联动逻辑 break } }
两种方式,各有优劣,这里主要分享思路,你们根据本身业务选择合适的方案。
表单验证,戳这个,上次写的验证还热乎的---->element-ui表单全局验证的方法