背景
由于在工作中经常需要处理报表数据,频繁使用表格组件,而 element-ui 中的表格组件使用起来不够方便。为了提高开发效率(有更多时间划水),决定对表格组件进行一些封装。
源码可以在我的 GitHub 仓库 ares-admin 中查看。如果有任何问题或建议,请随时联系我。
需求设计
在之前的 Vue 2 项目中,我对 el-table
组件进行了一些业务封装。由于当时经验不足,设计上存在一些不合理之处。经过仔细思考和调研,我总结了一些更合适的封装设计方案:
- 通过
columns
配置来管理表格的列信息; - 将查询表单、分页组件和操作按钮等功能集成到表格组件中;
- 针对实际需求,在组件内封装常见的业务逻辑。
方案分析
1. columns
配置管理表格列信息
通过定义 columns
配置来管理表格列信息,不仅使表格结构更清晰,还提升了扩展性和使用便捷性。此方案在之前的项目中已经取得了不错的效果。
类似的设计思路也被主流组件库如
ant design
采用。
2. 表单和业务操作的封装
将查询表单、分页器和操作按钮等功能封装到表格组件内部,我并不完全认同。尽管这能提高开发效率,但也增加了组件的复杂度和使用门槛,同时维护成本较高。
此外,这种设计缺乏灵活性,难以根据不同场景进行定制。当需要在多个页面或场景下使用不同的表单或操作逻辑时,封装在表格组件内的逻辑难以进行灵活调整,可能需要额外的代码来适应不同需求,从而增加了维护难度。
前期设计不当,后期可能面临许多维护上的问题。
最终方案设计
综合考虑后,我的最终方案如下:
- 表格列信息通过
columns
配置:使表格结构更清晰且便于扩展。 - 使用 TSX 封装组件:提升代码复用性和开发体验。
- 仅保留分页器逻辑:将分页器相关逻辑保留在表格组件内,保持表格功能简单且独立。
对于其他功能和业务逻辑,可以通过组件组合的方式进行集成。鉴于业务中有自定义列的需求,我也将自定义列的逻辑封装在表格组件中,以便更好地支持该需求。
代码实现
- 定义
columns
参数 | 说明 | 类型 |
---|---|---|
需要继承 el-table-column 所有属性 | ||
hidden | 是否隐藏列 | boolean |
children | 多级表头 | Array |
能根据需要灵活扩展 ... |
- 事件处理
事件名 | 说明 | 回调参数 |
---|---|---|
需要继承 el-table 所有事件 | ||
change | 分页排序回调 | pageSize, pageNum, prop, order, type |
column-change | 自定义列回调 | columns |
能根据需要灵活扩展 ... |
基本结构
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 组件封装的思路和简单实现。在工作中通过思考和总结,根据具体业务需求和团队特点,可以提炼一些业务组件来提高开发效率,减少重复的代码编写。
最后,欢迎大家在评论区讨论学习,分享自己的经验和更好的想法,帮助我们一起成长。