vue2最强封装table 拓展性嘎嘎强
DynamicTable是一个基于Element UI Table封装的Vue2动态表格组件,具有高度可配置性。主要特性包括:基础表格展示、分页功能(支持自定义分页布局和条数)、多选功能(可控制行可选性)、序号列(支持自定义计算)、列显示隐藏控制(支持布尔值或函数动态控制)。组件提供丰富的API,包括事件监听(选择变化、分页变化等)、方法调用(切换选择状态、获取选中行等)以及插槽(如底部插槽支持批
DynamicTable 动态表格组件 VUE2版本 常规版
DynamicTable 动态表格组件
一个基于 Element UI Table 封装的高度可配置的动态表格组件,支持多选、序号、自定义渲染、分页等功能。
📋 目录
⚙️ Vue2 JSX 配置
本组件的 render 函数支持现代的 JSX 语法。要在 Vue2 项目中使用 JSX,需要进行以下配置:
1. 安装依赖
npm install --save-dev @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
2. 配置 babel.config.js
在项目根目录的 babel.config.js 或 .babelrc 文件中添加 JSX 预设:
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
'@vue/babel-preset-jsx'
]
}
如果使用 .babelrc 文件:
{
"presets": [
"@vue/cli-plugin-babel/preset",
"@vue/babel-preset-jsx"
]
}
3. 配置 webpack(如果需要)
如果你使用的是自定义 webpack 配置,确保 babel-loader 能够处理 JSX:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@vue/babel-preset-jsx']
}
}
}
]
}
}
4. TypeScript 支持(可选)
如果你的项目使用 TypeScript,需要在 tsconfig.json 中配置:
{
"compilerOptions": {
"jsx": "preserve",
"jsxFactory": "h"
}
}
并安装 TypeScript JSX 类型定义:
npm install --save-dev @vue/babel-preset-jsx
5. ESLint 配置(可选)
如果使用 ESLint,可能需要配置 JSX 支持:
// .eslintrc.js
module.exports = {
extends: [
'plugin:vue/essential'
],
parserOptions: {
ecmaFeatures: {
jsx: true
}
},
plugins: ['vue']
}
6. 验证配置
配置完成后,重启开发服务器,然后可以在组件中使用 JSX 语法:
// 测试 JSX 是否工作正常
{
label: '测试',
prop: 'test',
render: (h, { row, column, value, index }) => {
return (
<div>
<el-tag type="success">JSX 工作正常!</el-tag>
</div>
);
}
}
常见问题排查
- 编译错误: 确保安装了正确的 babel 预设和插件
- 语法高亮: 如果使用 VSCode,安装 “Vetur” 或 “Vue Language Features (Volar)” 插件
- 热重载: JSX 更改后可能需要手动刷新页面
- 属性命名: JSX 中使用驼峰命名(如
onClick),而不是 kebab-case(on-click)
🚀 基本用法
最简单的用法
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
/>
</template>
<script>
export default {
data() {
return {
tableData: [
{ id: 1, name: '张三', age: 25, city: '北京' },
{ id: 2, name: '李四', age: 30, city: '上海' }
],
tableColumns: [
{ label: '姓名', prop: 'name' },
{ label: '年龄', prop: 'age' },
{ label: '城市', prop: 'city' }
]
}
}
}
</script>
带分页的表格
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
:total="total"
:current-page.sync="currentPage"
:page-size="pageSize"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
📝 Props 属性
基础属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data |
Array | [] |
表格数据 |
columns |
Array | [] |
列配置数组 |
loading |
Boolean | false |
表格加载状态 |
height |
String | '48vh' |
表格高度 |
rowKey |
String | 'id' |
行数据的 Key |
分页相关
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
showPagination |
Boolean | true |
是否显示分页 |
currentPage |
Number | 1 |
当前页码 |
pageSize |
Number | 60 |
每页显示条目个数 |
pageSizes |
Array | [60, 100, 150, 200, 250, 500] |
每页显示个数选择器的选项 |
total |
Number | 0 |
总条目数 |
paginationLayout |
String | 'total, sizes,prev, pager, next,jumper' |
分页组件布局 |
多选功能
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
showSelection |
Boolean | false |
是否显示多选框 |
selectable |
Function | null |
控制某一行是否可选择的函数 |
序号功能
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
showIndex |
Boolean | false |
是否显示序号列 |
indexLabel |
String | '序号' |
序号列标题 |
indexWidth |
String/Number | 60 |
序号列宽度 |
indexMethod |
Function | null |
自定义序号计算方法 |
📤 Events 事件
| 事件名 | 说明 | 回调参数 |
|---|---|---|
selection-change |
当选择项发生变化时触发 | selection 已选择的行数据数组 |
size-change |
当每页条数改变时触发 | pageSize 新的每页条数 |
current-change |
当当前页改变时触发 | currentPage 新的当前页 |
🔧 Methods 方法
| 方法名 | 说明 | 参数 |
|---|---|---|
toggleRowSelection |
切换某一行的选中状态 | (row, selected) |
clearSelection |
清空选择 | - |
toggleAllSelection |
切换全选 | - |
getSelectedRows |
获取选中的行数据 | - |
setCurrentRow |
设置某一行为选中状态 | (row) |
🎯 Slots 插槽
footer 插槽
表格底部插槽,可以用来放置批量操作按钮等
<dynamic-table>
<template #footer="{ selectedRows }">
<div v-if="selectedRows.length > 0">
<el-button type="primary" @click="batchDelete">
批量删除 ({{ selectedRows.length }})
</el-button>
</div>
</template>
</dynamic-table>
📋 列配置详解
每个列配置对象支持以下属性:
基础配置
{
label: '列标题', // 显示的标题
prop: 'fieldName', // 对应列内容的字段名
align: 'center', // 对齐方式:left/center/right
width: 120, // 固定宽度
minWidth: 100, // 最小宽度
fixed: 'left', // 固定列:left/right
sortable: true, // 是否可排序
showOverflowTooltip: true, // 是否显示溢出提示
visible: true // 控制列的显示隐藏
}
显示隐藏控制
使用 visible 属性可以灵活控制列的显示隐藏,支持多种用法:
// 1. 布尔值控制
{
label: '隐藏列',
prop: 'hidden',
visible: false // 始终隐藏
}
// 2. 函数动态控制(仅基于组件状态)
{
label: '管理员列',
prop: 'adminInfo',
visible: () => this.currentUserRole === 'admin' // 根据组件状态控制
}
// 3. 函数动态控制(支持行数据参数)
{
label: '权限列',
prop: 'permission',
visible: (row, column, cellValue, index) => {
// 可以访问行数据、列配置、单元格值和行索引
return row.userRole === 'admin' && row.status === 'active' && cellValue;
}
}
// 4. 复杂条件控制
{
label: '操作时间',
prop: 'operationTime',
visible: (row, column, cellValue) => {
// 结合组件状态和行数据进行判断
return this.params.status !== "0" && row.hasOperationTime;
}
}
// 5. 基于单元格值的控制
{
label: '备注',
prop: 'remark',
visible: (row, column, cellValue) => {
// 只有当备注有内容时才显示该列
return cellValue && cellValue.trim() !== '';
}
}
// 6. 基于行索引的控制
{
label: '特殊列',
prop: 'special',
visible: (row, column, cellValue, index) => {
// 只在偶数行显示
return index % 2 === 0 && this.showSpecialColumn;
}
}
visible 函数参数说明
当 visible 是函数时,会接收以下参数:
| 参数 | 类型 | 说明 |
|---|---|---|
row |
Object | 当前行的数据对象 |
column |
Object | 列的配置对象 |
cellValue |
Any | 当前单元格的值 (row[column.prop]) |
index |
Number | 行索引(从0开始) |
this |
Component | 组件实例的上下文,可以访问组件的数据和方法 |
列级别 vs 单元格级别控制
- 列级别控制:如果某列在所有行中都不可见,整个列会被隐藏(包括列头)
- 单元格级别控制:即使列显示,也可以在特定行中隐藏单元格内容
- 智能处理:组件会自动判断是否需要显示列头和列空间
格式化函数
formatter 用于格式化显示内容,不改变原始数据:
{
label: '金额',
prop: 'amount',
formatter: (row, column, cellValue, index) => {
return cellValue ? `¥${cellValue.toLocaleString()}` : '-';
}
}
// 格式化日期
{
label: '创建时间',
prop: 'createTime',
formatter: (row, column, cellValue) => {
return cellValue ? new Date(cellValue).toLocaleString() : '-';
}
}
// 格式化状态文本
{
label: '用户状态',
prop: 'userStatus',
formatter: (row, column, cellValue) => {
const statusMap = {
1: '正常',
0: '禁用',
-1: '删除'
};
return statusMap[cellValue] || '未知';
}
}
自定义渲染函数
支持传统的 createElement (h 函数) 语法和现代的 JSX 语法:
传统 h 函数语法
{
label: '状态',
prop: 'status',
render: (h, { row, column, value, index }) => {
const statusMap = {
0: { text: '待审核', type: 'warning' },
1: { text: '已通过', type: 'success' },
2: { text: '已拒绝', type: 'danger' }
};
const status = statusMap[value];
return h('el-tag', {
props: { type: status.type }
}, status.text);
}
}
JSX 语法(推荐)
注意: 使用 JSX 语法前,请确保已按照 Vue2 JSX 配置 章节完成相关配置。
{
label: '状态',
prop: 'status',
render: (h, { row, column, value, index }) => {
const statusMap = {
0: { text: '待审核', type: 'warning' },
1: { text: '已通过', type: 'success' },
2: { text: '已拒绝', type: 'danger' }
};
const status = statusMap[value];
return (
<el-tag type={status.type}>
{status.text}
</el-tag>
);
}
}
操作列示例
传统 h 函数语法
{
label: '操作',
prop: 'actions',
width: 180,
fixed: 'right',
render: (h, { row, column, value, index }) => {
const buttons = [];
buttons.push(
h('el-button', {
props: { size: 'mini', type: 'primary' },
on: { click: () => this.editRow(row) }
}, '编辑')
);
buttons.push(
h('el-button', {
props: { size: 'mini', type: 'danger' },
on: { click: () => this.deleteRow(row) }
}, '删除')
);
return h('div', buttons);
}
}
JSX 语法(推荐)
注意: 使用 JSX 语法前,请确保已按照 Vue2 JSX 配置 章节完成相关配置。
{
label: '操作',
prop: 'actions',
width: 180,
fixed: 'right',
render: (h, { row, column, value, index }) => {
return (
<div>
<el-button
size="mini"
type="primary"
style={{ marginRight: '8px' }}
onClick={() => this.editRow(row)}
>
编辑
</el-button>
<el-button
size="mini"
type="danger"
onClick={() => this.deleteRow(row)}
>
删除
</el-button>
</div>
);
}
}
🔥 高级用法
1. 条件显示列
使用 visible 属性根据数据或状态动态显示隐藏列:
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
/>
</template>
<script>
export default {
data() {
return {
currentUserRole: 'admin',
viewMode: 'detailed'
};
},
computed: {
tableColumns() {
return [
{
label: '用户名',
prop: 'username',
visible: true // 始终显示
},
{
label: '管理员信息',
prop: 'adminInfo',
visible: this.currentUserRole === 'admin' // 只有管理员能看到
},
{
label: '详细信息',
prop: 'details',
visible: this.viewMode === 'detailed' // 详细模式下显示
},
{
label: '状态',
prop: 'status',
visible: (row, column, cellValue) => {
// 根据行数据、组件状态和单元格值动态控制
return this.showStatusColumn &&
row.hasPermission &&
row.isActive &&
cellValue !== null;
}
}
];
}
}
};
</script>
2. 多选功能
2. 多选功能
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
:show-selection="true"
:selectable="selectableRow"
@selection-change="handleSelectionChange"
>
<template #footer="{ selectedRows }">
<div v-if="selectedRows.length > 0">
<el-button type="primary" @click="batchApprove">
批量通过 ({{ selectedRows.length }})
</el-button>
<el-button type="danger" @click="batchDelete">
批量删除 ({{ selectedRows.length }})
</el-button>
</div>
</template>
</dynamic-table>
</template>
<script>
export default {
methods: {
// 控制哪些行可以被选择
selectableRow(row, index) {
return row.status !== 'disabled';
},
// 处理选择变化
handleSelectionChange(selection) {
console.log('选中的行:', selection);
},
// 批量操作
batchApprove() {
const selectedRows = this.$refs.table.getSelectedRows();
// 执行批量通过逻辑
}
}
}
</script>
3. 序号功能
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
:show-index="true"
index-label="编号"
:index-width="80"
:index-method="customIndexMethod"
/>
</template>
<script>
export default {
methods: {
// 自定义序号计算
customIndexMethod(index) {
return `NO.${(this.currentPage - 1) * this.pageSize + index + 1}`;
}
}
}
</script>
3. 序号功能
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
:show-index="true"
index-label="编号"
:index-width="80"
:index-method="customIndexMethod"
/>
</template>
<script>
export default {
methods: {
// 自定义序号计算
customIndexMethod(index) {
return `NO.${(this.currentPage - 1) * this.pageSize + index + 1}`;
}
}
}
</script>
4. 复杂的 visible 和 render 组合使用
结合 visible 和 render 函数实现复杂的列控制:
<template>
<dynamic-table
:data="tableData"
:columns="tableColumns"
/>
</template>
<script>
export default {
data() {
return {
currentUserRole: 'admin',
viewMode: 'detailed',
showOperationTime: true
};
},
computed: {
tableColumns() {
return [
{
label: '用户信息',
prop: 'userInfo',
minWidth: 150,
render: (h, { row, column, value, index }) => {
return (
<div>
<div style={{ fontWeight: 'bold' }}>{row.username}</div>
<div style={{ fontSize: '12px', color: '#999' }}>{row.email}</div>
</div>
);
}
},
{
label: '待结算金额',
prop: 'pendingAmount',
align: 'right',
width: 120,
visible: (row, column, cellValue) => {
// 只在待结算状态下显示,且金额大于0
return this.currentStatus === 'pending' && cellValue > 0;
},
formatter: (row, column, cellValue) => {
return `¥${cellValue.toLocaleString()}`;
}
},
{
label: '操作时间',
prop: 'operationTime',
width: 160,
visible: (row, column, cellValue) => {
// 根据用户权限、显示设置和数据存在性控制
return this.currentUserRole === 'admin' &&
this.showOperationTime &&
cellValue;
},
formatter: (row, column, cellValue) => {
return new Date(cellValue).toLocaleString();
}
},
{
label: '状态标签',
prop: 'statusTags',
width: 200,
visible: (row, column, cellValue) => {
// 只有当用户有标签时才显示该列
return Array.isArray(cellValue) && cellValue.length > 0;
},
render: (h, { row, column, value }) => {
return (
<div>
{value.map((tag, index) => (
<el-tag
key={index}
size="mini"
style={{ marginRight: '4px' }}
type={tag.type}
>
{tag.text}
</el-tag>
))}
</div>
);
}
},
{
label: '动态操作',
prop: 'dynamicActions',
width: 200,
fixed: 'right',
visible: (row, column, cellValue, index) => {
// 根据行数据动态显示操作列
return row.canOperate || this.currentUserRole === 'admin';
},
render: (h, { row, column, value, index }) => {
const actions = [];
// 根据行数据动态生成按钮
if (row.canEdit) {
actions.push(
<el-button
size="mini"
type="primary"
style={{ marginRight: '8px' }}
onClick={() => this.editRow(row)}
>
编辑
</el-button>
);
}
if (row.canDelete && this.currentUserRole === 'admin') {
actions.push(
<el-button
size="mini"
type="danger"
onClick={() => this.deleteRow(row)}
>
删除
</el-button>
);
}
if (row.status === 'pending') {
actions.push(
<el-button
size="mini"
type="success"
onClick={() => this.approveRow(row)}
>
审核
</el-button>
);
}
return <div>{actions}</div>;
}
}
];
}
},
methods: {
editRow(row) {
console.log('编辑:', row);
},
deleteRow(row) {
console.log('删除:', row);
},
approveRow(row) {
console.log('审核:', row);
}
}
};
</script>
使用 JSX 语法可以更方便地编写复杂的渲染逻辑:
// 操作列 JSX 示例
{
label: '操作',
prop: 'actions',
width: 220,
fixed: 'right',
render: (h, { row, column, value, index }) => {
return (
<div>
{/* 详情按钮 - 始终显示 */}
<el-button
size="mini"
type="primary"
style={{ marginRight: '8px' }}
onClick={() => this.viewDetail(row)}
>
详情
</el-button>
{/* 编辑按钮 - 条件显示 */}
{row.canEdit && (
<el-button
size="mini"
type="warning"
style={{ marginRight: '8px' }}
onClick={() => this.editRow(row)}
>
编辑
</el-button>
)}
{/* 下拉菜单 - 更多操作 */}
<el-dropdown onCommand={(command) => this.handleCommand(command, row)}>
<el-button size="mini">
更多<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="approve">审核通过</el-dropdown-item>
<el-dropdown-item command="reject">审核拒绝</el-dropdown-item>
{row.canDelete && (
<el-dropdown-item command="delete" divided>删除</el-dropdown-item>
)}
</el-dropdown-menu>
</el-dropdown>
</div>
);
}
}
// 状态渲染 JSX 示例
{
label: '订单状态',
prop: 'orderStatus',
width: 120,
render: (h, { row, column, value, index }) => {
const statusConfig = {
'pending': { text: '待付款', color: '#E6A23C', icon: 'el-icon-time' },
'paid': { text: '已付款', color: '#67C23A', icon: 'el-icon-success' },
'shipped': { text: '已发货', color: '#409EFF', icon: 'el-icon-truck' },
'completed': { text: '已完成', color: '#67C23A', icon: 'el-icon-circle-check' },
'cancelled': { text: '已取消', color: '#F56C6C', icon: 'el-icon-circle-close' }
};
const config = statusConfig[value] || {};
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<i
class={config.icon}
style={{ color: config.color, marginRight: '4px' }}
></i>
<span style={{ color: config.color, fontWeight: 'bold' }}>
{config.text}
</span>
</div>
);
}
}
5. JSX 复杂渲染示例
// 状态列配置
{
label: '订单状态',
prop: 'orderStatus',
width: 120,
render: (h, { row, column, value, index }) => {
const statusConfig = {
'pending': { text: '待付款', color: '#E6A23C', icon: 'el-icon-time' },
'paid': { text: '已付款', color: '#67C23A', icon: 'el-icon-success' },
'shipped': { text: '已发货', color: '#409EFF', icon: 'el-icon-truck' },
'completed': { text: '已完成', color: '#67C23A', icon: 'el-icon-circle-check' },
'cancelled': { text: '已取消', color: '#F56C6C', icon: 'el-icon-circle-close' }
};
const config = statusConfig[value] || {};
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
}, [
h('i', {
class: config.icon,
style: {
color: config.color,
marginRight: '4px'
}
}),
h('span', {
style: {
color: config.color,
fontWeight: 'bold'
}
}, config.text)
]);
}
}
📘 完整示例
基础完整示例
<template>
<div class="table-container">
<dynamic-table
ref="dynamicTable"
:data="tableData"
:columns="tableColumns"
:loading="loading"
:total="total"
:current-page.sync="currentPage"
:page-size="pageSize"
:show-selection="true"
:show-index="true"
:selectable="selectableRow"
row-key="id"
@selection-change="handleSelectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #footer="{ selectedRows }">
<div v-if="selectedRows.length > 0" class="batch-operations">
<el-button
type="primary"
size="small"
@click="batchApprove"
>
批量通过 ({{ selectedRows.length }})
</el-button>
<el-button
type="danger"
size="small"
@click="batchDelete"
>
批量删除 ({{ selectedRows.length }})
</el-button>
</div>
</template>
</dynamic-table>
</div>
</template>
<script>
import DynamicTable from '@/components/common/DynamicTable.vue';
export default {
components: {
DynamicTable
},
data() {
return {
loading: false,
tableData: [],
total: 0,
currentPage: 1,
pageSize: 20,
tableColumns: [
{
label: '用户名',
prop: 'username',
align: 'left',
minWidth: 120,
showOverflowTooltip: true
},
{
label: '邮箱',
prop: 'email',
align: 'left',
minWidth: 180
},
{
label: '金额',
prop: 'amount',
align: 'right',
width: 120,
formatter: (row, column, cellValue) => {
return cellValue ? `¥${cellValue.toLocaleString()}` : '-';
}
},
{
label: '状态',
prop: 'status',
width: 100,
render: (h, { row, column, value, index }) => {
const statusMap = {
0: { text: '待审核', type: 'warning' },
1: { text: '已通过', type: 'success' },
2: { text: '已拒绝', type: 'danger' }
};
const status = statusMap[value];
return (
<el-tag type={status?.type}>
{status?.text || '-'}
</el-tag>
);
}
},
{
label: '创建时间',
prop: 'createTime',
width: 160,
sortable: true
},
{
label: '操作',
prop: 'actions',
width: 180,
fixed: 'right',
render: (h, { row, column, value, index }) => {
return (
<div>
<el-button
size="mini"
type="primary"
style={{ marginRight: '8px' }}
onClick={() => this.editRow(row)}
>
编辑
</el-button>
<el-button
size="mini"
type="danger"
onClick={() => this.deleteRow(row)}
>
删除
</el-button>
</div>
);
}
}
]
};
},
created() {
this.loadData();
},
methods: {
// 加载数据
async loadData() {
this.loading = true;
try {
// 模拟 API 调用
const response = await this.$api.getUserList({
page: this.currentPage,
size: this.pageSize
});
this.tableData = response.data.list;
this.total = response.data.total;
} catch (error) {
console.error('加载数据失败:', error);
} finally {
this.loading = false;
}
},
// 控制行是否可选择
selectableRow(row, index) {
return row.status !== 2; // 已拒绝的不可选择
},
// 处理选择变化
handleSelectionChange(selection) {
console.log('选中的行:', selection);
},
// 分页大小变化
handleSizeChange(size) {
this.pageSize = size;
this.currentPage = 1;
this.loadData();
},
// 当前页变化
handleCurrentChange(page) {
this.currentPage = page;
this.loadData();
},
// 编辑行
editRow(row) {
console.log('编辑:', row);
// 实现编辑逻辑
},
// 删除行
deleteRow(row) {
this.$confirm('确定要删除这条记录吗?', '提示', {
type: 'warning'
}).then(() => {
// 实现删除逻辑
console.log('删除:', row);
});
},
// 批量通过
batchApprove() {
const selectedRows = this.$refs.dynamicTable.getSelectedRows();
console.log('批量通过:', selectedRows);
// 实现批量通过逻辑
},
// 批量删除
batchDelete() {
const selectedRows = this.$refs.dynamicTable.getSelectedRows();
this.$confirm(`确定要删除选中的 ${selectedRows.length} 条记录吗?`, '提示', {
type: 'warning'
}).then(() => {
console.log('批量删除:', selectedRows);
// 实现批量删除逻辑
});
}
}
};
</script>
<style scoped>
.table-container {
padding: 20px;
}
.batch-operations {
display: flex;
gap: 8px;
}
</style>
高级 visible 控制示例
这是一个基于业务状态的复杂表格示例,展示如何使用增强的 visible 功能:
<template>
<div class="order-management">
<!-- 状态切换 -->
<el-tabs v-model="currentStatus" @tab-click="handleTabChange">
<el-tab-pane label="待处理" name="pending" />
<el-tab-pane label="处理中" name="processing" />
<el-tab-pane label="已完成" name="completed" />
</el-tabs>
<dynamic-table
:data="orderData"
:columns="orderColumns"
:loading="loading"
/>
</div>
</template>
<script>
export default {
data() {
return {
currentStatus: 'pending',
currentUserRole: 'admin', // admin, operator, viewer
loading: false,
orderData: [
{
id: 1,
orderNo: 'ORD001',
customerName: '张三',
amount: 1000,
status: 'pending',
operatorName: '',
operationTime: '',
hasRefund: false,
refundAmount: 0,
remark: '重要订单',
canEdit: true,
canDelete: false
}
]
};
},
computed: {
orderColumns() {
return [
{
label: '订单号',
prop: 'orderNo',
width: 120,
fixed: 'left'
},
{
label: '客户姓名',
prop: 'customerName',
width: 100
},
{
label: '订单金额',
prop: 'amount',
width: 120,
align: 'right',
formatter: (row, column, cellValue) => {
return `¥${cellValue.toLocaleString()}`;
}
},
{
label: '退款金额',
prop: 'refundAmount',
width: 120,
align: 'right',
// 只有存在退款时才显示该列
visible: (row, column, cellValue) => {
return row.hasRefund && cellValue > 0;
},
formatter: (row, column, cellValue) => {
return `¥${cellValue.toLocaleString()}`;
}
},
{
label: '处理人',
prop: 'operatorName',
width: 100,
// 只在非待处理状态下显示
visible: (row, column, cellValue) => {
return this.currentStatus !== 'pending' && cellValue;
}
},
{
label: '处理时间',
prop: 'operationTime',
width: 160,
// 只在非待处理状态下显示,且有处理时间
visible: (row, column, cellValue) => {
return this.currentStatus !== 'pending' && cellValue;
},
formatter: (row, column, cellValue) => {
return new Date(cellValue).toLocaleString();
}
},
{
label: '备注',
prop: 'remark',
minWidth: 150,
// 只有管理员且有备注内容时才显示
visible: (row, column, cellValue) => {
return this.currentUserRole === 'admin' &&
cellValue &&
cellValue.trim() !== '';
},
showOverflowTooltip: true
},
{
label: '状态',
prop: 'status',
width: 100,
render: (h, { row, column, value }) => {
const statusMap = {
'pending': { text: '待处理', type: 'warning' },
'processing': { text: '处理中', type: 'primary' },
'completed': { text: '已完成', type: 'success' }
};
const status = statusMap[value];
return (
<el-tag type={status.type}>
{status.text}
</el-tag>
);
}
},
{
label: '操作',
prop: 'actions',
width: 200,
fixed: 'right',
// 根据用户权限和订单状态控制操作列显示
visible: (row, column, cellValue) => {
return this.currentUserRole !== 'viewer' &&
(row.canEdit || row.canDelete || this.currentUserRole === 'admin');
},
render: (h, { row, column, value, index }) => {
const actions = [];
// 待处理状态的操作
if (this.currentStatus === 'pending') {
if (row.canEdit) {
actions.push(
<el-button
size="mini"
type="primary"
style={{ marginRight: '8px' }}
onClick={() => this.processOrder(row)}
>
处理
</el-button>
);
}
}
// 处理中状态的操作
if (this.currentStatus === 'processing') {
actions.push(
<el-button
size="mini"
type="success"
style={{ marginRight: '8px' }}
onClick={() => this.completeOrder(row)}
>
完成
</el-button>
);
}
// 管理员可以删除(除了已完成的订单)
if (this.currentUserRole === 'admin' && this.currentStatus !== 'completed') {
if (row.canDelete) {
actions.push(
<el-button
size="mini"
type="danger"
onClick={() => this.deleteOrder(row)}
>
删除
</el-button>
);
}
}
// 详情按钮始终显示
actions.push(
<el-button
size="mini"
type="info"
onClick={() => this.viewOrderDetail(row)}
>
详情
</el-button>
);
return <div>{actions}</div>;
}
}
];
}
},
methods: {
handleTabChange(tab) {
this.currentStatus = tab.name;
this.loadOrderData();
},
async loadOrderData() {
this.loading = true;
try {
// 根据状态加载不同的数据
const response = await this.$api.getOrderList({
status: this.currentStatus
});
this.orderData = response.data.list;
} catch (error) {
console.error('加载订单数据失败:', error);
} finally {
this.loading = false;
}
},
processOrder(row) {
console.log('处理订单:', row);
},
completeOrder(row) {
console.log('完成订单:', row);
},
deleteOrder(row) {
console.log('删除订单:', row);
},
viewOrderDetail(row) {
console.log('查看详情:', row);
}
}
};
</script>
🎨 样式自定义
组件支持通过 CSS 变量和类名覆盖来自定义样式:
/* 自定义分页样式 */
.dynamic-table .pagination-wrapper {
text-align: center;
margin-top: 30px;
}
/* 自定义底部信息栏样式 */
.dynamic-table .table-footer {
background-color: #f5f7fa;
padding: 16px;
border-radius: 4px;
}
/* 自定义选择信息样式 */
.dynamic-table .selection-info {
font-weight: bold;
color: #409eff;
}
📝 注意事项
- 性能优化: 当数据量较大时,建议使用虚拟滚动或分页
- 行键设置: 使用多选功能时,务必设置正确的
row-key - 渲染函数: render 函数中的
this指向父组件,可以调用父组件方法 - 事件处理: 所有 Element UI Table 的原生事件都会透传
- 响应式: 表格会自动适应容器宽度,建议设置合适的
minWidth - JSX 语法配置:
- 使用 JSX 前必须完成 Vue2 JSX 配置
- JSX 中使用驼峰命名属性(如
onClick而不是on-click) - 事件处理使用箭头函数语法:
onClick={() => this.handleClick()} - 样式对象使用驼峰命名:
style={{ marginRight: '8px' }}
- visible 函数增强:
- 支持
(row, column, cellValue, index)四个参数 - 可以根据行数据、列配置、单元格值和行索引动态控制显示
- 函数内可以通过
this访问组件实例的数据和方法 - 自动处理列级别和单元格级别的显示控制
- 支持
- 列控制最佳实践:
- 优先使用组件状态控制列显示,性能更好
- 需要基于行数据控制时,考虑数据量对性能的影响
- 复杂的显示逻辑建议缓存到计算属性中
- 错误处理: visible 函数执行出错时,默认显示该列,确保界面稳定性
🔄 更新日志
v1.2.1
- 📝 文档更新: 新增完整的 Vue2 JSX 配置指南
- 📝 补充 JSX 语法使用的前置配置要求说明
- 📝 完善 JSX 相关注意事项和最佳实践
v1.2.0
- 🚀 重大更新:
visible函数现在支持(row, column, cellValue, index)参数 - ✨ 新增列级别和单元格级别的智能显示控制
- ✨ 增强的
visible函数可以根据行数据、列配置、单元格值动态控制显示 - 🔧 优化列过滤算法,提供更好的性能和用户体验
- 📝 完善文档,添加更多实际使用案例和最佳实践
v1.1.0
- ✨ 新增
visible属性支持列的显示隐藏控制 - ✨ 新增 JSX 语法支持,render 函数可使用现代 JSX 语法
- 🔧 优化 RenderCell 组件,更好地支持 JSX 和传统语法
- 📝 更新文档,添加 JSX 语法示例和 visible 属性用法
v1.0.0
- ✨ 新增多选功能
- ✨ 新增序号列功能
- ✨ 新增自定义渲染函数支持
- ✨ 新增底部插槽和选择信息显示
- ✨ 新增分页功能
- ✨ 支持所有 Element UI Table 属性透传
完整代码
<template>
<div class="dynamic-table">
<el-table
ref="table"
v-loading="loading"
:data="data"
border
:row-key="rowKey"
stripe
v-bind="$attrs"
v-on="$listeners"
@selection-change="handleSelectionChange"
>
<!-- 多选框列 -->
<el-table-column v-if="showSelection" type="selection" width="55" :selectable="selectable" align="center" />
<!-- 序号列 -->
<el-table-column
v-if="showIndex"
type="index"
:label="indexLabel"
:width="indexWidth"
align="center"
:index="getIndexMethod"
/>
<!-- 动态列 -->
<el-table-column
v-for="(column, index) in visibleColumns"
:key="index"
:align="column.align || 'center'"
:label="column.label"
:prop="column.prop"
:width="column.width"
:min-width="column.minWidth"
:fixed="column.fixed"
:sortable="column.sortable"
:show-overflow-tooltip="column.showOverflowTooltip !== false"
>
<template slot-scope="{ row, column: col, $index }">
<!-- 根据visible属性控制单元格显示隐藏 -->
<template v-if="getCellVisible(column, row, col, $index)">
<!-- 自定义渲染函数优先级最高 -->
<render-cell
v-if="column.render"
:render="column.render"
:row="row"
:column="col"
:value="row[column.prop]"
:index="$index"
/>
<!-- 格式化函数 -->
<span v-else-if="column.formatter">
{{ column.formatter(row, col, row[column.prop], $index) }}
</span>
<!-- 默认显示 -->
<span v-else class="default-cell">
{{ row[column.prop] === null || row[column.prop] === undefined ? "-" : row[column.prop] }}
</span>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination
v-if="showPagination"
:current-page.sync="currentPage"
:page-size="pageSize"
:page-sizes="pageSizes"
:total="total"
background
:layout="paginationLayout"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
class="pagination-wrapper"
/>
</div>
</template>
<script>
export default {
name: "DynamicTable",
components: {
RenderCell: {
functional: true,
props: {
render: Function,
row: Object,
column: Object,
value: [String, Number, Object, Array],
index: Number,
},
render: (h, ctx) => {
const { render, row, column, value, index } = ctx.props;
// 支持 JSX 语法和传统 h 函数
if (typeof render === 'function') {
return render(h, { row, column, value, index });
}
return null;
},
},
},
props: {
// 表格数据
data: {
type: Array,
default: () => [],
},
// 列配置
columns: {
type: Array,
default: () => [],
},
// 加载状态
loading: {
type: Boolean,
default: false,
},
// 行键
rowKey: {
type: String,
default: "id",
},
// 是否显示分页
showPagination: {
type: Boolean,
default: true,
},
// 当前页
currentPage: {
type: Number | Object,
default: 1,
},
// 每页条数
pageSize: {
type: Number,
default: 60,
},
// 每页条数选项
pageSizes: {
type: Array,
default: () => [60, 100, 150, 200, 250, 500],
},
// 总条数
total: {
type: Number,
default: 0,
},
// 分页布局
paginationLayout: {
type: String,
default: "total, sizes,prev, pager, next,jumper",
},
// 是否显示多选框
showSelection: {
type: Boolean,
default: false,
},
// 是否显示序号
showIndex: {
type: Boolean,
default: false,
},
// 序号列标题
indexLabel: {
type: String,
default: "序号",
},
// 序号列宽度
indexWidth: {
type: [String, Number],
default: 60,
},
// 多选时的可选择函数
selectable: {
type: Function,
default: null,
},
// 序号计算方法
indexMethod: {
type: Function,
default: null,
},
},
data() {
return {
selectedRows: [], // 存储选中的行数据
};
},
computed: {
// 过滤可见的列
visibleColumns() {
return this.columns.filter(column => {
// 如果没有visible属性,默认显示
if (column.visible === undefined || column.visible === null) {
return true;
}
// 如果visible是函数,需要检查是否所有行都不可见
if (typeof column.visible === "function") {
// 如果表格没有数据,按组件上下文判断
if (!this.data || this.data.length === 0) {
// 尝试调用函数,如果函数需要row参数则返回true(显示列)
try {
return column.visible.call(this.$parent || this);
} catch (e) {
return true; // 如果出错,默认显示
}
}
// 如果有数据,检查是否至少有一行满足显示条件
return this.data.some((row, index) => {
try {
const cellValue = row[column.prop];
return column.visible.call(this.$parent || this, row, column, cellValue, index);
} catch (e) {
// 如果函数不接受这些参数,尝试只传this上下文
try {
return column.visible.call(this.$parent || this);
} catch (e2) {
return true; // 默认显示
}
}
});
}
// 如果visible是布尔值,直接返回
if (typeof column.visible === "boolean") {
return column.visible;
}
// 默认显示
return true;
});
},
// 计算序号的方法
getIndexMethod() {
if (this.indexMethod) {
return this.indexMethod;
}
// 默认序号计算方法(支持分页)
return (index) => {
return (this.currentPage - 1) * this.pageSize + index + 1;
};
},
},
methods: {
// 获取单元格的可见性
getCellVisible(column, row, tableColumn, index) {
// 如果没有visible属性,默认显示
if (column.visible === undefined || column.visible === null) {
return true;
}
// 如果visible是函数,调用函数判断
if (typeof column.visible === "function") {
const cellValue = row[column.prop];
try {
return column.visible.call(this.$parent || this, row, column, cellValue, index);
} catch (e) {
// 如果函数不接受这些参数,尝试只传this上下文
try {
return column.visible.call(this.$parent || this);
} catch (e2) {
return true; // 默认显示
}
}
}
// 如果visible是布尔值,直接返回
if (typeof column.visible === "boolean") {
return column.visible;
}
// 默认显示
return true;
},
handleSizeChange(val) {
this.$emit("size-change", val);
},
handleCurrentChange(val) {
this.$emit("current-change", val);
},
// 处理选择变化
handleSelectionChange(selection) {
this.selectedRows = selection;
this.$emit("selection-change", selection);
},
// 切换某一行的选中状态
toggleRowSelection(row, selected) {
this.$refs.table.toggleRowSelection(row, selected);
},
// 切换全选
toggleAllSelection() {
this.$refs.table.toggleAllSelection();
},
// 获取选中的行
getSelectedRows() {
return this.selectedRows;
},
// 设置某一行为选中状态
setCurrentRow(row) {
this.$refs.table.setCurrentRow(row);
},
},
};
</script>
<style scoped lang="scss">
.dynamic-table {
width: 100%;
}
.table-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-top: 1px solid #ebeef5;
margin-top: 8px;
}
.selection-info {
display: flex;
align-items: center;
gap: 12px;
color: #606266;
font-size: 14px;
}
.pagination-wrapper {
margin-top: 20px;
text-align: right;
}
</style>
更多推荐


所有评论(0)