專案做的越來越多,重複的東西不斷的封裝成了元件,慢慢的,頁面就元件化了,只需要定義元件配置資料,使用就好了,這是一件非常舒服的事情,這篇文章主要和大家講講如何對element-ui中的el-table進行二次封裝。
分析需求
公有元件,可以被任何頁面呼叫,首先要保證元件不和外部業務發生耦合,其次就是要設計合理的欄位,使用時通過不同的配置即可使用。 那先大致來分析以下可能有的需求:
- 動態表頭
- 巢狀表頭
- 表格顯示內容型別自定義(文字,圖片,超連結等)
- 動態介面載入資料
- 表格和分頁聯動
- 分頁和查詢資料聯動
- 表格事件的處理
- className, width, height...
- 更多需求... 目前封裝的元件並不算完美,不可能滿足所以需求,這裡的話主要還是和大家分享思路
動態表頭和巢狀表頭的實現
實現動態表頭,這個應該是許多使用table的朋友們的痛點,明明是一樣的東西,卻要寫多個表格,實在不能忍,讓我們今天來一舉殲滅它。
分析表頭結構
el-table表頭有兩個必須的屬性,prop值和label名字,其他非必須的有fixed,align,width或者min-width等,那由此可設計出一個這樣的資料結構:
{
prop: 'name',
label: '名稱',
fixed: true/false,
align: 'center',
minWidth: 160
}
複製程式碼
進階->巢狀表格
上面我們得出了普通表頭列的設計,那我們繼續分析,看看巢狀表格配置多了哪些欄位。 根據element-ui官網文件,可以看到前面欄位基本一樣,巢狀表格多了children欄位,用來迴圈子級表頭,那由此我們可以設計出這樣的資料結構:
{
prop: 'name',
label: '名稱',
fixed: true/false,
align: 'center',
minWidth: 160,
children: [
{
prop: 'oldName',
label: '舊名稱',
fixed: true/false,
align: 'center',
minWidth: 160,
},
{
prop: 'newName',
label: '新名稱',
fixed: true/false,
align: 'center',
minWidth: 160,
}
]
}
複製程式碼
表頭設計總結
表頭設計思路大概是這樣,並不複雜,根據業務需求,大家都可以設計適合自己使用的欄位。
完整的表頭設計欄位應該大概會是這個樣子這個是個人欄位配置的例子,其中將prop欄位改成了value, 下面程式碼統一會使用value代替prop。
fieldList: [
{ label: '賬號', value: 'account' },
{ label: '使用者名稱', value: 'name' },
{ label: '所屬角色', value: 'role_name', minWidth: 120 },
{ label: '性別', value: 'sex', width: 80, list: 'sexList' },
{ label: '賬號型別', value: 'type', width: 100, list: 'accountTypeList' },
{ label: '狀態', value: 'status', width: 90, type: 'slot', list: 'statusList' },
{ label: '建立人', value: 'create_user_name' },
{ label: '建立時間', value: 'create_time', minWidth: 180 },
{ label: '更新人', value: 'update_user_name' },
{ label: '更新時間', value: 'update_time', minWidth: 180 }
]
複製程式碼
表格顯示內容型別自定義
表頭設計只是將一些基本的需求實現了,但是實際業務往往更為複雜,比如當前列要顯示的是圖片,tag,超連結,或者列的資料是一個id要顯示對應的label。
欄位列表擴充套件
之前定義的欄位列表都是簡單的文字顯示,當有了不同的型別顯示需求,則意味著需要一個型別欄位,type,根據業務需求,可以設計滿足image,tag,href等。 欄位設計為type為image時,同時可以考慮設計width和height欄位。 欄位設計為href時,可以同時設計顏色,跳轉方式欄位。 比如:
{label: '裝置資訊', prop: 'deviceInfo', type: 'href', herf: 'https://www.baidu.com', target: '_blank'},
{label: '裝置圖示', prop: 'deviceImage', type: 'image', src: 'https://www.baidu.com', height: '60px', width: 'auto'}
複製程式碼
當列的資料是一個id的時候需要顯示對應的label,情況又稍微複雜了一點,多種實現方法:
- 獲取到表格資料後對資料做處理,這個比較簡單,但需要在元件外部操作(不推薦)
- 將對應的列表傳入元件中,在元件內部進行轉換(推薦)
- 設定為slot(好用,但建議使用在複雜的自定義場景,這個在下面會細講) 講講第二種方式,將對應的列表傳入元件中,在元件內部進行轉換,需要設定當前欄位的型別為id轉換為label的型別,我在欄位上定義的是type: select,然後要定義相關的list,欄位設計大概長這樣:
{ label: '選單元件', value: 'component', type: 'select', list: 'componentList1' }
複製程式碼
我的實現方式是定義了一個listType物件,然後把頁面上用到的list都掛在了這個物件上面,將listType傳入到table元件中,通過listType[item.list]可以獲取到欄位對應列表然後獲取對應的label顯示。
slot
非常非常非常重要的slot,特別提醒大家,如果想寫複雜的元件,考慮到自定義型別,請一定去了解slot不瞭解的請戳 vue2.6+已經廢棄slot-scope官網api描述
插槽
- 父級可以向元件內部傳入dom,元件內部通過插槽接收
- 渲染方式1: dom使用父級資料渲染,傳入元件
- 渲染方式2: dom使用元件內部插槽穿出的資料渲染,再傳入元件
匿名插槽
父級在使用元件的時候,在元件標籤內編寫內容,將會元件內部接收到
具名插槽
父級設定傳入的插槽的名字,元件內部匹配到名字相同的插槽進行渲染。 元件內部具名插槽傳輸資料到父級(dom接收方,資料傳出方):
<!-- solt 自定義列-->
<template v-if="item.type === 'slot'">
<slot
:name="'col-' + item.value"
:row="scope.row"
/>
</template>
複製程式碼
父級獲取插槽資料渲染dom(dom傳出方,資料接收方):
<!-- 自定義插槽顯示狀態 -->
<template v-slot:col-status="scope">
<i
:class="scope.row.status === 1 ? 'el-icon-check' : 'el-icon-close'"
:style="{color: scope.row.status === 1 ? '#67c23a' : '#f56c6c', fontSize: '20px'}"
/>
</template>
複製程式碼
總結
slot是自定義元件的神器。 回到table元件,我們需要自定義顯示內容,設計的欄位應該如下:
{ label: '選單圖示', value: 'icon', type: 'slot' }
複製程式碼
動態介面載入資料
上面說的都是顯示欄位設計的東西,現在開始分析表格的資料,從哪裡來,到哪裡去。
如果要偷懶,那麼一定是要把懶偷到底的,有一丁點多餘的工作要做,都是偷懶不成功的。
元件內部載入資料
需要什麼:
- 介面
- 資料響應成功後在response的哪個欄位上面
- 怎麼重新整理介面
- 是否分頁,分頁初始化
介面
定義一個api欄位,將需要請求的介面傳入到元件中,如果有相關引數,需要同時將引數傳入到元件中
資料所在欄位
定義一個resFieldList,比如資料在res.content.data上,則傳入資料:
resFieldList: ['content', ‘data’] // 資料所在欄位
複製程式碼
元件內部則需要在介面請求成功之後做這樣一步操作:
let resData = res
const resFieldList = tableInfo.resFieldList
// 得到定義的響應成功的資料欄位
for (let i = 0; i < resFieldList.length; i++) {
resData = resData[resFieldList[i]]
}
複製程式碼
資料獲取成功之後,建議使用父子元件雙向通訊,.sync或者自定義model都可以實現,將資料派發到父元件,然後由父元件傳入子元件渲染元件。 直接由元件內部獲取資料並且渲染可能會需要擴充套件等問題限制元件的使用範圍。
重新整理介面
定義一個refresh欄位,重新整理頁面只需要設定為:
// 重新整理表格
tableInfo.refresh = Math.random()
複製程式碼
而元件內部watch欄位change,重新調獲取資料的介面,即可實現重新整理功能
分頁相關設定
- 是否分頁,設定欄位比如 pager: true/false
- 是否初始化分頁,設定欄位比如 initCurpage = Math.random() // 重新整理則重置
元件事件處理
分析有哪幾種型別的事件:
- 表頭點選事件
- 列點選事件
- 表格操作欄點選事件
- 多選
- ....
事件中介軟體的設計
不同的業務可能涉及到各種型別的事件,如果封裝成為了元件,怎麼處理??? 換一個思路,我們把事件看作是一個型別操作,比如點選是click,刪除是delete,那我們只需要一個事件轉發器,比如:
// 資料渲染事件的派發
this.$emit('handleEvent', 'list', arr)
// 表格選擇事件的派發
this.$emit('handleEvent', 'tableCheck', rows)
// 點選事件的派發
this.$emit('handleClick', event, data)
複製程式碼
我們定義事件中介軟體,元件內部發生事件時將事件的型別還有相關的資料派發,父級接收並且處理。
元件完整欄位和使用
欄位
- refresh 重新整理資料
- api 資料介面
- resFieldList 資料成功的響應欄位
- pager 是否分頁
- initCurpage 初始化分頁
- data 表格資料
- fieldList 欄位列表
- handle 操作欄配置
// 表格相關
tableInfo: {
refresh: 1,
initCurpage: 1,
data: [],
fieldList: [
{ label: '賬號', value: 'account' },
{ label: '使用者名稱', value: 'name' },
{ label: '所屬角色', value: 'role_name', minWidth: 120 },
{ label: '性別', value: 'sex', width: 80, list: 'sexList' },
{ label: '賬號型別', value: 'type', width: 100, list: 'accountTypeList' },
{ label: '狀態', value: 'status', width: 90, type: 'slot', list: 'statusList' },
{ label: '建立人', value: 'create_user_name' },
{ label: '建立時間', value: 'create_time', minWidth: 180 },
{ label: '更新人', value: 'update_user_name' },
{ label: '更新時間', value: 'update_time', minWidth: 180 }
],
handle: {
fixed: 'right',
label: '操作',
width: '280',
btList: [
{ label: '啟用', type: 'success', icon: 'el-icon-albb-process', event: 'status', loading: 'statusLoading', show: false, slot: true },
{ label: '編輯', type: '', icon: 'el-icon-edit', event: 'update', show: false },
{ label: '刪除', type: 'danger', icon: 'el-icon-delete', event: 'delete', show: false }
]
}
}
複製程式碼
使用
<!-- 表格 -->
<page-table
:refresh="tableInfo.refresh"
:init-curpage="tableInfo.initCurpage"
:data.sync="tableInfo.data"
:api="getListApi"
:query="filterInfo.query"
:field-list="tableInfo.fieldList"
:list-type-info="listTypeInfo"
:handle="tableInfo.handle"
@handleClick="handleClick"
@handleEvent="handleEvent"
>
<!-- 自定義插槽顯示狀態 -->
<template v-slot:col-status="scope">
<i
:class="scope.row.status === 1 ? 'el-icon-check' : 'el-icon-close'"
:style="{color: scope.row.status === 1 ? '#67c23a' : '#f56c6c', fontSize: '20px'}"
/>
</template>
<!-- 自定義插槽狀態按鈕 -->
<template v-slot:bt-status="scope">
<el-button
v-if="scope.data.item.show && (!scope.data.item.ifRender || scope.data.item.ifRender(scope.data.row))"
v-waves
size="mini"
:type="scope.data.row.status - 1 >= 0 ? 'danger' : 'success'"
:icon="scope.data.item.icon"
:disabled="scope.data.item.disabled"
:loading="scope.data.row[scope.data.item.loading]"
@click="handleClick(scope.data.item.event, scope.data.row)"
>
{{ scope.data.row.status - 1 >= 0 ? '停用' : '啟用' }}
</el-button>
</template>
</page-table>
複製程式碼