前言
上回二次封装的el-table组件(详情可查看文章:二次封装el-tble组件)暂不支持多级表头的情况(感谢@用户3318846632560),本次修复这个问题。
一、问题分析
参考el-table组组件官方文档多级表头的实现(官网文档),其实就是el-table-column嵌套来实现的,所以我们可以用递归组件来实现(可以参照之前的文档《递归组件实现配置化菜单栏》)。vue中递归组件实现也很简单,类似于递归函数,其实就是组件内部自己调用自己。那么下面我们就尝试用递归组件来实现。
二、实现
我们可以单独将需要递归调用的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中注册为组件直接使用,否则会报组件未注册成功的错误,错误内容如下:
那么如何解决呢?我们可以后续在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: '操作',
}]
效果
三、结论
以上,我们就使封装的组件支持多级表头了~
github链接:github.com/qiangguangl…
其实,也可以单独的用jsx的语法实现,这样就不用单独抽一个vue组件了,直接递归方法返回dom元素,然后在render中渲染即可。