使用 TSX 封装 el-table 表格组件

2,154 阅读4分钟

背景

由于在工作中经常需要处理报表数据,频繁使用表格组件,而 element-ui 中的表格组件使用起来不够方便。为了提高开发效率(有更多时间划水),决定对表格组件进行一些封装。

源码可以在我的 GitHub 仓库 ares-admin 中查看。如果有任何问题或建议,请随时联系我。

banner.png

需求设计

在之前的 Vue 2 项目中,我对 el-table 组件进行了一些业务封装。由于当时经验不足,设计上存在一些不合理之处。经过仔细思考和调研,我总结了一些更合适的封装设计方案:

  1. 通过 columns 配置来管理表格的列信息;
  2. 将查询表单、分页组件和操作按钮等功能集成到表格组件中;
  3. 针对实际需求,在组件内封装常见的业务逻辑。

方案分析

1. columns 配置管理表格列信息

通过定义 columns 配置来管理表格列信息,不仅使表格结构更清晰,还提升了扩展性和使用便捷性。此方案在之前的项目中已经取得了不错的效果。

类似的设计思路也被主流组件库如 ant design 采用。

2. 表单和业务操作的封装

将查询表单、分页器和操作按钮等功能封装到表格组件内部,我并不完全认同。尽管这能提高开发效率,但也增加了组件的复杂度和使用门槛,同时维护成本较高。

此外,这种设计缺乏灵活性,难以根据不同场景进行定制。当需要在多个页面或场景下使用不同的表单或操作逻辑时,封装在表格组件内的逻辑难以进行灵活调整,可能需要额外的代码来适应不同需求,从而增加了维护难度。

前期设计不当,后期可能面临许多维护上的问题。

最终方案设计

综合考虑后,我的最终方案如下:

  1. 表格列信息通过 columns 配置:使表格结构更清晰且便于扩展。
  2. 使用 TSX 封装组件:提升代码复用性和开发体验。
  3. 仅保留分页器逻辑:将分页器相关逻辑保留在表格组件内,保持表格功能简单且独立。

对于其他功能和业务逻辑,可以通过组件组合的方式进行集成。鉴于业务中有自定义列的需求,我也将自定义列的逻辑封装在表格组件中,以便更好地支持该需求。

代码实现

  • 定义 columns
参数说明类型
需要继承 el-table-column 所有属性
hidden是否隐藏列boolean
children多级表头Array
能根据需要灵活扩展 ...
  • 事件处理
事件名说明回调参数
需要继承 el-table 所有事件
change分页排序回调pageSize, pageNum, prop, order, type
column-change自定义列回调columns
能根据需要灵活扩展 ...

table.png

基本结构

function render() {
  return (
    <div class="x-table">
      <ElTable
        {...tableProps}
        v-slots={extraSlots}
      >
        {
          props.columns.map((item) => {
            if (Array.isArray(item.children)) {
              return renderColumnChildren(item, item.children)
            }
            return renderTableColumn(item)
          })
        }
      </ElTable>
      {showPagination.value && renderPagination()}
      {!isUndefined(props.visibleColumn) && renderCustomColumn()}
    </div>
  )
}

表格列渲染

function renderTableColumn(column: XTableColumn) {
  const columnSlots: {
    default?: (scope: Record<string, any>) => any
    header?: (scope: Record<string, any>) => any
  } = {}

  const slot = getSlot(column)
  const headerSlot = getSlot(column, 'header')

  if (slot) {
    columnSlots.default = scope => slot(scope)
  }

  if (headerSlot) {
    columnSlots.header = scope => headerSlot(scope)
  }

  return (
    <ElTableColumn {...getColumnProps(column)} >
      {columnSlots}
    </ElTableColumn>
  )
}

自定义列渲染

function renderCustomColumn() {
  const customColumnProps = {
    columns: props.columns,
    visible: props.visibleColumn,
    onChange: handleColumnChange,
    onVisibleChange: handleVisibleChange,
  }

  return (
    <XColumn {...customColumnProps} />
  )
}

使用示例

const columns = [
  {
    type: 'selection',
  },
  {
    label: '日期',
    prop: 'date',
    align: 'left',
    sortable: 'custom',
    formatter(row) {
      return dayjs(row.date).format('YYYY-MM-DD')
    },
  },
  {
    label: '地址',
    prop: 'address',
    hidden: true,
    children: [
      {
        label: '区域',
        prop: 'area',
      },
    ],
  },
  {
    label: '操作',
    prop: 'action',
    fixed: 'right',
  },
]
<x-table
  v-model:visible-column="dialogVisible"
  :columns="columns"
  :data-source="tableData"
  :total="tableTotal"
  :page-size="form.pageSize"
  :page-num="form.pageNum"
  @change="handleTableChange"
  @column-change="handleColumnChange"
  @selection-change="handleSelectionChange"
  @header-dragend="handleHeaderDragend"
>
  <template #name="{ row }">
    <el-button>{{ row.name }}</el-button>
  </template>

  <template #name-header>
    <span>表头插槽</span>
  </template>

  <template #action>
    <el-button type="primary">新增</el-button>
    <el-button>修改</el-button>
  </template>
</x-table>

// 修改自定义列
function handleColumnChange(cols: XTableColumn[]) {
  columns.value = cols
}

// 表格多选
function handleSelectionChange(value: XTableData[]) {
  console.log('selection change =>',value)
}

// 分页和排序
function handleTableChange(data: XTableChangeData) {
  const { pageSize, pageNum, sort, prop, type } = data
  state.pageNum = pageNum
  state.pageSize = pageSize
  state.sort = sort
  state.prop = prop
}

完整的代码在GitHub 仓库 中查看。

总结

本文分享了一些关于 el-table 组件封装的思路和简单实现。在工作中通过思考和总结,根据具体业务需求和团队特点,可以提炼一些业务组件来提高开发效率,减少重复的代码编写。

最后,欢迎大家在评论区讨论学习,分享自己的经验和更好的想法,帮助我们一起成长。