vue+element多级表头表格的封装(插槽+递归的嵌套)
平时开发过程中,一般都会对element这些组件库中的表格进行二次封装以方便使用。为了可以通过传入配置项之间展示多级表头的表格才有了这篇文章。开发环境:vue3+element-plus问题:表头和表格内容区域都可以进行自定义,也就是会使用插槽。多层级的插槽还会涉及到透传,而且表格列组件需要进行递归,所以这里主要解决了递归+插槽嵌套的问题简单介绍表格具有的几个主要功能:表头自定义、表格内容区域自定
平时开发过程中,一般都会对element这些组件库中的表格进行二次封装以方便使用。为了可以通过传入配置项之间展示多级表头的表格才有了这篇文章。
开发环境:vue3+element-plus(vue2也适用)
问题:表头和表格内容区域都可以进行自定义,也就是会使用插槽。多层级的插槽还会涉及到透传,而且表格列组件需要进行递归,所以这里主要解决了递归+插槽嵌套的问题
2021.9.10更改
问题:之前获取slots是通过getInstance中的ctx获取的,但是ctx开发与生产环境获取的值不一致,详情可查看https://github.com/vuejs/vue-next/issues/2743
解决方法:通过getInstance的proxy获取即可,和之前的ctx使用类似const { proxy } = getInstance()
简单介绍
表格具有的几个主要功能:表头自定义、表格内容区域自定义、多级表头(效果图如下)
为了使代码看起来简洁,关于分页、单双选、合计等功能及居中、排序、格式化等属性都不会在这里面展示。
表格组件接收的配置项为一整个对象compTableOpts,这个对象中包含一些与上述几个主要功能无关的项我就不累述了,主要有用的就是columns属性,这个属性传入的是所有列的有关配置,具体的属性和说明如下。
属性 | 类型 | 说明 |
---|---|---|
label | string | 表头显示的文字 |
prop | string | 数据对应的字段名(如果为嵌套表头则不需要传此属性) |
headerSlot | string | 当前表头如果为自定义内容,此属性值为当前自定义内容的插槽名 |
slotFlag | boolean | 当前内容区域是否为自定义内容,为true时则prop作为当前自定义内容的插槽名 |
children | array | 如当前表头为嵌套表头,则被嵌套的区域作为当前项的children传入 |
上面的描述可能说的不清晰,所以下面直接上示例代码
实际业务页面business.vue
<x-table :comp-table-opts="compTableOpts">
<!-- 这里的header1与下面定义中的headerSlot的值对应,也就是当前插槽的内容会渲染在这一项的表头上 -->
<template #header1>
<span style="color: red">成年</span>
</template>
<template #header2>
<span style="color: yellow">未成年</span>
</template>
<template #header3>
<span style="color: blue;">爱好</span>
</template>
<!-- 这里的minor与下面定义中的prop的值对应,同时这一项的slotFlag为true,所以当前插槽的内容会渲染在这一表格内容中 -->
<template #minor="row">
<span>{{ row.rowData.minor === '是' ? '🌼' : '🥬' }}</span>
</template>
</x-table>
<script>
setup() {
// 这里为了展示多层嵌套的插槽,所以多加几个children
const compTableOpts = reactive({
columns: [
{ label: 'ID', prop: 'id' },
{ label: '姓名', prop: 'name' },
{ label: '爱好', prop: 'hobby', headerSlot: 'header3' },
{
label: '基本情况',
children: [
{ label: '性别', prop: 'sex' },
{ label: '年龄', prop: 'age' },
{
label: '是否成年',
children: [
{ headerSlot: 'header1', prop: 'adult' },
{ headerSlot: 'header2', prop: 'minor', slotFlag: true }
]
}
]
}
]
})
}
</script>
表格组件x-table
这里表格组件需要注意的是关于slot+递归嵌套的问题。
假设具体页面为父页面,表格组件为子页面,表格列组件TableColumn为孙子组件,在父页面中使用了插槽,并且插槽放置了内容,在孙子组件中是获取不到的。
在官网查找中并没有找到这方面的描述,不过github上vue的issue里有提到关于嵌套插槽的问题
https://github.com/vuejs/vue/issues/5965
这里尤老师回答说建议使用渲染函数,不过在找资料的过程中发现了
两者相同的解决思路是把父页面中的所有插槽获取到,在子组件中用之前插槽的名称把这些插槽传递给孙子组件。我采用的是模板解决
<el-table
ref="compoundTableRef"
v-loading="tableLoading"
element-loading-text="拼命加载中"
:data="tableData"
style="width: 100%;"
>
<TableColumn
v-for="item in compTableOpts.columns"
key="id"
:col="item"
>
<!-- 注意:如果是vue2中的话customSlots可以替换为$scopedSlots,而且下面setup中的取值也不需要了 -->
<template v-for="slot in Object.keys(customSlots)" #[slot]="scope">
<!-- 以之前的名字命名插槽,同时把数据原样绑定 -->
<slot :name="slot" v-bind="scope" />
</template>
</TableColumn>
</el-table>
<script>
import { getCurrentInstance } from 'vue'
setup() {
const { proxy } = getCurrentInstance()
const customSlots = reactive({
...proxy.$slots
})
return {
customSlots
}
}
</script>
表格列组件TableColumn
此组件是被递归的组件,所以有关的插槽也需要同时进行传递
<template>
<el-table-column
v-if="!col.children"
:label="col.label"
:prop="col.prop || ''"
:show-overflow-tooltip="!col.els"
>
<template #header>
<slot v-if="col.headerSlot" :name="col.headerSlot" />
<span v-else>{{ col.label }}</span>
</template>
<template #default="scope">
<slot v-if="col.slotFlag" :name="col.prop" :rowData="scope.row" />
<span v-else>{{ typeof scope.row[col.prop] !== 'number' && !scope.row[col.prop] ? '--' : scope.row[col.prop] }}</span>
</template>
</el-table-column>
<el-table-column
v-else
:label="col.label"
>
<TableColumn
v-for="t in col.children"
:key="t.prop"
:col="t"
>
<!-- 注意:如果是vue2中的话customSlots可以替换为$scopedSlots,而且下面setup中的取值也不需要了 -->
<template v-for="slot in Object.keys(customSlots)" #[slot]="scope">
<!-- 以之前的名字命名插槽,同时把数据原样绑定 -->
<slot :name="slot" v-bind="scope" />
</template>
</TableColumn>
</el-table-column>
</template>
<script>
import { getCurrentInstance, reactive } from 'vue'
export default {
// 这里的name为必须的
name: 'TableColumn',
props: {
col: {
type: Object,
default: () => {}
}
},
setup() {
const { proxy } = getCurrentInstance()
const customSlots = reactive({
...proxy.$slots
})
return {
customSlots
}
}
}
</script>
结束,撒花
更多推荐
所有评论(0)