二次封装el-table组件

4,903 阅读3分钟

前言

平时在使用el-tabl的时候,经常要写el-table又要写el-table-column,有分页的时候还要加上el-pagination,实在是挺烦的,影响开发效率,那么我们是否可以二次封装组件,满足我们想要的一些能力,但又想保留原有组件的能力,那么就可以使用上回讲解到的组件能力的透传。

一、分析

因为想要将el-table、el-table-column还有el-pagination三个组件封装在一起,而又想保留原有组件的能力,那就需要将组件的属性、事件插槽分开,el-table-column很容易想到循环遍历,而要将el-pagination的属性分开,可以单独以pagination用props接收,单独透传给el-pagination。

二、实现

基于以上简单的思考,就可以动手实现啦~

new-table组件

通过$atrrs将父组件没有通过props接受的属性以及$lisentners将父组件的事件监听透传给el-table,headerCellStyle,cellStyle使用props接受(这样的话$attrs就不会存在这两个属性了),是用于给其默认样式,也提供父组件修改的能力;$slots.append用于暴露原有的append插具名插槽。

el-table-column是通过遍历父组件传递的columns渲染的,这里将type为selection以及index的单独抽出来写是为了解决,与其他column一起写,selection复选框和序号回显为空的问题。并且暴露了组件原有的header插槽,渲染表头,与其他column一样,暴露的header插槽重命名为item.prop,因为父组件使用的时候,唯一确定插槽是要插在哪一列;通过将columns中每一项的item作为属性透传给el-table-column,实现继承原有组件的属性,同时每一列的单元格的插槽是通过item.prop暴露的,这样父组件就可以自定义每列的格式了。

el-pagination的属性是继承的父组件传递的pagination,并且通过defaultPagination设置了一些属性的默认值(用于默认样式调整),并不封闭父组件使用这些属性。通过sizeChange、currentChange事件实现table翻页(注意el-pagination原有的事件就没有暴露了)。

在mounted中通过遍历el-table组件实例,将function类型的方法继承给了新的new-table组件(当然也可以通过el-table实例,一个个的继承方法,但需要枚举,比较麻烦)。

<el-table
    ref="table"
    :data="tempData"
    :header-cell-style="headerCellStyle"
    :cell-style="cellStyle"
    v-bind="$attrs"
    v-on="$listeners"
>
    <template v-if="$slots.append" slot="append">
            <slot name="append"></slot>
    </template>
    <template v-for="item in columns">
            <el-table-column
                    v-if="['selection', 'index'].includes(item.prop)"
                    :key="`${item.prop}-if`"
                    v-bind="item"
            >
                    <template v-slot:header="scope">
                            <slot :name="`header-${item.prop}`" v-bind="scope"></slot>
                    </template>
            </el-table-column>
            <el-table-column
                    v-else
                    :key="item.prop"
                    v-bind="item"
            >
                    <template v-slot:header="scope">
                            <span v-if="$scopedSlots[`header-${item.prop}`]">
                                    <slot :name="`header-${item.prop}`" v-bind="scope"></slot>
                            </span>
                            <span v-else>{{scope.column.label}}</span>
                    </template>
                    <template slot-scope="scope">
                            <span v-if="$scopedSlots[item.prop]">
                                    <slot :name="item.prop" v-bind="scope"></slot>
                            </span>
                            <span v-else>{{scope.row[item.prop]}}</span>
                    </template>
            </el-table-column>
    </template>
</el-table>
<el-pagination 
    class="pagination"
    v-bind="tempPagination"
    :current-page="currentPage"
    :page-size="pageSize"
    :total="total"
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
></el-pagination>
const defaultPagination = {background: true, layout: 'total, sizes, prev, pager, next, jumper'}
export default {
name: 'newTable',
props: {
        columns: {
                type: Array,
                default: () => []
        },
        pagination: {
                type: Object,
                default: () => ({})
        },
        data: {
                type: Array,
                default: () => []
        },
        headerCellStyle: {
                type: Function || Object,
                default: () => {
                        return {
                                'background-color': '#f5f6f7',
                                'font-size': '12px',
                                'padding': '10px',
                        }
                }
        },
        cellStyle: {
                type: Function || Object,
                default: () => {
                        return {
                                'font-size': '12px',
                                'padding': '10px'
                        }
                }
        },
},
data() {
        return {
                currentPage: 1,
                pageSize: 10,
                tempData: [],
        }
},
computed: {
        paging() {
                const offset = (this.currentPage - 1) * this.pageSize
                return { offset, limit: this.pageSize }
        },
        total() {
                return this.data?.length || 0
        },
        tempPagination() {
                return {...defaultPagination, ...this.pagination}
        }
},
watch: {
        pagination: {
                handler(nVal) {
                        this.currentPage = nVal.currentPage || 1
                        this.pageSize = nVal.pageSize || nVal.pageSizes?.[0] || 10
                },
                immediate: true,
                deep: true,
        },
        paging: {
                handler() {
                        this.getTableData()
                },
                immediate: true,
                deep: true,
        }
},
mounted() {
        const tempStore = this.$refs?.table || {}
        for(const key in tempStore) {
                if(typeof tempStore[key] === 'function') {
                        this[key] = tempStore[key]
                }
        }
},
methods: {
        handleSizeChange(val) {
                this.pageSize = val;
                this.getTableData();
        },
        handleCurrentChange(val) {
                this.currentPage = val;
                this.getTableData();
        },
        getTableData() {
                const { offset, limit } = this.paging || {};
                this.tempData = this.data.filter((v, i) => i >= offset && i < (offset + limit))
        },
}

}

父组件使用

父组件的使用如下,其中使用了columns传递了el-table-column使用的数据,每一项是使用的属性;pagination传递给el-pagination使用,通过header-name填充了name列的表头插槽,operate使用了operate列的作用域插槽,date插槽改写了date列数据,append插槽填充了原有的append插槽;并且通过两个按钮,调用了clearSelectionclearSort方法,用于清除选择和清除排序。

<div class="button_group">
    <el-button type="primary" size="small" @click="clearSelect">清除选择</el-button>
    <el-button type="primary" size="small" @click="clearSort">清除排序</el-button>
</div>
<new-table
    ref="table"
    :columns="columns"
    :data="tableData"
    style="width: 100%"
    :default-sort = "{prop: 'date', order: 'descending'}"
    border
    :pagination="{
                    pageSizes: [2,5],
    }"
>
    <template slot="header-name" slot-scope="scope">
        <el-input v-model="search" size="mini" :placeholder="`输入关键字搜索<${scope.column.label}>`"></el-input>
    </template>
    <template slot="operate">
        <el-button size="mini" type="primary">操作</el-button>
    </template>
    <template slot="date" slot-scope="scope">
        我是日期:{{scope.row.date}}
    </template>
    <template slot="append">
        <span style="padding:20px">我是append插槽</span>
    </template>
</new-table>
export default {
    components: {
        NewTable
    },
    data() {
        return {
            columns,
            tableData,
            search: ''
        }
    },
    methods: {
        clearSelect() {
            this.$refs.table.clearSelection()
        },
        clearSort() {
            this.$refs.table.clearSort()
        }
    }

}

js文件的配置内容

const columns = [{
        prop: 'selection',
        type: 'selection',
        width: 80
    },{
        prop: 'date',
        label: '日期',
        width: 180,
        sortable: true,
    },{
        prop: 'name',
        label: '名字',
        width: 200
    },{
        prop: 'address',
        label: '地址',
    },{
        prop: 'operate',
        label: '操作'
    }]
    
const tableData = [{
    date: '2016-05-02',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1518 弄'
  }, {
    date: '2016-05-04',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1517 弄'
  }, {
    date: '2016-05-01',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1519 弄'
  }, {
    date: '2016-05-03',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1516 弄'
  }]

效果

image.png

三、总结

以上就是封装el-table组件的过程,后续我们就可以用这个保留了原有组件能力的自定义的组件进行开发了,显著提升了开发效率有木有~

附上github链接: new-table组件:github.com/qiangguangl…

父组件使用:github.com/qiangguangl…

下期预告:

有点累了歇一歇,下一期就讲解下两种简单实现v-model的语法糖的方法吧~