Vue el-table封装,支持多级表头,自动高度
实现多级表头
废话不多说,正式开始,首先既然要实现多级表头那么就有两种实现方式,要么是递归子组件,要么使用render渲染,这里我就用递归子组件的方式去实现。通过判断是否存在 children
字段来递归遍历el-table-column
<el-table-column
v-if="item.children && item.children.length"
:label="item.label"
:key="item.prop"
>
<table-column :columns="item.children || []">
<template
v-for="(index, name) in $slots"
:slot="name"
>
<slot :name="name" />
</template>
<template
v-for="(index, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
/>
</template>
</table-column>
</el-table-column>
作用域插槽通过下面的方式进行参数传递。
<template
v-for="(index, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
/>
</template>
props传参
既然是封装el-table,那么我的想法是尽可能的保留官方的参数,减少使用的心智负担,下面是无孩子时候渲染的el-table-column
,默认加入了文字过长显示tooltip选项,同时要对指定列使用插槽则是根据prop的字段,prop叫什么插槽名称就叫什么:name="item.prop"
,插入该列的头就是在前面加入header即可。header-${item.prop}
,同时加入了render渲染,可以在选项中直接写render函数进行渲染。
<el-table-column
v-else
:key="`${item.prop}-else`"
v-bind="{
'show-overflow-tooltip': true,
...item,
}"
>
<template #header="scope">
<BaseRender
v-if="item.headerRender"
:render="item.headerRender"
:item="item"
:data="scope.row"
:scope="scope"
/>
<span v-else-if="$scopedSlots[`header-${item.prop}`]">
<slot
:name="`header-${item.prop}`"
v-bind="scope"
/>
</span>
<span v-else>{{ scope.column.label }}</span>
</template>
<template slot-scope="scope">
<BaseRender
v-if="item.render"
:render="item.render"
:item="item"
:data="scope.row"
:scope="scope"
/>
<span v-else-if="$scopedSlots[item.prop]">
<slot
:name="item.prop"
v-bind="scope"
/>
</span>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
el-table默认开启了stripe
和border
属性,头样式和行样式有一些默认值,这些可以根据自己的项目变换,除了这三个外所有的属性与官网一致。
下面是封装的el-table的props,其他传参与el-table官方一致
props | 含义 | 类型 | 默认值 |
---|---|---|---|
columns | 表格头列表 | Array | [] |
data | 表格数据 | Array | [] |
autoHeight | 是否开启自动高度 | Boolean | true |
<el-table
class="base-table"
ref="baseTableRef"
:data="data"
:header-cell-style="headerCellStyle"
:cell-style="cellStyle"
v-bind="{
height: autoHeight ? tableHeight : '300px',
stripe: true,
border: true,
...$attrs,
}"
v-on="$listeners"
>
要开启index和selection列只需要在columns中配置这两项
{
type: 'index'
},
{
type: 'selection'
},
实现自动高度
自动高度的实现主要是通过观测高度的变化对高度进行准确赋值。
if (!this.autoHeight) return
const resizeObserver = new ResizeObserver((entries) => {
const { height } = entries.shift().contentRect
this.tableHeight = height
})
resizeObserver.observe(this.$el.parentElement)
this.$once('hook:beforeDestroy', () => {
resizeObserver.disconnect()
})
使用举例
<template>
<div class="base-table">
<BaseTable
:columns="columns"
:data="tableData"
/>
</div>
</template>
<script>
import BaseTable from '@/components/BaseTable/index.vue'
export default {
components: {
BaseTable
},
data () {
return {
columns: [
{
type: 'index'
},
{
type: 'selection'
},
{
prop: 'name',
label: '姓名'
},
{
prop: 'age',
label: '年龄'
},
{
prop: 'sex',
label: '性别'
}
],
tableData: []
}
},
mounted () {
this.initData()
},
methods: {
initData () {
this.tableData = this.generateData()
},
generateData (length = 60) {
return Array.from({ length }).map((it, idx) => {
return {
name: 'name' + idx,
sex: 'test2',
age: idx
}
})
}
}
}
</script>
对名称为name的列使用插槽:内容和头
<BaseTable
:columns="columns"
:data="tableData"
>
<template #header-name>
name的头
</template>
<template #name>
name插槽test
</template>
</BaseTable>
或者使用render的方式
{
prop: 'name',
label: '姓名',
headerRender:(h,{ data }) => {
return (
<span>{data.name + '头'}</span>
)
},
render:(h,{ data }) => {
return (
<span>{data.name}</span>
)
}
}
多级表头写法:通过children字段,数组里面的对象跟columns的格式一样
要对姓名1使用插槽也只需要跟上面一样即可
columns: [
{
type: 'index'
},
{
type: 'selection'
},
{
prop: 'name1',
label: '姓名',
children: [
{
prop: 'name',
label: '姓名1'
},
{
prop: 'name2',
label: '姓名2'
},
{
prop: 'name3',
label: '姓名3'
}
]
},
{
prop: 'age',
label: '年龄'
},
{
prop: 'sex',
label: '性别'
}
]
下面是完整的代码:
Vue2版本
index.vue
<template>
<el-table
class="base-table"
ref="baseTableRef"
:data="data"
:header-cell-style="headerCellStyle"
:cell-style="cellStyle"
v-bind="{
height: autoHeight ? tableHeight : '300px',
stripe: true,
border: true,
...$attrs,
}"
v-on="$listeners"
>
<template slot="append">
<slot name="append" />
</template>
<template v-for="item in columns">
<!-- selection和index的列渲染 -->
<el-table-column
v-if="['selection', 'index'].includes(item.type)"
:key="`${item.type}`"
v-bind="item"
>
<template #header="scope">
<slot
:name="`header-${item.type}`"
v-bind="scope"
/>
</template>
</el-table-column>
<TableColumn
v-else-if="item.children && item.children.length"
:columns="item.children"
:label="item.label"
:key="item.prop"
>
<template
v-for="(index, name) in $slots"
:slot="name"
>
<slot :name="name" />
</template>
<template
v-for="(index, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
/>
</template>
</TableColumn>
<el-table-column
v-else
:key="`${item.prop}-else`"
v-bind="{
'show-overflow-tooltip': true,
...item,
}"
>
<template #header="scope">
<BaseRender
v-if="item.headerRender"
:render="item.headerRender"
:item="item"
:data="scope.row"
:scope="scope"
/>
<span v-else-if="$scopedSlots[`header-${item.prop}`]">
<slot
:name="`header-${item.prop}`"
v-bind="scope"
/>
</span>
<span v-else>{{ scope.column.label }}</span>
</template>
<template slot-scope="scope">
<BaseRender
v-if="item.render"
:render="item.render"
:item="item"
:data="scope.row"
:scope="scope"
/>
<span v-else-if="$scopedSlots[item.prop]">
<slot
:name="item.prop"
v-bind="scope"
/>
</span>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
import TableColumn from './table-column.vue'
import BaseRender from './BaseRender.js'
export default {
name: 'BaseTablePro',
components: {
TableColumn,
BaseRender
},
props: {
columns: {
type: Array,
default: () => []
},
data: {
type: Array,
default: () => []
},
headerCellStyle: {
type: Function || Object,
default: () => {
return {
'background-color': '#f5f6f7',
'text-align': 'center'
}
}
},
cellStyle: {
type: Function || Object,
default: () => {
return {
'text-align': 'center'
}
}
},
autoHeight: {
type: Boolean,
default: true
}
},
data () {
return {
tableHeight: null
}
},
mounted () {
this.initTableHeight()
},
methods: {
initTableHeight () {
if (!this.autoHeight) return
const resizeObserver = new ResizeObserver((entries) => {
const { height } = entries.shift().contentRect
this.tableHeight = height
})
resizeObserver.observe(this.$el.parentElement)
this.$once('hook:beforeDestroy', () => {
resizeObserver.disconnect()
})
}
}
}
</script>
<style lang="scss" scoped></style>
table-column.vue
<template>
<el-table-column
v-bind="$attrs"
v-on="$listeners"
>
<template v-for="item in columns">
<el-table-column
v-if="item.children && item.children.length"
:label="item.label"
:key="item.prop"
>
<table-column :columns="item.children || []">
<template
v-for="(index, name) in $slots"
:slot="name"
>
<slot :name="name" />
</template>
<template
v-for="(index, name) in $scopedSlots"
#[name]="data"
>
<slot
:name="name"
v-bind="data"
/>
</template>
</table-column>
</el-table-column>
<el-table-column
v-else
:key="`${item.prop}-else`"
v-bind="{
'show-overflow-tooltip': true,
...item,
}"
>
<template #header="scope">
<BaseRender
v-if="item.headerRender"
:render="item.headerRender"
:item="item"
:data="scope.row"
:scope="scope"
/>
<span v-else-if="$scopedSlots[`header-${item.prop}`]">
<slot
:name="`header-${item.prop}`"
v-bind="scope"
/>
</span>
<span v-else>{{ scope.column.label }}</span>
</template>
<template slot-scope="scope">
<BaseRender
v-if="item.render"
:render="item.render"
:item="item"
:data="scope.row"
:scope="scope"
/>
<span v-else-if="$scopedSlots[item.prop]">
<slot
:name="item.prop"
v-bind="scope"
/>
</span>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
</template>
</el-table-column>
</template>
<script>
import BaseRender from './BaseRender.js'
export default {
name: 'TableColumn',
components: {
BaseRender
},
props: {
columns: {
type: Array,
default: () => []
}
}
}
</script>
<style scoped lang="scss"></style>
BaseRender.js
export default {
functional: true,
render (h, context) {
return context.props.render(h, context.props)
}
}
好了,本文就到这里了,收工
转载自:https://juejin.cn/post/7218916720323985463