元件化頁面:封裝el-form
目前在編寫個人專案,有一個是管理平臺,基本每個頁面都有el-from,所以對el-form做了二次封裝, 元件在個人開發使用不錯,但不確定能滿足各種業務需求,所以這裡主要和大家分享一下設計思路。
設計元件
分析問題
el-form是element-ui庫的表單元件,如果我們要將其進行二次封裝,那麼需要考慮幾個問題:
- 如何設計表單渲染欄位
- 不同型別的el-form-item怎麼去渲染,比如input,select,或者自定義顯示內容等
- 表單聯動怎麼去處理
- 事件繫結
- 表單驗證
- 更多需求...
下面通過這些點,來實現封裝一個二次的el-form元件。
從欄位開始
拿業務用到的表單來舉例,在這個表單中的需求分析。
首先是el-form-item的型別:
- tag型別顯示
- input輸入
- select選擇
- 按鈕或者圖片的顯示或者繫結操作
- textarea輸入
然後再分析每個節點:
- label寬度
- 是否需要驗證
- placeholder顯示
- 驗證規則
- 繫結的相關事件
- 是否可為readonly/disabled
- 節點的class/樣式 (一行顯示一個或者多個)
初步分析,大概會有這些需求,接下來對單個問題一一來分析解決。
設計渲染欄位列表
正常情況下,我們使用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的欄位列表:
- label (欄位名)
- value (欄位值 prop,後面會有value代替)
- type (欄位型別 input/select/password/textarea等)
然後除了上面的例子我們還可以自己擴充套件一些欄位:
- event (繫結的方法)
- list (列表 如果是select型別,需要有對於的list去渲染)
- TimePickerOptions (時間配置 如果是時間型別,可以傳入配置)
- disabled (是否禁止)
- filterable (是否可篩選)
- clearable (是否可清除)
- required (是否必填 根據這個欄位,去設定對於的驗證規則)
- validator (自定義驗證 驗證時將使用自定義驗證方法)
- show (是否顯示, 布林值或者是函式,下面會對聯動渲染詳細分析)
- 更多(根據需求和場景擴充套件更多欄位)
然後完整的欄位配置列表大概是這樣的(個人使用場景,不需要使用到所有的設計欄位):
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>
自定義和聯動
通過上面的操作,我們完成了基本的表單動態渲染,但是隻是針對於模版型的場景,舉個例子,如果表單中我要顯示選擇頭像,或者需要增加一個第三方的控制元件,這個時候,上面的渲染規則就有些無能威力了,所以我們需要表單元件支援自定義渲染。
slot-元件自定義神器
不瞭解的同學請戳這個 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文章--- 再也不想寫表單了
表單元件,重要點之一就是表單聯動了,我們來分析一下聯動的場景:
- 建立使用者時,賬號可以填寫,編輯使用者時,賬號只能檢視
- 建立時表單顯示的欄位只有三個,編輯時可檢視到欄位為十個
- 單選框,選擇A,AA欄位消失,選擇B, AA,BB欄位消失,選擇C,所有欄位都顯示,並且變成必填
- 更多聯動場景...
顯示聯動
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表單全域性驗證的方法