在使用ElementUI過程當中,表格的使用佔了很大一部分,可是使用起來總感受不方便,並且部分想要的功能也沒有。這促使我在ElementUI 表格組件上再進行一次封裝。在這基礎上添加所需功能,而且要使用更方便,可是不能破壞原有功能。javascript
此項目已經上傳GitHub,歡迎交流,但願能給個star鼓勵一下,謝謝^-^。 在線Demohtml
由於代碼太多因此沒有所有放,只抽出部分講解,感興趣的小夥伴能夠到GitHub下載一份源碼。vue
將原有的html結構變爲數據結構,只用寫一個數組傳入就可生成想要的結構java
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="日期" width="150"></el-table-column>
<!-- 多級表頭 -->
<el-table-column label="配送信息">
<el-table-column prop="name" label="姓名" width="120"></el-table-column>
<el-table-column label="地址">
<el-table-column prop="province" label="省份" width="120"></el-table-column>
<el-table-column prop="city" label="市區" width="120"></el-table-column>
<el-table-column prop="address" label="地址" width="300"></el-table-column>
<el-table-column prop="zip" label="郵編" width="120"></el-table-column>
</el-table-column>
</el-table-column>
</el-table>
</el-table>
複製代碼
//html e-table爲封裝好的組件名
<e-table :data="tableDate" :columns="columns"></e-table>
//數據
columns: [
{
prop: "date",
label: "日期",
width: 150
},
{
label: "配送信息",
childrens: [
{
label: "地址",
childrens: [
{
label: "省份",
prop: "province",
with:120
},
{
label: "市區",
prop: "city"
}
...
]
}
]
}
]
複製代碼
這樣數據化以後能夠更方便操做,結構也更清晰。每個el-table-column
對應columns
數組中的一個元素,能夠嵌套實現多級表頭。元素裏面能夠包含全部el-table-column
上的屬性。git
經過render
函數循環嵌套生成,也能夠寫.vue
的單文件用html結構生成,可是沒有render
函數生成可操控性高,render
函數能夠更加精確的操控元素,也更加貼近編譯器。還沒用過的同窗趕快學習一下把 rendergithub
// render 函數部分代碼
render(h){
let _this = this,
columnRender = function(col,h){
if(!hasOwn(col, 'childrens')){
.... // 主要內容 單獨講
}else{
if (Array.isArray(col.childrens) && col.childrens.length > 0) {
return h('el-table-column', {
attrs: {
label: col.label || col.prop
}
}, [...col.childrens.map(function (column) { //遞歸
return columnRender(column, h)
})])
}
return null;
}
}
return h('el-table', {
ref: 'elTable',
props: {
...this.$attrs, //傳遞全部綁定的屬性
// 添加功能須要用到屬性,覆蓋,並將這些屬性做爲props接受,而後再在內部添加,保證功能不缺失
rowStyle: this.rowStyle_,
cellClassName: this.cellClassName_,
rowClassName: this.rowClassName_,
},
on: {
...this.$listeners, // 傳遞監聽的全部事件
...this.eListeners // 添加功能所需,同理 在內部再添加該事件,保證不缺失功能
},
scopedSlots: { //保留其具名插槽
empty: function () {
return _this.$slots.empty
},
append: function () {
return _this.$slots.append
}
},
}, [ this.columns.map(function (col) { // 渲染轉遞過來的columns數組
return columnRender(col, h) //循環遞歸column 單獨抽離爲一個函數(定義在上面)
}),
this.$slots.default // 保留默認的html寫法
])
}
複製代碼
網上不少的ElementUI表格可編輯表格教程都是一整行的切換編輯狀態,這不是我想要的。應該是每一個單元格均可以控制編輯狀態api
//添加編輯功能 所添加的屬性
columns:[
{
prop:'name',
label:'姓名',
edit:true, //該列開啓可編輯模式
editType:'selection', //選擇編輯形式,默認default,即input框 能夠不寫該屬性
editControl:function(value,row,column){return true} ,//更精確控制該列每一個單元格是否可編輯
editComponent:customCompontent,//能夠傳入自定義編輯組件
editAttrs:{size:'mini',...},// 能夠傳入屬性
editListeners: { focus:function(){},...} //可接受事件
}
]
複製代碼
// 主要代碼
if (!hasOwn(col, 'childrens')) {
...
return h('el-table-column',{
props: {
...col //賦予屬性
},
scopedSlots:{
default:function (props) { //改寫默認內容
let {
row,
column
} = props;
let funControl = isFunction(col.editControl) ? col.editControl.call(null, row[col.prop], row, column) : true, //控制每一個單元格的可編輯性
isCan = (_this.tableConfig.cellEdit !== false && col.edit && funControl) ? true : false;
isCan ? _this.setEditMap({ //生成可編輯表格的具體位置集合 點擊單元格時匹配,顯示編輯框
x: row.rowIndex,
y: column.property
}) : null;
if (isCan && _this.editX === row.rowIndex && _this.editY === column.property){//點擊單元格知足條件顯示編輯框
let options = { // .... props 和 listeners 的綁定
attrs: {
...col.editAttrs
},
props: { //給編輯組件傳遞數據
value_: row[col.prop],
column: column,
columnObj: col,
row: row
},
on: {
...col.editListeners,
setValue: (v) => { //改變單元格原數據,自定義編輯組件也應提供這個事件來改變原數據
row[col.prop] = v
},
change: (v) => { //覆蓋change事件,轉移到table主體事件
_this.$emit('cell-value-change', v, row, column, col)
}
},
if (col.editComponent && typeof col.editComponent === 'object') { //使用自定義編輯組件
return h(col.editComponent, options)
} else { //使用內部自帶編輯組件
return h(editComponents[col.editType || 'default'], options)
}
} else {
//... 默認內容
}
}
}
})
}
複製代碼
在使用html結構時能夠使用做用域插槽進行自行自定義顯示,而如今數據化結構後只能使用使用數據化方式,最爲合理且惟一的應該就是調用render
函數渲染自定義內容了。(若是寫成字符串解析成html,有不少問題,沒法使用組件,沒法綁定事件...)數組
columns:[
{
prop:'render',
label:'自定義顯示',
renderCell:function(h,{value,row,column}){ //自定義渲染內容
return h('el-button',
{
attrs:{size:'mini'},
on:{
click:e=>{...},
...
}
...
},'自定義')
} //自定義渲染內容
formatter:function(value){return `<b>${value}</b>`}//格式化內容,可寫入html字符串
}
]
複製代碼
//主要代碼 接可編輯代碼
if (isCan && _this.editX === row.rowIndex && _this.editY === column.property){
....
}else{
if (col.renderCell && typeof col.renderCell === 'function') { //顯示自定義內容
return col.renderCell.call(null, h, { // 實現render功能
value: row[col.prop],
row: row,
column: column,
})
} else { //顯示默認內容
return h('span', {
domProps: { //將內容做爲html解析
innerHTML: (col.formatter && typeof col.formatter === 'function') ?
col.formatter(row[col.prop]) : row[col.prop]
}
})
}
}
複製代碼
ElementUI 表格自帶自由兩種下拉篩選形式的選擇並且須要先給定值,不能知足下拉篩選數據異步從服務器拿數據的需求。因此爲了保留原來自帶的下拉功能,就要將這兩種方式區別開來。添加defaultHeader
屬性,爲true則使用默認列表頭,不然使用自定義表頭。bash
//<e-table :columns="columns" :getFilters="getFilters"></e-table>
getFilters:function(){ //總的獲取下拉數據異步函數 需返回Promise.resolve(data)
return new Promse((resolve,reject)=>{
req('/api/getFilters').then(res=>{
resolve(res.data)
})
})
},
columns:[
{
prop:'filter',
label:'自定義下拉篩選',
defaultHeader:false, // 默認爲false 爲true時自定義篩選無用,顯示爲默認列表頭 添加filters數組屬性則使用默認表格下拉篩選
filter:true, //開啓過濾
filterType:'selection', //內置下拉篩選類型 默認selection 多選
filterComponent:customCp //自定義下拉篩選組件
getFilters:function(){ //可控制獲取每一個列的下拉篩選數據
return new Promise(....)
},
filterAttrs:{ //傳遞屬性
size:'mini',
...
},
filterListeners:{ //綁定事件
change:function(){}
...
},
}
]
複製代碼
由於下拉內容爲獨立出來的部分,因此使用el-popover
組件組爲載體,顯示下拉篩選內容,將popover單獨封裝爲組件當點擊下拉篩選按鈕時再實例化顯示它。服務器
// 點擊按鈕時主要代碼
async headFilterBtnClick(columnObj, column, event) {
let colKey = column.columnKey || column.property || column.id;
if (this.filterLoads.some(fd => fd === colKey)) return; //已在loading狀態點擊無效
const target = event.target;
let cell = target.tagName === 'I' ? target : target.parentNode,
filterPanel = this.filterPanels[colKey],
filtersData = [];
cell = cell.querySelector('.e-filter-tag') || cell;
if (filterPanel && this.headFCNs.some(f => f === colKey)) { // 已經存在過濾面板且已打開面板
filterPanel.doClose()
return
}
this.filterLoads.push(colKey) //顯示loading
try { //await異步獲取過濾數據時 捕獲異常
if (columnObj.getFilters && typeof columnObj.getFilters === 'function') {
filtersData = (await columnObj.getFilters(columnObj, column)) || [] //每一列可單獨異步獲取下拉數據
} else if (this.getFilters) {
filtersData = (await this.getFilters(columnObj, column)) || [] //下拉數據獲取
}
} catch (error) {
this.filterLoads.splice(this.filterLoads.findIndex(fd => fd === colKey), 1)
throw new Error(error)
return
}
if (filterPanel) { //存在但當前未打開
this.filters = filtersData;
filterPanel.filtedList = this.filtedList;
filterPanel.filters = filtersData;
this.filterLoads.splice(this.filterLoads.findIndex(fd => fd === colKey), 1);
filterPanel.doShow();
return
}
if (!filterPanel) { //不存在過濾面板
filterPanel = new Vue(FilterPanel) //實例化popover組件
this.filterPanels[colKey] = filterPanel //將每一個下拉的popover保存到table內部方便操做
filterPanel.reference = cell //popover顯示依賴
filterPanel.columnId = colKey //傳遞數據
filterPanel.column = column
filterPanel.columnObj = columnObj
filterPanel.table = this._self
filterPanel.filters = filtersData,
filterPanel.filtedList = this.filtedList,
filterPanel.$mount(document.createElement('div')); //掛載
this.filterLoads.splice(this.filterLoads.findIndex(fd => fd === colKey), 1)
filterPanel.doShow() //顯示組件
}
},
複製代碼
render(h) {
let _this = this
return h('el-popover', {
props: {
reference: this.reference,
},
on: {
hide: this.hide,
show: this.show,
},
ref: "filterPane",
scopedSlots: {
default: function (props) {
let options = {
attrs: {
..._this.columnObj.filterAttrs //傳遞屬性
},
props: {
filters: _this.filters,
filtedList: _this.filtedList,
column: _this.column,
columnObj: _this.columnObj,
showPopper: _this.showPopper
},
on: {
..._this.columnObj.filterListeners, //傳遞事件
filterChange: _this.filterChange //數據改變時觸發
}
};
if (_this.columnObj.filterComponent && typeof _this.columnObj.filterComponent === 'object') { //自定義下拉篩選組件
return h(_this.columnObj.filterComponent, options)
}
//內置下拉篩選組件
return h(filterComponents[_this.columnObj.filterType || 'selection'], options)
}
}
})
//主要事件,將數據傳給table
filterChange(value, columnObj, column) {
this.table.filterChange(value, columnObj, column)
this.doClose() //關閉面板
},
複製代碼
這個內容在以前已經單獨講過,Element UI 表格點擊選中行/取消選中 快捷多選 以及快捷連續多選,高亮選中行;
修改這些功能主要靠render
函數的特性,雖然結構看起來不宜讀(可採用jsx語法),可是可以更好的完成需求,也算將功補過吧,總的來講完成的還算比較滿意。若是有什麼建議,或則有什麼更好的方式完成這些需求,歡迎交流。