目前在編寫我的項目,有一個是管理平臺,基本每一個頁面都有el-from,因此對el-form作了二次封裝, 組件在我的開發使用不錯,但不肯定能知足各類業務需求,因此這裏主要和你們分享一下設計思路。vue
el-form是element-ui庫的表單組件,若是咱們要將其進行二次封裝,那麼須要考慮幾個問題:git
下面經過這些點,來實現封裝一個二次的el-form組件。github
拿業務用到的表單來舉例,在這個表單中的需求分析。element-ui
首先是el-form-item的類型:bash
初步分析,大概會有這些需求,接下來對單個問題一一來分析解決。微信
正常狀況下,咱們使用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>
複製代碼
仔細一看,外面那一層殼是能夠拿掉的,好比長這樣:組件化
<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的字段列表:post
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就是這麼點代碼:ui
<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表單全局驗證的方法