UseElTable - 表格
组件介绍
UseElTable 是基于 Element Plus 的 el-table 核心能力二次封装的通用表格组件。该组件通过抽象「选择器、序号列、操作列、分页、自适应」等高频配置项,将原生表格的零散配置收敛为标准化的全局 / 局部配置,解决了原生表格配置繁琐、自适应适配复杂、分页与表格联动冗余等问题,同时保留原生 el-table 的全量能力,适用于中后台系统各类数据列表场景。
核心亮点
- 配置化驱动,大幅降低开发成本
- 内置高频功能,无需重复开发
- 高扩展性与自定义能力
- 统一交互与样式规范
- 自动化分页联动,减少逻辑冗余
- 全量保留原生能力,无功能阉割
解决痛点
- 原生 el-table 重复代码过多
- 痛点:每个表格页面都需编写大量 el-table、el-table-column 标签,列的样式、对齐方式、宽度等需重复配置,开发效率低且易出错。
- 解决方案:通过配置项 tableColumns 集中定义所有列属性,模板仅需引入组件并传入配置,减少 80% 以上的重复代码,且配置项可复用、可维护。
- 高频功能需手动封装,逻辑不统一
- 痛点:多选 / 单选、分页联动、空状态、加载状态等功能,每个页面需手动编写逻辑,不同开发者实现方式不同,导致代码混乱、维护成本高。
- 解决方案:组件内置这些高频功能,通过简单配置即可使用,且逻辑统一,无需重复开发。
- 自定义交互与通用逻辑冲突
- 痛点:原生 el-table 自定义列(如操作按钮组、自定义模板)时,需与通用逻辑(如选中行、排序)兼容,易出现交互异常。
- 解决方案:组件预留插槽、自定义事件等扩展入口,自定义内容可与通用逻辑解耦,同时提供默认的兼容处理,确保自定义开发不破坏原有功能。
- 跨页面表格样式 / 交互不一致
- 痛点:中后台系统多个页面的表格,因无统一封装,样式(如行高、表头样式)、交互(如排序触发方式、选中行样式)不一致,影响用户体验。
- 解决方案:组件内置统一的默认样式和交互规则,同时支持通过配置项覆盖,既保证一致性,又满足个性化样式需求。
使用
全配置前端分页
该例演示复选+序号+前端分页等全配置
PS:1、表格数据由mockjs生成,非真实数据;2、由于ElementPlus组件库语言默认为英语,故在本例中组件层语言为英语,如需设置为中文需使用UseElConfigProvider组件提供的语言配置能力
CheckBox单选
该例演示CheckBox单选+同步设置选中数据
Radio单选
该例演示Radio单选+异步设置选中数据
Dict字典
该例演示Dict字典的各种使用
Slot插槽及其它
该例演示Slot插槽及其它常用配置
树形表格
该例演示树形表格(含懒加载)
额外表头
Types
js
import type { ExtractPropTypes } from 'vue'
import type { Dict } from '@/types/dict'
import componentProps from './props'
/**
* 选择器配置项
*/
export interface Selection {
label?: string
type?: string
width: number
key?: string
enableRowClick: boolean
selectable?: Function
disabledConfig?:
| Record<string, string | number | string[] | number[]>
| Array<{ key: string; value: string | number | string[] | number[] }>
align: string
fixed: boolean
}
/**
* UseElTable的extConfig接口
*/
export interface ExtConfig {
// 额外表头配置项
extraHeader: {
class?: string
style: { padding: string; borderBottom: string }
}
// 选择器配置项
selection: Selection
// 序号列配置项
index: { label?: string; width: number; align: string; fixed: boolean }
// 自适应配置项
adaptive: { disabled: boolean; extraHeight: number; autoMinWidth: boolean }
// 展开列配置项
expand: { show: boolean; width?: number }
// 操作列配置项
operation: { label?: string; width: number; fixed: string }
// 分页器配置项
pagination: {
total: number
marginTop: number
position: string
pageSizes: number[]
background: boolean
layout: string
func?: Function
}
}
/**
* 表头配置项
*/
export interface Header {
// 表头具名插槽 为true则在模板代码中添加`<template #prop-header="{ item }"></template>`即可,可传入string自定义具名插槽名称
slot?: boolean | string
// 表头是否必填,必填表头默认在表头名称前添加*号
required?: boolean
// 必填表头*号位置,默认为before
asteriskPosition?: 'before' | 'after'
}
/**
* tableColumn的extConfig接口
*/
export interface TableColumnExtConfig {
// 表头配置项
header?: Header
// 字典
dict?: Dict
// 过滤器
formatter?: Function
// 类名
class?: string | any[]
// 样式
style?: object
// 插槽 为true则在模板代码中添加`<template #prop="{ row }"></template>`即可,可传入string自定义具名插槽名称
slot?: boolean | string
}
/**
* tableColumn接口
*/
export interface TableColumn {
label: string
prop?: string //多级表头的父级表头可不传
width?: string | number
minWidth?: string | number
show?: boolean
align?: string
fixed?: boolean | string
sortable?: boolean
showOverflowTooltip?: boolean
extConfig?: TableColumnExtConfig
children?: TableColumn[]
}
/**
* 表头列样式接口
*/
export interface HeaderCellStyle {
backgroundColor?: string
color?: string
[key: string]: any
}
/**
* props类型
*/
export type Props = ExtractPropTypes<typeof componentProps>
/**
* emits接口
*/
export interface Emits {
/**
* 选中数据更新事件(支持 v-model:selectionData)
*/
(e: 'update:selectionData', selectionData: any[] | any): void
/**
* 当前行更新事件(支持 v-model:currentRow)
*/
(e: 'update:currentRow', currentRow: any): void
/**
* 当前页码更新事件(支持 v-model:currentPage)
*/
(e: 'update:currentPage', currentPage: number): void
/**
* 每页条数更新事件(支持 v-model:pageSize)
*/
(e: 'update:pageSize', pageSize: number): void
}DefaultExtConfig
js
import type { ExtConfig } from './types'
/**
* 默认扩展配置
*/
export default {
/**
* 额外表头配置项
* class:额外表头类名
* style:额外表头样式对象
*/
extraHeader: {
style: {
padding: '10px',
borderBottom: '1px solid var(--el-border-color-light)'
}
},
/**
* 选择器配置项
* 绑定v-model:selectionData="selectionData"则开启选择器 不显式设置type则默认为CHECKBOX复选
* type:选择类型 可选值:CHECKBOX(复选) | CHECKBOXRADIO(复选框单选) | RADIO(单选)
* label:type为CHECKBOXRADIO、RADIO时表头全选复选框的文本 不设置则为空显示
* key:选中数据的唯一键 对应tableData中的唯一键 如id
* selectable:决定当前行复选框是否可以勾选 传入function,如果是简单禁用逻辑推荐优先使用disabledConfig配置项,复杂逻辑才建议使用本配置项
* disabledConfig:禁用配置 可传对象或对象数组
* enableRowClick:允许通过点击行触发单选/复选 默认否 PS:默认不高亮当前行,若需高亮当前行,只需给UseElTable组件设置highlightCurrentRow属性即可
* width:选择器的列宽 默认为40px
* align:选择器的排列方式 默认为center居中
* fixed:是否固定 默认为true
*/
selection: { width: 40, enableRowClick: false, align: 'center', fixed: true },
/**
* 序号列配置项
* label:序号列表头文本 不显式设置则不显示序号列
* width、align、fixed同选择器配置项
*/
index: { width: 55, align: 'center', fixed: true },
/**
* 展开列配置项
*/
expand: { show: false },
/**
* 自适应配置项
* disabled:是否禁用高度自适应 默认不禁用
* extraHeight:开启高度自适应后的额外高度(margin+padding) 非显式设置则为全局配置的【表格底部额外高度】,可手动显式设置额外高度
* autoMinWidth:是否开启自动最小宽度 默认开启
*/
adaptive: { disabled: false, extraHeight: 20, autoMinWidth: true },
/**
* 操作列配置项
* label:操作列文案 显式设置则开启操作列
* width:操作列宽度 默认为128px
* fixed:固定方向 默认固定在右侧
*/
operation: { width: 128, fixed: 'right' },
/**
* 分页器配置项
* total:分页总数 为-1时不显示分页器
* marginTop:分页器与表格的间距 默认20px
* func:分页方法 显式设置则在组件内部自动处理分页逻辑
* position:分页器位置 默认为right 可选值:left | center | right
*/
pagination: {
total: -1,
marginTop: 20,
position: 'right',
pageSizes: [10, 20, 30, 40, 50],
background: true,
layout: 'total, sizes, prev, pager, next, jumper'
}
} satisfies ExtConfigComponentProps
js
import type { PropType } from 'vue'
import type { ExtConfig, HeaderCellStyle, TableColumn } from './types'
import type { DictMap, DictProps } from '@/types/dict'
/**
* 组件props
*/
export default {
/**
* 表格列 必传
*/
tableColumns: {
type: Array as PropType<TableColumn[]>,
required: true
},
/**
* 表格数据 必传
*/
tableData: {
type: Array as PropType<Record<string, unknown>[]>,
required: true
},
/**
* 表格loading
*/
tableLoading: {
type: Boolean,
default: false
},
/**
* 表格列排列方式 默认center
*/
tableColumnAlign: {
type: String,
default: 'center',
validator: (val: string) => ['left', 'center', 'right'].includes(val)
},
/**
* 字典映射 一般数据结构为{ tableColumns[].label: Array<{ label: string, value: any }> },亦可自定义key,然后通过optionKey与其匹配
*/
dictMap: {
type: Object as PropType<DictMap>,
default: () => ({})
},
/**
* 字典属性 默认为{ value: 'value', label: 'label' },可通过全局配置进行设置,为适配全局配置此处不再设置默认值
*/
dictProps: {
type: Object as PropType<DictProps>,
default: () => ({})
},
/**
* 字典数据是否转换为字符串 可通过全局配置进行设置
*/
dictDataToString: {
type: Boolean,
default: false
},
/**
* 选中的数据 通过属性v-model:selectionData绑定
* type为CHECKBOX时为Array,type为CHECKBOXRADIO和RADIO时为Object
* 为精确判断组件是否绑定v-model:selectionData属性,故不在此处显式指定,而是通过proxy.$attrs['selectionData']获取
*/
// selectionData:{
// type: [Array, Object] as PropType<Record<string, unknown>[] | Record<string, unknown>>,
// default: undefined
// },
/**
* 当内容过长被隐藏时是否显示tooltip 默认是
*/
showOverflowTooltip: {
type: Boolean,
default: true
},
/**
* 表头列样式
*/
headerCellStyle: {
type: Object as PropType<HeaderCellStyle>,
default: () => ({ backgroundColor: '#F0F2F6', color: '#000' })
},
/**
* 主题模式
*/
themeMode: {
type: String,
default: '',
validator: (val: string) => ['', 'light', 'dark'].includes(val)
},
/**
* 扩展配置 如【序号/复选单选/展开列/分页】等
*/
extConfig: {
type: Object as PropType<ExtConfig>,
default: () => ({})
}
}Props && Emits
js
const props = defineProps(componentProps)
const emits = defineEmits<Emits>()