组件概述
本文档描述了一个基于Element Plus的可复用表格组件实现,包含一个通用表格组件(UseTable)和其使用示例(BasicTableDemo)。
UseTable 组件
组件说明
UseTable是一个封装了Element Plus表格的通用组件,提供了灵活的配置选项和插槽功能。
属性说明
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| index | Boolean | false | 是否显示序号列 |
| selection | Boolean | false | 是否显示多选列 |
| tableHeader | Array | - | 表格列配置数组 |
| attrs | Object | - | 传递给el-table的其他属性 |
tableHeader配置项说明
每个列配置对象包含以下属性:
{
id: String, // 列唯一标识
prop: String, // 对应数据字段名
label: String, // 列标题
sort: Boolean, // 是否可排序
// ...其他el-table-column支持的属性
}
插槽
- 默认插槽:可以通过列的prop名称来自定义列内容
- 支持el-table的所有内置插槽
暴露方法
clearSelection(): 清空表格选择状态
组件封装代码
UseTable 组件完整代码
<template>
<div class="use-table">
<!-- 表格主体 -->
<el-table
ref="tableRef"
v-bind="attrs"
:data="data"
:border="border"
:stripe="stripe"
:height="height"
:max-height="maxHeight"
:show-header="showHeader"
:highlight-current-row="highlightCurrentRow"
:show-overflow-tooltip="showOverflowTooltip"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
@row-click="handleRowClick"
>
<!-- 序号列 -->
<el-table-column
v-if="index"
type="index"
label="序号"
width="60"
align="center"
/>
<!-- 多选列 -->
<el-table-column
v-if="selection"
type="selection"
width="55"
align="center"
/>
<!-- 动态列 -->
<template v-for="column in tableHeader" :key="column.id">
<el-table-column
v-bind="getColumnProps(column)"
:align="column.align || 'left'"
>
<template #default="scope">
<!-- 使用自定义插槽 -->
<slot
v-if="$slots[column.prop]"
:name="column.prop"
v-bind="scope"
:index="scope.$index"
:prop="column.prop"
/>
<!-- 默认内容展示 -->
<span v-else>
{{ formatColumnValue(scope.row, column) }}
</span>
</template>
<!-- 表头插槽 -->
<template v-if="$slots[`${column.prop}-header`]" #header="scope">
<slot :name="\`${column.prop}-header\`" v-bind="scope" />
</template>
</el-table-column>
</template>
<!-- 操作列 -->
<slot name="operation" />
</el-table>
<!-- 分页组件 -->
<div v-if="showPagination" class="pagination-container">
<el-pagination
v-bind="paginationProps"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, computed, defineProps, defineEmits } from 'vue'
const props = defineProps({
// 表格数据
data: {
type: Array,
default: () => []
},
// 表格列配置
tableHeader: {
type: Array,
default: () => []
},
// 是否显示序号列
index: {
type: Boolean,
default: false
},
// 是否显示多选列
selection: {
type: Boolean,
default: false
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 是否显示斑马纹
stripe: {
type: Boolean,
default: true
},
// 表格高度
height: {
type: [String, Number],
default: ''
},
// 表格最大高度
maxHeight: {
type: [String, Number],
default: ''
},
// 是否显示表头
showHeader: {
type: Boolean,
default: true
},
// 是否高亮当前行
highlightCurrentRow: {
type: Boolean,
default: false
},
// 当内容过长时是否显示 tooltip
showOverflowTooltip: {
type: Boolean,
default: true
},
// 分页配置
paginationProps: {
type: Object,
default: () => ({})
},
// 是否显示分页
showPagination: {
type: Boolean,
default: false
},
// 其他 el-table 属性
attrs: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits([
'selection-change',
'sort-change',
'row-click',
'page-change',
'size-change'
])
// 表格实例
const tableRef = ref(null)
// 分页相关数据
const currentPage = computed(() => props.paginationProps?.currentPage || 1)
const pageSize = computed(() => props.paginationProps?.pageSize || 10)
const total = computed(() => props.paginationProps?.total || 0)
// 获取列配置
const getColumnProps = (column) => {
const { prop, label, width, minWidth, sortable, fixed, ...rest } = column
return {
prop,
label,
width,
minWidth,
sortable,
fixed,
...rest
}
}
// 格式化列值
const formatColumnValue = (row, column) => {
const value = row[column.prop]
if (column.formatter && typeof column.formatter === 'function') {
return column.formatter(row, column)
}
return value ?? '--'
}
// 多选变化事件
const handleSelectionChange = (selection) => {
emit('selection-change', selection)
}
// 排序变化事件
const handleSortChange = (sort) => {
emit('sort-change', sort)
}
// 行点击事件
const handleRowClick = (row, column, event) => {
emit('row-click', row, column, event)
}
// 每页条数变化
const handleSizeChange = (size) => {
emit('size-change', size)
}
// 当前页变化
const handleCurrentChange = (page) => {
emit('page-change', page)
}
// 暴露方法
defineExpose({
// 清空选择
clearSelection: () => {
tableRef.value?.clearSelection()
},
// 切换行选中状态
toggleRowSelection: (row, selected) => {
tableRef.value?.toggleRowSelection(row, selected)
},
// 切换全选状态
toggleAllSelection: () => {
tableRef.value?.toggleAllSelection()
},
// 设置当前行
setCurrentRow: (row) => {
tableRef.value?.setCurrentRow(row)
}
})
</script>
<style scoped>
.use-table {
width: 100%;
}
.pagination-container {
margin-top: 15px;
display: flex;
justify-content: flex-end;
}
</style>
类型定义 (types.ts)
// 表格列配置接口
export interface TableColumn {
id: string | number;
prop: string;
label: string;
width?: string | number;
minWidth?: string | number;
fixed?: boolean | 'left' | 'right';
sortable?: boolean;
align?: 'left' | 'center' | 'right';
formatter?: (row: any, column: TableColumn) => any;
[key: string]: any;
}
// 分页配置接口
export interface PaginationProps {
currentPage?: number;
pageSize?: number;
total?: number;
pageSizes?: number[];
layout?: string;
background?: boolean;
[key: string]: any;
}
// 表格属性接口
export interface TableProps {
data: any[];
tableHeader: TableColumn[];
index?: boolean;
selection?: boolean;
border?: boolean;
stripe?: boolean;
height?: string | number;
maxHeight?: string | number;
showHeader?: boolean;
highlightCurrentRow?: boolean;
showOverflowTooltip?: boolean;
paginationProps?: PaginationProps;
showPagination?: boolean;
attrs?: Record<string, any>;
}
这个封装的表格组件具有以下特点:
- 完整的类型定义:使用 TypeScript 定义了所有的接口,提供更好的类型提示
- 灵活的列配置:支持自定义列的各种属性,包括宽度、排序、对齐方式等
- 丰富的功能:
- 支持序号列和多选列
- 支持自定义列模板
- 支持表头自定义
- 集成分页功能
- 支持行选择、排序等事件
- 可扩展性:
- 通过 attrs 属性支持传入任意 el-table 的原生属性
- 提供了丰富的插槽支持
- 暴露了常用的表格方法
- 样式优化:
- 默认添加了基础样式
- 分页组件布局优化
使用这个组件可以大大简化表格的开发工作,同时保持了足够的灵活性和可扩展性。
使用示例 (BasicTableDemo)
基本用法
<template>
<div>
<MyTable
:table-header="tableHeader"
:index="true"
:data="tableAttrs"
:selection="true"
>
<!-- 自定义列插槽 -->
<template #status="{ row, prop }">
<el-tag :type="getTagType(row[prop])">
{{ getStatusText(row[prop]) }}
</el-tag>
</template>
</MyTable>
</div>
</template>
<script setup>
import MyTable from '@/components/UseTable/index.vue'
import { ref } from 'vue'
// 定义表格列配置
const tableHeader = ref([
{ id: 'id', prop: 'id', label: 'ID', sort: true },
{ id: 'name', prop: 'name', label: '名称', sort: true },
{ id: 'custom', prop: 'customField', label: '自定义字段' },
{ id: 'status', prop: 'status', label: '状态' },
])
// 表格数据
const tableAttrs = ref([
{ id: 1, name: '示例1', customField: '自定义值1', status: 1 },
{ id: 2, name: '示例2', customField: '自定义值2', status: 2 },
{ id: 3, name: '示例3', customField: '自定义值3', status: 3 },
])
// 状态标签类型处理函数
function getTagType(status) {
switch (status) {
case 1: return 'success'
case 2: return 'info'
case 3: return 'danger'
default: return 'warning'
}
}
// 状态文本处理函数
function getStatusText(status) {
const statusMap = {
1: '已处理',
2: '未处理',
3: '紧急',
}
return statusMap[status] || '未知状态'
}
</script>
更多使用示例
1. 带分页的表格
<template>
<div>
<MyTable
:table-header="tableHeader"
:data="tableData"
:attrs="{
'pagination-props': {
total: total,
currentPage: currentPage,
pageSize: pageSize,
},
'on-page-change': handlePageChange
}"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100)
const handlePageChange = (page) => {
currentPage.value = page
// 这里可以调用接口获取数据
}
</script>
2. 带搜索和操作按钮的表格
<template>
<div>
<!-- 搜索区域 -->
<div class="search-area">
<el-input
v-model="searchKeyword"
placeholder="请输入搜索关键词"
@input="handleSearch"
/>
<el-button type="primary" @click="handleAdd">新增</el-button>
</div>
<!-- 表格 -->
<MyTable
:table-header="tableHeader"
:data="tableData"
:selection="true"
>
<!-- 操作列 -->
<template #operation="{ row }">
<el-button type="text" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" @click="handleDelete(row)">删除</el-button>
</template>
</MyTable>
</div>
</template>
<script setup>
import { ref } from 'vue'
const searchKeyword = ref('')
const tableHeader = ref([
{ id: 'name', prop: 'name', label: '名称' },
{ id: 'operation', prop: 'operation', label: '操作', width: '150px' }
])
const handleSearch = (value) => {
// 实现搜索逻辑
console.log('搜索关键词:', value)
}
const handleAdd = () => {
// 实现新增逻辑
}
const handleEdit = (row) => {
// 实现编辑逻辑
console.log('编辑行:', row)
}
const handleDelete = (row) => {
// 实现删除逻辑
console.log('删除行:', row)
}
</script>
<style scoped>
.search-area {
margin-bottom: 20px;
display: flex;
gap: 10px;
}
</style>
3. 自定义列模板的表格
<template>
<div>
<MyTable
:table-header="tableHeader"
:data="tableData"
>
<!-- 图片列 -->
<template #image="{ row }">
<el-image
:src="row.image"
:preview-src-list="[row.image]"
fit="cover"
style="width: 50px; height: 50px"
/>
</template>
<!-- 标签列 -->
<template #tags="{ row }">
<el-tag
v-for="tag in row.tags"
:key="tag"
class="mx-1"
>
{{ tag }}
</el-tag>
</template>
<!-- 开关列 -->
<template #status="{ row }">
<el-switch
v-model="row.status"
@change="(val) => handleStatusChange(row, val)"
/>
</template>
</MyTable>
</div>
</template>
<script setup>
import { ref } from 'vue'
const tableHeader = ref([
{ id: 'image', prop: 'image', label: '图片', width: '80px' },
{ id: 'name', prop: 'name', label: '名称' },
{ id: 'tags', prop: 'tags', label: '标签' },
{ id: 'status', prop: 'status', label: '状态', width: '100px' }
])
const tableData = ref([
{
image: 'https://example.com/image1.jpg',
name: '示例项目1',
tags: ['标签1', '标签2'],
status: true
},
{
image: 'https://example.com/image2.jpg',
name: '示例项目2',
tags: ['标签3', '标签4'],
status: false
}
])
const handleStatusChange = (row, value) => {
console.log('状态改变:', row, value)
}
</script>
这些示例展示了表格组件的不同使用场景,包括:
- 基础表格的使用
- 分页功能的实现
- 搜索和操作按钮的集成
- 自定义列模板的使用(图片、标签、开关等)
每个示例都包含了完整的代码实现,您可以根据实际需求选择合适的示例进行参考和使用。
状态展示功能
组件实现了一个状态标签展示功能,包含以下状态:
| 状态码 | 状态文本 | 标签类型 |
|---|---|---|
| 1 | 已处理 | success |
| 2 | 未处理 | info |
| 3 | 紧急 | danger |
| 其他 | 未知状态 | warning |
辅助函数
getTagType(status): 根据状态码返回对应的标签类型getStatusText(status): 根据状态码返回对应的状态文本
最佳实践
- 表格列的配置应通过
tableHeader属性统一管理 - 对于需要自定义渲染的列,使用具名插槽实现
- 状态类型的展示建议使用标签组件,并通过状态映射保持一致性
注意事项
- 确保提供正确的数据结构和必要的属性
- 自定义插槽的使用需要与tableHeader中定义的prop对应
- 状态码的使用需要与预定义的映射保持一致