二次封装的el-table支持多级表头

1,660 阅读2分钟

前言

上回二次封装的el-table组件(详情可查看文章:二次封装el-tble组件)暂不支持多级表头的情况(感谢@用户3318846632560),本次修复这个问题。

一、问题分析

参考el-table组组件官方文档多级表头的实现(官网文档),其实就是el-table-column嵌套来实现的,所以我们可以用递归组件来实现(可以参照之前的文档《递归组件实现配置化菜单栏》)。vue中递归组件实现也很简单,类似于递归函数,其实就是组件内部自己调用自己。那么下面我们就尝试用递归组件来实现。

image.png

二、实现

我们可以单独将需要递归调用的el-table-column抽出来。

这里需要注意的是:

1.el-table组件内部用el-table-column的时候不能用其他实体元素套在外面,否则会有表格列顺序错乱的问题,所以我们这里用el-table-column套el-table-column来实现;

2.递归组件要做一些判断,只有当配置的数组中有children或children数组有元素的时候,才需要递归调用;

3.在递归调用的时候,需要将插槽透传进去,否则就无法实现具名插槽和作用域插槽的效果了;

4.每次调用的时候,还需要将children当做columns属性传进去。

抽出来的el-table-column代码如下:

<template>
    <el-table-column class="my-column">
        <template v-for="item in columns">
            <el-table-column v-if="item.children && item.children.length" :label="item.label" :key="item.prop">
                <new-table-column
                    :columns="item.children || []"
                >
                    <template v-for="(index, name) in $slots" :slot="name">
                        <slot :name="name"></slot>
                    </template>
                    <template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
                        <slot :name="name" v-bind="data"></slot>
                    </template>
                </new-table-column>
            </el-table-column>
            <el-table-column
                v-else-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}-else`"
                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-column>
</template>

<script>
export default {
    name: 'newTableColumn',
    props: {
        columns: {
            type: Array,
            default: () => ([])
        }
    },
}
</script>

<style scoped lang="scss">
</style>

new-table组件使用:

new-table改版关键代码如下

需要注意:

1.因为子组件NewTableColumn是循环递归实现的,所以这里不能直接在vue的components中注册为组件直接使用,否则会报组件未注册成功的错误,错误内容如下:

image.png

那么如何解决呢?我们可以后续在data中引用,再用component组件使用;也可以在beforeCreated生命周期中注册。

2.在使用抽出的el-table-column组件时,还要注意将插槽透传进去,否则插槽会无法使用;

3.其次还要注意一点,因为el-table-column单独抽出来了,是套在另一个el-table-column中的,所以会导致多出空白的表头,这里可以用hack的方式处理一下,将奇数元素的tr给隐藏掉,css代码如下:

.my-table ::v-deep .is-group tr:nth-child(odd) { display: none; }

暂时没有想到更好的方法,大家有什么好的想法也欢迎补充~

		<el-table
			class="my-table"
			v-loading="loading"
			ref="table"
			:data="tempData"
			:header-cell-style="headerCellStyle"
			:cell-style="cellStyle"
			v-bind="$attrs"
			v-on="$listeners"
		>
			<template slot="append">
				<slot name="append"></slot>
			</template>
			<component :is="newTableColumn" :columns="columns">
				<template v-for="(index, name) in $slots" :slot="name">
					<slot :name="name"></slot>
				</template>
				<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
					<slot :name="name" v-bind="data"></slot>
				</template>
			</component>
		</el-table>
                
                <script>
                    import NewTableColumn from './new-table-column.vue'
                    
                    export default {
                        data() {
                            return {
                                newTableColumn: NewTableColumn,
                            }
                        }
                    }
                </script>

配置column列

const columns = [{
        prop: 'selection',
        type: 'selection',
        width: 80
    },{
        prop: 'date',
        label: '日期',
        width: 180,
        sortable: true,
    },{
        prop: 'sendInfo',
        label: '配送信息',
        children: [
            {
                prop: 'name',
                label: '名字',
                width: 200,
            },
            {
                prop: 'addressInfo',
                label: '地址',
                children: [
                    {
                        prop: 'province',
                        label: '省市',
                        width: 120
                    },
                    {
                        prop: 'city',
                        label: '市区',
                        width: 120
                    },
                    {
                        prop: 'address',
                        label: '地址',
                        width: 200
                    }
                ]
            }
        ]
    },{
        prop: 'operate',
        label: '操作',
    }]

效果

image.png

三、结论

以上,我们就使封装的组件支持多级表头了~

github链接:github.com/qiangguangl…

其实,也可以单独的用jsx的语法实现,这样就不用单独抽一个vue组件了,直接递归方法返回dom元素,然后在render中渲染即可。