平时开发过程中,一般都会对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属性,这个属性传入的是所有列的有关配置,具体的属性和说明如下。

属性类型说明
labelstring表头显示的文字
propstring数据对应的字段名(如果为嵌套表头则不需要传此属性)
headerSlotstring当前表头如果为自定义内容,此属性值为当前自定义内容的插槽名
slotFlagboolean当前内容区域是否为自定义内容,为true时则prop作为当前自定义内容的插槽名
childrenarray如当前表头为嵌套表头,则被嵌套的区域作为当前项的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>

结束,撒花
Logo

前往低代码交流专区

更多推荐