Element ui表格組件修改,實現可編輯單元格,自定義下拉篩選,數據化結構,快捷選擇。

前言

在使用ElementUI過程當中,表格的使用佔了很大一部分,可是使用起來總感受不方便,並且部分想要的功能也沒有。這促使我在ElementUI 表格組件上再進行一次封裝。在這基礎上添加所需功能,而且要使用更方便,可是不能破壞原有功能。javascript

此項目已經上傳GitHub,歡迎交流,但願能給個star鼓勵一下,謝謝^-^。 在線Demohtml

效果

編輯-選擇

篩選

主要內容

  • 數據化結構(減小html代碼)
  • 可編輯單元格(同時知足不一樣編輯形式)
  • 自定義顯示單元格內容 (el-table-column做用域插槽的數據結構版)
  • 自定義下拉篩選(獲取遠程下拉篩選條件,自定義下拉篩選形式)
  • 選擇數據簡單化(單擊選擇/取消,快捷多選,連續多選)
  • 保證原有功能

由於代碼太多因此沒有所有放,只抽出部分講解,感興趣的小夥伴能夠到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() //顯示組件
            }

        },

複製代碼
  • filterPanel組件組要代碼
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語法),可是可以更好的完成需求,也算將功補過吧,總的來講完成的還算比較滿意。若是有什麼建議,或則有什麼更好的方式完成這些需求,歡迎交流。

相關文章
相關標籤/搜索