Vue3 + Element Plus 通用Table组件封装
在后台管理系统开发中,表格是最常用的组件之一。为了提高开发效率和代码复用性,我们通常会对基础的表格组件进行二次封装。本文将介绍如何基于 Element Plus 封装一个功能完善的通用 Table 组件。
设计思路
核心功能需求
- ✅ 基础表格展示:支持数据展示和列配置
- ✅ 多选功能:可选择性开启多选列
- ✅ 序号列:可配置的序号显示
- ✅ 自定义插槽:支持自定义列内容
- ✅ 状态标签:内置状态标签显示
- ✅ 分页功能:集成分页组件
- ✅ 事件处理:完善的事件回调机制
组件特点
- 高度可配置:通过 props 控制各种功能的开启
- 插槽支持:灵活的自定义内容插槽
- 响应式设计:支持 v-model 双向绑定
- 类型安全:完整的 TypeScript 支持
组件实现
完整代码
<template>
<div>
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
v-bind="$attrs"
>
<!-- 多选列 -->
<el-table-column
v-if="showSelection"
type="selection"
width="55"
align="center"
/>
<!-- 序号列 -->
<el-table-column
v-if="showIndex"
type="index"
:label="indexLabel || '序号'"
:width="indexWidth || '60'"
align="center"
/>
<el-table-column
v-for="(item, index) in columns"
:key="item.prop || index"
:prop="item.prop"
:label="item.label"
:width="item.width"
:align="item.align || 'left'"
>
<!-- 自定义插槽 -->
<template #default="{ row }" v-if="item.slot">
<slot :name="item.slot" :row="row">
<!-- 默认内容 -->
<span>{{
(row[item.prop] ?? "") === "" ? "--" : row[item.prop]
}}</span>
</slot>
</template>
<!-- 默认插槽 -->
<template #default="{ row }" v-else>
<span>{{
(row[item.prop] ?? "") === "" ? "--" : row[item.prop]
}}</span>
</template>
<!-- 状态 -->
<template v-if="item.options" #default="{ row }">
<span v-for="options in item.options">
<el-tag
v-if="row[item.prop] === options.value"
:type="options.type"
:key="options.value"
>
{{ options.label }}
</el-tag>
</span>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<div class="pagination-container" v-if="total > 0">
<el-pagination
v-model:current-page="curPage"
v-model:page-size="pageSize"
:page-sizes="pageSizes"
background
:layout="paginationLayout"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
dDefineOptions({
name: "Table",
inheritAttrs: false,
});
const page = defineModel("page", 1);
const size = defineModel("size", 10);
const props = defineProps({
tableData: {
type: Array,
default: () => [],
},
columns: {
type: Array,
default: () => [],
},
showSelection: {
type: Boolean,
default: false,
},
showIndex: {
type: Boolean,
default: false,
},
indexLabel: {
type: String,
default: "序号",
},
indexWidth: {
type: String,
default: "60",
},
total: {
type: Number,
default: 120,
},
pageSizes: {
type: Array,
default: () => [10, 20, 50, 100],
},
paginationLayout: {
type: String,
default: "total, sizes, prev, pager, next",
},
});
const emits = defineEmits(["pagination", "selection-change"]);
const pageSize = computed({
get: () => size.value,
set: (val) => {
size.value = val;
},
});
const curPage = computed({
get: () => page.value,
set: (val) => {
page.value = val;
},
});
const handleSelectionChange = (selection) => {
emits("selection-change", selection);
};
const handleCurrentChange = () => {
emits("pagination");
};
const handleSizeChange = () => {
emits("pagination");
};
</script>
<style lang="scss" scoped>
.pagination-container {
margin-top: 20px;
text-align: right;
}
</style>核心功能解析
1. 列配置系统
组件通过 columns 属性接收列配置,支持多种列类型:
const columns = [
{
prop: 'name',
label: '姓名',
width: '120',
align: 'center'
},
{
prop: 'status',
label: '状态',
options: [
{ value: 1, label: '启用', type: 'success' },
{ value: 0, label: '禁用', type: 'danger' }
]
},
{
prop: 'action',
label: '操作',
slot: 'action' // 自定义插槽
}
]2. 插槽机制
支持具名插槽,可以自定义列内容:
<template #action="{ row }">
<el-button type="primary" size="small" @click="edit(row)">
编辑
</el-button>
<el-button type="danger" size="small" @click="delete(row)">
删除
</el-button>
</template>3. 分页集成
内置分页功能,支持双向绑定:
<Table
v-model:page="currentPage"
v-model:size="pageSize"
:total="total"
@pagination="loadData"
/>4. 多选功能
通过 showSelection 开启多选,监听选择变化:
<Table
:show-selection="true"
@selection-change="handleSelection"
/>使用示例
基础用法
<template>
<div>
<Table
:table-data="tableData"
:columns="columns"
:show-index="true"
:show-selection="true"
:total="total"
v-model:page="currentPage"
v-model:size="pageSize"
@pagination="loadData"
@selection-change="handleSelection"
>
<!-- 自定义操作列 -->
<template #action="{ row }">
<el-button type="primary" size="small" @click="editUser(row)">
编辑
</el-button>
<el-button type="danger" size="small" @click="deleteUser(row)">
删除
</el-button>
</template>
</Table>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import Table from '@/components/Table.vue'
// 响应式数据
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const tableData = ref([])
const selectedRows = ref([])
// 列配置
const columns = reactive([
{
prop: 'name',
label: '姓名',
width: '120'
},
{
prop: 'email',
label: '邮箱',
width: '200'
},
{
prop: 'status',
label: '状态',
width: '100',
align: 'center',
options: [
{ value: 1, label: '启用', type: 'success' },
{ value: 0, label: '禁用', type: 'danger' }
]
},
{
prop: 'createTime',
label: '创建时间',
width: '180'
},
{
prop: 'action',
label: '操作',
width: '150',
align: 'center',
slot: 'action'
}
])
// 模拟数据
const mockData = [
{
id: 1,
name: '张三',
email: 'zhangsan@example.com',
status: 1,
createTime: '2024-01-15 10:30:00'
},
{
id: 2,
name: '李四',
email: 'lisi@example.com',
status: 0,
createTime: '2024-01-14 09:20:00'
},
{
id: 3,
name: '王五',
email: 'wangwu@example.com',
status: 1,
createTime: '2024-01-13 14:45:00'
}
]
// 加载数据
const loadData = async () => {
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 500))
// 模拟分页数据
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
tableData.value = mockData.slice(start, end)
total.value = mockData.length
} catch (error) {
console.error('加载数据失败:', error)
}
}
// 处理选择变化
const handleSelection = (selection) => {
selectedRows.value = selection
console.log('选中的行:', selection)
}
// 编辑用户
const editUser = (row) => {
console.log('编辑用户:', row)
// 这里可以打开编辑对话框或跳转到编辑页面
}
// 删除用户
const deleteUser = (row) => {
console.log('删除用户:', row)
// 这里可以显示确认对话框
}
// 组件挂载时加载数据
onMounted(() => {
loadData()
})
</script>高级用法
<template>
<div>
<!-- 工具栏 -->
<div class="toolbar">
<el-button type="primary" @click="batchDelete" :disabled="!selectedRows.length">
批量删除 ({{ selectedRows.length }})
</el-button>
<el-button @click="exportData">
导出数据
</el-button>
</div>
<!-- 表格 -->
<Table
:table-data="tableData"
:columns="columns"
:show-index="true"
:show-selection="true"
:total="total"
v-model:page="currentPage"
v-model:size="pageSize"
@pagination="loadData"
@selection-change="handleSelection"
stripe
border
>
<!-- 自定义状态列 -->
<template #status="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="updateStatus(row)"
/>
</template>
<!-- 自定义操作列 -->
<template #action="{ row }">
<el-dropdown>
<el-button type="primary" size="small">
操作 <el-icon><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="viewDetail(row)">
查看详情
</el-dropdown-item>
<el-dropdown-item @click="editUser(row)">
编辑
</el-dropdown-item>
<el-dropdown-item @click="resetPassword(row)">
重置密码
</el-dropdown-item>
<el-dropdown-item divided @click="deleteUser(row)">
<span style="color: #f56c6c">删除</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</Table>
</div>
</template>
<script setup>
// ... 其他代码类似基础用法
// 批量删除
const batchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的数据')
return
}
ElMessageBox.confirm(
`确定要删除选中的 ${selectedRows.value.length} 条数据吗?`,
'批量删除',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
// 执行批量删除逻辑
console.log('批量删除:', selectedRows.value)
ElMessage.success('删除成功')
loadData()
})
}
// 更新状态
const updateStatus = async (row) => {
try {
// 调用API更新状态
console.log('更新状态:', row)
ElMessage.success('状态更新成功')
} catch (error) {
// 回滚状态
row.status = row.status === 1 ? 0 : 1
ElMessage.error('状态更新失败')
}
}
</script>
<style scoped>
.toolbar {
margin-bottom: 16px;
}
</style>组件优势
1. 开发效率提升
- 快速配置:通过简单的配置即可生成复杂表格
- 代码复用:一次封装,多处使用
- 维护简单:统一的组件逻辑,便于维护和升级
2. 功能完善
- 插槽支持:灵活的自定义内容
- 状态管理:内置状态标签显示
- 分页集成:无需额外配置分页逻辑
- 事件处理:完善的事件回调机制
3. 扩展性强
- 属性透传:支持 Element Plus 原生属性
- 插槽机制:支持任意自定义内容
- 事件监听:支持所有原生事件
总结
通过封装通用的 Table 组件,我们可以:
- 提高开发效率:减少重复代码,快速构建表格页面
- 统一用户体验:保持一致的交互和视觉效果
- 便于维护:集中管理表格相关逻辑,便于后期维护和功能扩展
- 增强可复用性:一次开发,多处使用,降低开发成本
这个组件在实际项目中已经得到了广泛应用,大大提升了后台管理系统的开发效率。你可以根据具体需求进一步扩展功能,比如添加排序、筛选、导出等功能。
Last updated on