前言
在前端开发中,表格(Table)组件是最常用的UI组件之一。Element Plus作为Vue3的流行UI库,提供了功能强大的Table组件。但在实际项目中,直接使用原生Table组件往往会导致代码重复、维护困难。本文将介绍如何基于Vue3和Element Plus二次封装一个更高效、更易用的Table组件。
为什么需要二次封装Table组件?
-
统一风格:保持项目中所有表格样式和行为一致
-
减少重复代码:封装公共逻辑,避免每个页面重复编写相似代码
-
提高开发效率:通过配置化方式快速生成表格
-
便于维护:统一修改点,一处修改全局生效
基础封装实现
1、创建基础组件
<template>
<el-table
v-bind="$attrs"
:data="tableData"
:border="border"
:stripe="stripe"
@selection-change="handleSelectionChange"
>
<template v-for="item in columns" :key="item.prop">
<!-- 选择列 -->
<el-table-column v-if="item.type === 'selection'" type="selection" width="55" />
<!-- 序号列 -->
<el-table-column
v-else-if="item.type === 'index'"
type="index"
:width="item.width || '80'"
:label="item.label || '序号'"
/>
<!-- 普通列 -->
<el-table-column
v-else
:prop="item.prop"
:label="item.label"
:width="item.width"
:min-width="item.minWidth"
:align="item.align || 'center'"
>
<!-- 自定义列内容 -->
<template #default="scope" v-if="item.slot">
<slot :name="item.slot" :row="scope.row"></slot>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script setup>
const props = defineProps({
columns: {
type: Array,
required: true
},
tableData: {
type: Array,
required: true
},
border: {
type: Boolean,
default: true
},
stripe: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['selection-change'])
const handleSelectionChange = (val) => {
emit('selection-change', val)
}
</script>
2、使用示例
<template>
<ProTable
:columns="columns"
:table-data="tableData"
@selection-change="handleSelectionChange"
>
<!-- 自定义状态列 -->
<template #status="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
<!-- 自定义操作列 -->
<template #action="{ row }">
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</ProTable>
</template>
<script setup>
const columns = [
{ type: 'selection' },
{ type: 'index', label: '序号' },
{ prop: 'name', label: '姓名', width: '120' },
{ prop: 'age', label: '年龄', width: '80' },
{ prop: 'address', label: '地址', minWidth: '200' },
{ prop: 'status', label: '状态', slot: 'status' },
{ prop: 'action', label: '操作', slot: 'action', width: '180' }
]
const tableData = [
// 表格数据...
]
</script>
进阶功能扩展
1、添加分页功能
```<template>
<div class="pro-table-container">
<!-- 表格部分 -->
<el-table ...></el-table>
<!-- 分页部分 -->
<div class="pagination" v-if="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
// 添加分页相关props和方法
const props = defineProps({
// ...其他props
pagination: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 0
},
pageSize: {
type: Number,
default: 10
},
currentPage: {
type: Number,
default: 1
}
})
const emit = defineEmits([
// ...其他emits
'update:pageSize',
'update:currentPage',
'pagination-change'
])
const handleSizeChange = (val) => {
emit('update:pageSize', val)
emit('pagination-change', { pageSize: val, currentPage: props.currentPage })
}
const handleCurrentChange = (val) => {
emit('update:currentPage', val)
emit('pagination-change', { pageSize: props.pageSize, currentPage: val })
}
</script>
2、添加表格工具栏
<template>
<div class="pro-table-container">
<!-- 工具栏 -->
<div class="toolbar" v-if="$slots.toolbar || showToolbar">
<div class="left">
<slot name="toolbar-left"></slot>
</div>
<div class="right">
<slot name="toolbar-right">
<el-button type="primary" @click="handleRefresh">
<el-icon><refresh /></el-icon>
刷新
</el-button>
</slot>
</div>
</div>
<!-- 表格部分 -->
<el-table ...></el-table>
<!-- 分页部分 -->
<div ...></div>
</div>
</template>
<script setup>
// 添加工具栏相关props
const props = defineProps({
// ...其他props
showToolbar: {
type: Boolean,
default: true
}
})
const emit = defineEmits([
// ...其他emits
'refresh'
])
const handleRefresh = () => {
emit('refresh')
}
</script>
3、添加加载状态
<template>
<el-table
v-loading="loading"
element-loading-text="加载中..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.1)"
...
>
...
</el-table>
</template>
<script setup>
const props = defineProps({
// ...其他props
loading: {
type: Boolean,
default: false
}
})
</script>
完整封装示例
<template>
<div class="pro-table-container">
<!-- 工具栏 -->
<div class="toolbar" v-if="$slots.toolbar || showToolbar">
<div class="left">
<slot name="toolbar-left"></slot>
</div>
<div class="right">
<slot name="toolbar-right">
<el-button type="primary" @click="handleRefresh">
<el-icon><refresh /></el-icon>
刷新
</el-button>
</slot>
</div>
</div>
<!-- 表格部分 -->
<el-table
v-bind="$attrs"
:data="tableData"
:border="border"
:stripe="stripe"
v-loading="loading"
element-loading-text="加载中..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.1)"
@selection-change="handleSelectionChange"
@sort-change="handleSortChange"
>
<template v-for="item in columns" :key="item.prop">
<!-- 选择列 -->
<el-table-column v-if="item.type === 'selection'" type="selection" width="55" />
<!-- 序号列 -->
<el-table-column
v-else-if="item.type === 'index'"
type="index"
:width="item.width || '80'"
:label="item.label || '序号'"
/>
<!-- 普通列 -->
<el-table-column
v-else
:prop="item.prop"
:label="item.label"
:width="item.width"
:min-width="item.minWidth"
:align="item.align || 'center'"
:sortable="item.sortable || false"
>
<!-- 自定义列内容 -->
<template #default="scope" v-if="item.slot">
<slot :name="item.slot" :row="scope.row"></slot>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页部分 -->
<div class="pagination" v-if="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="pageSizes"
:total="total"
:layout="paginationLayout"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
import { Refresh } from '@element-plus/icons-vue'
const props = defineProps({
columns: {
type: Array,
required: true
},
tableData: {
type: Array,
required: true
},
border: {
type: Boolean,
default: true
},
stripe: {
type: Boolean,
default: true
},
loading: {
type: Boolean,
default: false
},
pagination: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 0
},
pageSize: {
type: Number,
default: 10
},
currentPage: {
type: Number,
default: 1
},
pageSizes: {
type: Array,
default: () => [10, 20, 50, 100]
},
paginationLayout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
showToolbar: {
type: Boolean,
default: true
}
})
const emit = defineEmits([
'selection-change',
'sort-change',
'update:pageSize',
'update:currentPage',
'pagination-change',
'refresh'
])
const handleSelectionChange = (val) => {
emit('selection-change', val)
}
const handleSortChange = (val) => {
emit('sort-change', val)
}
const handleSizeChange = (val) => {
emit('update:pageSize', val)
emit('pagination-change', { pageSize: val, currentPage: props.currentPage })
}
const handleCurrentChange = (val) => {
emit('update:currentPage', val)
emit('pagination-change', { pageSize: props.pageSize, currentPage: val })
}
const handleRefresh = () => {
emit('refresh')
}
</script>
<style scoped>
.pro-table-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
</style>
使用技巧与最佳实践
-
合理设计columns配置:将列配置设计为可扩展的,支持更多自定义选项
-
插槽灵活运用:通过作用域插槽提供最大限度的自定义能力
-
性能优化:
-
大数据量时使用虚拟滚动
-
合理使用v-if和v-show
-
避免不必要的响应式数据
-
-
错误处理:添加表格数据为空时的空状态提示
-
国际化支持:考虑将表头标签等文本支持国际化
总结
通过对Element Plus Table组件的二次封装,我们实现了:
-
统一的表格风格和行为
-
配置化的表格生成方式
-
丰富的扩展功能(分页、工具栏、加载状态等)
-
更高的代码复用率和可维护性
这种封装方式可以显著提高开发效率,特别适合中后台管理系统开发。根据项目实际需求,你还可以进一步扩展更多功能,如列显隐控制、列拖拽排序、导出Excel等。
希望本文对你有所帮助,欢迎在评论区分享你的封装经验和想法!
如果你喜欢这篇文章,欢迎关注我的微信公众号【前端的那点事情】,获取更多精彩内容!