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>
        );
    }
}

常见问题排查

  1. 编译错误: 确保安装了正确的 babel 预设和插件
  2. 语法高亮: 如果使用 VSCode,安装 “Vetur” 或 “Vue Language Features (Volar)” 插件
  3. 热重载: JSX 更改后可能需要手动刷新页面
  4. 属性命名: 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 组合使用

结合 visiblerender 函数实现复杂的列控制:

<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;
}

📝 注意事项

  1. 性能优化: 当数据量较大时,建议使用虚拟滚动或分页
  2. 行键设置: 使用多选功能时,务必设置正确的 row-key
  3. 渲染函数: render 函数中的 this 指向父组件,可以调用父组件方法
  4. 事件处理: 所有 Element UI Table 的原生事件都会透传
  5. 响应式: 表格会自动适应容器宽度,建议设置合适的 minWidth
  6. JSX 语法配置:
    • 使用 JSX 前必须完成 Vue2 JSX 配置
    • JSX 中使用驼峰命名属性(如 onClick 而不是 on-click
    • 事件处理使用箭头函数语法:onClick={() => this.handleClick()}
    • 样式对象使用驼峰命名:style={{ marginRight: '8px' }}
  7. visible 函数增强:
    • 支持 (row, column, cellValue, index) 四个参数
    • 可以根据行数据、列配置、单元格值和行索引动态控制显示
    • 函数内可以通过 this 访问组件实例的数据和方法
    • 自动处理列级别和单元格级别的显示控制
  8. 列控制最佳实践:
    • 优先使用组件状态控制列显示,性能更好
    • 需要基于行数据控制时,考虑数据量对性能的影响
    • 复杂的显示逻辑建议缓存到计算属性中
  9. 错误处理: 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>

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐