目前在編寫我的項目,有一個是管理平臺,基本每一個頁面都有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表單全局驗證的方法