vue3+elementplus表格组件封装
CustomTable 组件使用文档
1. 组件概述
CustomTable 是一个基于 Element Plus 封装的表格组件,提供了丰富的配置选项和灵活的插槽机制,支持分页、多选、自定义列内容等功能。它简化了表格的使用流程,使开发者能够更专注于业务逻辑的实现。
2. 组件特性
- ✅ 支持动态列配置
- ✅ 支持分页功能
- ✅ 支持多选框功能
- ✅ 支持自定义插槽(首列、末列、自定义类型列)
- ✅ 支持表格宽高配置
- ✅ 支持列宽配置
- ✅ 支持列内容换行控制
- ✅ 支持固定列
3. 安装和使用
3.1 安装依赖
bash
# 安装 Element Plus
npm install element-plus --save
# 安装 Vue 3
npm install vue@3 --save3.2 组件代码
js
<template>
<div class="custom-table-container" :style="tableContainerStyle">
<!-- 表格 -->
<el-table
:data="data"
:style="tableStyle"
border
@selection-change="handleSelectionChange"
>
<!-- 多选框列 -->
<el-table-column
v-if="showSelection"
type="selection"
:width="selectionWidth"
fixed="left"
/>
<!-- 开始插槽(第一列) -->
<el-table-column
v-if="$slots.start"
:width="startColumnWidth"
fixed="left"
:label="startColumnLabel"
>
<template #default="{ row }">
<slot name="start" :row="row"></slot>
</template>
</el-table-column>
<!-- 动态列 -->
<el-table-column
v-for="column in columns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:width="column.width"
>
<template #default="{ row }">
<!-- 自定义插槽 -->
<template v-if="$slots[column.type]">
<slot :name="column.type" :row="row"></slot>
</template>
<!-- 默认显示 -->
<template v-else>
<span
v-if="column.formatter && typeof column.formatter === 'function'"
:style="{ whiteSpace: column.noWrap ? 'nowrap' : 'normal' }"
>
{{ column.formatter(row) }}
</span>
<span
v-else
:style="{ whiteSpace: column.noWrap ? 'nowrap' : 'normal' }"
>
{{ row[column.prop] }}
</span>
</template>
</template>
</el-table-column>
<!-- 结束插槽(最后一列) -->
<el-table-column
v-if="$slots.end"
:width="endColumnWidth"
fixed="right"
:label="endColumnLabel"
>
<template #default="{ row }">
<slot name="end" :row="row"></slot>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container" v-if="showPagination">
<el-pagination
:current-page="localCurrentPage"
:page-size="localPageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
// 接收的props
const props = defineProps({
// 表格列配置
columns: {
type: Array,
required: true
},
// 表格数据
data: {
type: Array,
default: () => []
},
// 总条数
total: {
type: Number,
default: 0
},
// 当前页码
currentPage: {
type: Number,
default: 1
},
// 每页条数
pageSize: {
type: Number,
default: 10
},
// 是否显示分页
showPagination: {
type: Boolean,
default: true
},
// 多选框配置
showSelection: {
type: Boolean,
default: false
},
// 多选框列宽度
selectionWidth: {
type: [String, Number],
default: '55px'
},
// 首列标题
startColumnLabel: {
type: String,
default: ''
},
// 末列标题
endColumnLabel: {
type: String,
default: ''
},
// 表格宽度
tableWidth: {
type: String,
default: '100%'
},
// 表格高度
tableHeight: {
type: String,
default: ''
},
// 开始插槽宽度
startColumnWidth: {
type: [String, Number],
default: '80px'
},
// 结束插槽宽度
endColumnWidth: {
type: [String, Number],
default: '150px'
}
});
// 定义事件
const emit = defineEmits(['page-change', 'selection-change']);
// 本地分页数据
const localCurrentPage = ref(props.currentPage);
const localPageSize = ref(props.pageSize);
// 表格样式
const tableStyle = computed(() => {
const style = {
width: props.tableWidth
};
if (props.tableHeight) {
style.height = props.tableHeight;
}
return style;
});
// 表格容器样式
const tableContainerStyle = computed(() => {
const style = {};
if (props.tableWidth !== '100%') {
style.width = props.tableWidth;
}
return style;
});
// 监听props变化
watch(() => props.currentPage, (newVal) => {
localCurrentPage.value = newVal;
});
watch(() => props.pageSize, (newVal) => {
localPageSize.value = newVal;
});
// 处理每页条数变化
const handleSizeChange = (size) => {
localPageSize.value = size;
emit('page-change', {
currentPage: localCurrentPage.value,
pageSize: size
});
};
// 处理当前页码变化
const handleCurrentChange = (current) => {
localCurrentPage.value = current;
emit('page-change', {
currentPage: current,
pageSize: localPageSize.value
});
};
// 处理多选框选择变化
const handleSelectionChange = (selection) => {
emit('selection-change', selection);
};
// 暴露方法
defineExpose({
// 刷新表格
refresh: () => {
// 可以在这里添加刷新逻辑
},
// 获取当前分页信息
getPagination: () => {
return {
currentPage: localCurrentPage.value,
pageSize: localPageSize.value
};
}
});
</script>
<style scoped>
.custom-table-container {
width: 100%;
margin: 0 auto;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-start;
}
</style>3.3 引入组件
vue
<template>
<CustomTable
:columns="tableColumns"
:data="tableData"
:total="total"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
@page-change="handlePageChange"
>
<!-- 插槽内容 -->
</CustomTable>
</template>
<script setup>
import CustomTable from './components/CustomTable.vue';
// 组件配置
const tableColumns = ref([
// 列配置
]);
const tableData = ref([]);
const total = ref(0);
const pagination = ref({
currentPage: 1,
pageSize: 10
});
const handlePageChange = (pageInfo) => {
// 分页处理逻辑
};
</script>4. Props 详细说明
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
columns | Array | 必填 | 表格列配置,详细见下方列配置说明 |
data | Array | [] | 表格数据 |
total | Number | 0 | 总条数,用于分页显示 |
currentPage | Number | 1 | 当前页码 |
pageSize | Number | 10 | 每页条数 |
showPagination | Boolean | true | 是否显示分页 |
showSelection | Boolean | false | 是否显示多选框 |
selectionWidth | [String, Number] | '55px' | 多选框列宽度 |
startColumnLabel | String | '' | 首列标题 |
endColumnLabel | String | '' | 末列标题 |
tableWidth | String | '100%' | 表格宽度 |
tableHeight | String | '' | 表格高度,为空时自适应 |
startColumnWidth | [String, Number] | '80px' | 开始插槽宽度 |
endColumnWidth | [String, Number] | '150px' | 结束插槽宽度 |
4.1 列配置说明
| 字段 | 类型 | 说明 |
|---|---|---|
label | String | 列标题 |
prop | String | 数据属性名 |
width | [String, Number] | 列宽度 |
type | String | 列类型,用于自定义插槽 |
formatter | Function | 格式化函数,接收行数据作为参数,返回格式化后的值 |
noWrap | Boolean | 是否禁止换行,默认 false(支持换行) |
5. Events 详细说明
| Event | 参数 | 说明 |
|---|---|---|
page-change | { currentPage, pageSize } | 分页变化时触发,返回当前页码和每页条数 |
selection-change | selection | 多选框选择变化时触发,返回选中的行数据数组 |
6. Slots 详细说明
| Slot | 参数 | 说明 |
|---|---|---|
start | { row } | 首列插槽,位于多选框列之后(如果显示多选框) |
end | { row } | 末列插槽,位于所有动态列之后 |
[type] | { row } | 自定义类型插槽,根据列配置中的 type 字段匹配 |
7. 方法说明
| 方法 | 返回值 | 说明 |
|---|---|---|
refresh() | 无 | 刷新表格 |
getPagination() | { currentPage, pageSize } | 获取当前分页信息 |
8. 完整使用示例
8.1 基础使用
vue
<template>
<CustomTable
:columns="tableColumns"
:data="tableData"
:total="total"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
@page-change="handlePageChange"
/>
</template>
<script setup>
import { ref } from 'vue';
import CustomTable from './components/CustomTable.vue';
const tableColumns = ref([
{
label: '姓名',
prop: 'name',
width: '120px'
},
{
label: '年龄',
prop: 'age',
width: '80px'
},
{
label: '性别',
prop: 'gender',
width: '80px'
}
]);
const tableData = ref([
{ id: 1, name: '张三', age: 25, gender: '男' },
{ id: 2, name: '李四', age: 30, gender: '女' }
]);
const total = ref(2);
const pagination = ref({
currentPage: 1,
pageSize: 10
});
const handlePageChange = (pageInfo) => {
pagination.value.currentPage = pageInfo.currentPage;
pagination.value.pageSize = pageInfo.pageSize;
// 加载数据
};
</script>8.2 完整使用示例代码
vue
<template>
<div class="app-container">
<h1>CustomTable 组件使用示例</h1>
<!-- 搜索框和控制按钮 -->
<div class="search-container">
<el-input
v-model="searchQuery"
placeholder="请输入姓名"
style="width: 300px"
clearable
>
<template #append>
<el-button @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
</template>
</el-input>
<el-checkbox v-model="showSelection" style="margin-left: 20px">
显示多选框
</el-checkbox>
<el-button type="primary" @click="handleViewSelected" style="margin-left: 20px">
查看选中值
</el-button>
</div>
<!-- 表格组件 -->
<CustomTable
:columns="tableColumns"
:data="tableData"
:total="total"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
:show-pagination="true"
:show-selection="showSelection"
:start-column-label="'操作'"
:end-column-label="'操作'"
:table-width="'100%'"
:table-height="'500px'"
:start-column-width="'100px'"
:end-column-width="'180px'"
@page-change="handlePageChange"
@selection-change="handleSelectionChange"
>
<!-- 首列插槽 -->
<template #start="{ row }">
<el-button type="info" size="small">
开始{{row.id}}
</el-button>
</template>
<!-- 末列插槽 -->
<template #end="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)" style="margin-right: 5px">
编辑
</el-button>
<el-button type="danger" size="small" @click="handleDelete(row.id)">
删除
</el-button>
</template>
<!-- 自定义类型插槽 -->
<template #status="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'danger'">
{{ row.status === 'active' ? '自定义插槽激活' : '自定义插槽禁用' }}
</el-tag>
</template>
<template #action="{ row }">
<el-button type="info" size="small">
自定义插槽{{row.id}}
</el-button>
</template>
</CustomTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import CustomTable from './components/CustomTable.vue';
import { Search } from '@element-plus/icons-vue';
// 搜索查询
const searchQuery = ref('');
// 多选框开关
const showSelection = ref(false);
// 选中的行数据
const selectedRows = ref([]);
// 分页参数
const pagination = ref({
currentPage: 1,
pageSize: 10
});
// 总条数
const total = ref(100);
// 表格列配置
const tableColumns = ref([
{
label: '姓名',
prop: 'name',
width: '120px'
},
{
label: '年龄',
prop: 'age',
width: '80px'
},
{
label: '性别',
prop: 'gender',
width: '80px'
},
{
label: '邮箱',
prop: 'email',
width: '200px'
},
{
label: '状态',
prop: 'status',
width: '150px',
type: 'status' // 自定义类型,用于插槽
},
{
label: '操作',
prop: 'action',
width: '120px',
type: 'action' // 自定义类型,用于插槽
}
]);
// 表格数据
const tableData = ref([]);
// 固定的模拟数据
const mockData = ref([]);
// 生成模拟数据
const generateMockData = (count) => {
const data = [];
for (let i = 1; i <= count; i++) {
data.push({
id: i,
name: `用户${i}`,
age: Math.floor(Math.random() * 30) + 20,
gender: Math.random() > 0.5 ? '男' : '女',
email: `user${i}@example.com`,
status: Math.random() > 0.5 ? 'active' : 'inactive',
checked: false
});
}
return data;
};
// 初始化数据
mockData.value = generateMockData(100); // 生成100条固定数据
// 加载数据
const loadData = (currentPage, pageSize, searchKeyword = '') => {
// 模拟异步加载
setTimeout(() => {
let filteredData = [...mockData.value]; // 使用固定的模拟数据
// 真实筛选逻辑
if (searchKeyword) {
filteredData = filteredData.filter(item =>
item.name.toLowerCase().includes(searchKeyword.toLowerCase())
);
}
// 计算总条数
total.value = filteredData.length;
// 分页处理
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
tableData.value = filteredData.slice(startIndex, endIndex);
}, 300);
};
// 搜索
const handleSearch = () => {
// 重置页码
pagination.value.currentPage = 1;
// 重新加载数据(带搜索条件)
loadData(pagination.value.currentPage, pagination.value.pageSize, searchQuery.value);
};
// 分页变化
const handlePageChange = (pageInfo) => {
pagination.value.currentPage = pageInfo.currentPage;
pagination.value.pageSize = pageInfo.pageSize;
// 重新加载数据(带搜索条件)
loadData(pageInfo.currentPage, pageInfo.pageSize, searchQuery.value);
};
// 处理多选框选择变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection;
};
// 查看选中值
const handleViewSelected = () => {
if (selectedRows.value.length === 0) {
alert('请先选择数据');
return;
}
console.log('选中的数据:', selectedRows.value);
alert(`已选中 ${selectedRows.value.length} 条数据,详情请查看控制台`);
};
// 初始化
onMounted(() => {
loadData(pagination.value.currentPage, pagination.value.pageSize);
});
</script>
<style scoped>
.app-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.search-container {
margin-bottom: 20px;
}
/* 表格样式 */
:deep(.el-table) {
margin-top: 20px;
}
</style>9. 常见问题和解决方案
9.1 表格数据不显示
问题:表格没有显示数据
解决方案:
- 检查
data属性是否正确传递 - 检查
columns配置是否正确,确保prop字段与数据对象的属性名匹配
9.2 分页不生效
问题:分页组件显示但点击页码无反应
解决方案:
- 确保添加了
@page-change事件监听器 - 在事件处理函数中更新
currentPage和pageSize,并重新加载数据
9.3 多选框不显示
问题:启用 showSelection 后多选框不显示
解决方案:
- 确保
showSelection属性设置为true - 检查表格数据是否有唯一的
id字段(Element Plus 表格需要)
9.4 插槽不显示
问题:自定义插槽内容不显示
解决方案:
- 对于首列和末列插槽,确保使用了正确的插槽名称:
#start和#end - 对于自定义类型插槽,确保列配置中的
type字段与插槽名称匹配
9.5 表格宽度不正确
问题:表格宽度没有按照设置的值显示
解决方案:
- 确保
tableWidth属性设置正确,如'100%'或'800px' - 检查父容器是否有宽度限制
10. 浏览器兼容性
- Chrome ✅
- Firefox ✅
- Safari ✅
- Edge ✅
11. 版本历史
| 版本 | 日期 | 变更内容 |
|---|---|---|
| 1.0.0 | 2024-01-01 | 初始版本 |
| 1.0.1 | 2024-01-02 | 添加多选框功能 |
| 1.0.2 | 2024-01-03 | 优化表格样式和配置选项 |
12. 总结
CustomTable 组件是一个功能强大、配置灵活的表格组件,它基于 Element Plus 进行封装,提供了丰富的功能和良好的用户体验。通过本文档的介绍,您应该能够快速上手并使用 CustomTable 组件来构建各种复杂的表格场景。
如果您在使用过程中遇到任何问题,欢迎随时提出反馈和建议。祝您使用愉快!
