news 2026/4/16 8:13:56

Vue2 + Element UI 实战:Table 组件封装与 CRUD 钩子设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue2 + Element UI 实战:Table 组件封装与 CRUD 钩子设计

前言 👋

在前一篇文章中,我们封装了SearchForm组件,解决了列表页的头部搜索问题。

今天,我们要攻克中后台最核心、最高频出现的组件——Table(表格)

你是否厌倦了在每个.vue文件中重复写这样的代码?

  • el-table/el-table-column

  • loading状态

  • 分页组件el-pagination

  • 新增/编辑/删除/查看的逻辑

本文将带你封装一个企业级 Table 组件,并引入CRUD Hooks​ 的设计思想,让你的列表页代码量减少80%!🚀


一、设计目标与最终效果 🎯

我们要实现什么?

  1. <ProTable>组件:集成了表格、分页、loading、空状态。

  2. Column 配置化:像SearchForm一样,通过配置生成列。

  3. CRUD Hooks:通过useCrud函数,一键生成列表/新增/编辑/删除逻辑。

使用前 vs 使用后

使用前(传统写法):

<template> <div> <el-table :data="list" v-loading="loading"> <el-table-column prop="name" label="姓名"></el-table-column> <!-- 一堆列... --> </el-table> <el-pagination background layout="total, sizes, prev, pager, next" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> </template> <script> export default { data() { /* 大量样板代码 */ }, methods: { /* 增删改查方法 */ } } </script>

使用后(我们的方案):

<template> <ProTable ref="tableRef" :columns="columns" :request="fetchTableData" :search-form="queryParams" > <!-- 操作列插槽 --> <template #operation="{ row }"> <el-button @click="crud.edit(row)">编辑</el-button> <el-button @click="crud.del(row.id)">删除</el-button> </template> </ProTable> </template> <script> import { useCrud } from '@/hooks/useCrud' export default { setup() { const { crud, fetchTableData } = useCrud('/api/user') return { crud, fetchTableData } } } </script>

(注:Vue2 中 setup 需借助@vue/composition-api,但我们先用 Options API 实现核心逻辑)


二、核心实现:ProTable 组件 🛠️

我们先来实现ProTable.vue

1. 组件结构 (src/components/ProTable/index.vue)

<template> <div class="pro-table"> <!-- 表格主体 --> <el-table ref="elTableRef" v-loading="loading" :data="tableData" v-bind="$attrs" height="100%" > <!-- 序号列 --> <el-table-column v-if="showIndex" type="index" label="序号" width="60" align="center" /> <!-- 动态列 --> <template v-for="(col, index) in columns"> <!-- 普通列 --> <el-table-column v-if="!col.children && col.prop !== 'operation'" :key="index" v-bind="col" > <!-- 自定义列的插槽 --> <template slot-scope="{ row, $index }"> <slot v-if="col.slot" :name="col.prop" :row="row" :index="$index" /> <span v-else>{{ row[col.prop] }}</span> </template> </el-table-column> <!-- 操作列 --> <el-table-column v-if="col.prop === 'operation'" :key="index" v-bind="col" > <template slot-scope="{ row, $index }"> <slot name="operation" :row="row" :index="$index" /> </template> </el-table-column> </template> </el-table> <!-- 分页 --> <div class="pagination-container" v-if="showPagination"> <el-pagination background layout="total, sizes, prev, pager, next, jumper" :page-sizes="[10, 20, 50, 100]" :total="total" :page-size.sync="pagination.pageSize" :current-page.sync="pagination.pageNum" @size-change="handleSizeChange" @current-change="handlePageChange" /> </div> </div> </template>

2. JS 逻辑

<script> export default { name: 'ProTable', props: { columns: { type: Array, required: true }, request: { type: Function, required: true }, // 请求API的函数 searchForm: { type: Object, default: () => ({}) }, showIndex: { type: Boolean, default: true }, showPagination: { type: Boolean, default: true } }, data() { return { loading: false, tableData: [], total: 0, pagination: { pageNum: 1, pageSize: 10 } } }, watch: { // 监听搜索条件变化,自动刷新表格 searchForm: { deep: true, handler() { this.pagination.pageNum = 1 // 搜索时重置页码 this.refresh() } } }, mounted() { this.refresh() }, methods: { // 刷新表格 async refresh() { this.loading = true try { const params = { ...this.pagination, ...this.searchForm } const res = await this.request(params) this.tableData = res.list || [] this.total = res.total || 0 } catch (error) { console.error(error) } finally { this.loading = false } }, handleSizeChange(size) { this.pagination.pageSize = size this.refresh() }, handlePageChange(page) { this.pagination.pageNum = page this.refresh() } } } </script>

三、CRUD Hooks 设计 (useCrud.js) 🎣

虽然 Vue2 原生不支持 Composition API,但我们可以模拟一个Hooks 工厂函数,将 CRUD 逻辑抽离。

1. 创建 Hook (src/hooks/useCrud.js)

import { MessageBox } from 'element-ui' import request from '@/utils/request' export function useCrud(apiPrefix) { const state = { dialogVisible: false, dialogTitle: '', form: {}, rules: {}, isEdit: false } const methods = { // 打开新增弹窗 openAddDialog(defaultForm = {}) { state.dialogTitle = '新增' state.isEdit = false state.form = { ...defaultForm } state.dialogVisible = true }, // 打开编辑弹窗 openEditDialog(row) { state.dialogTitle = '编辑' state.isEdit = true state.form = { ...row } // 深拷贝视情况而定 state.dialogVisible = true }, // 提交表单 async submit(api) { try { const res = await api(state.form) if (res.code === 20000) { this.$message.success(`${state.dialogTitle}成功`) state.dialogVisible = false // 通知表格刷新 this.$refs.tableRef?.refresh() } } catch (e) { console.error(e) } }, // 删除 async del(id) { try { await MessageBox.confirm('此操作将永久删除该数据, 是否继续?', '提示', { type: 'warning' }) const res = await request({ url: `${apiPrefix}/delete/${id}`, method: 'post' }) if (res.code === 20000) { this.$message.success('删除成功') this.$refs.tableRef?.refresh() } } catch (e) { if (e !== 'cancel') console.error(e) } } } return { crudState: state, crudMethods: methods } }

四、页面集成与使用 🧩

现在,我们把SearchForm,ProTable,useCrud组合起来。

1. 页面视图 (views/user/index.vue)

<template> <div class="page-container"> <!-- 搜索区 --> <SearchForm :config="searchConfig" :model.sync="queryParams" @search="handleSearch"> <el-button type="primary" @click="crud.openAddDialog()">新增</el-button> </SearchForm> <!-- 表格区 --> <ProTable ref="tableRef" :columns="tableColumns" :request="fetchTableData" :search-form="queryParams" > <template #status="{ row }"> <el-tag :type="row.status === 1 ? 'success' : 'danger'"> {{ row.status === 1 ? '启用' : '禁用' }} </el-tag> </template> <template #operation="{ row }"> <el-button type="text" @click="crud.openEditDialog(row)">编辑</el-button> <el-button type="text" class="danger-text" @click="crud.del(row.id)">删除</el-button> </template> </ProTable> <!-- 新增/编辑弹窗 (示例) --> <el-dialog :title="crudState.dialogTitle" :visible.sync="crudState.dialogVisible"> <el-form :model="crudState.form" label-width="80px"> <el-form-item label="用户名"> <el-input v-model="crudState.form.username"></el-input> </el-form-item> </el-form> <span slot="footer"> <el-button @click="crudState.dialogVisible = false">取消</el-button> <el-button type="primary" @click="crud.submit(submitApi)">确定</el-button> </span> </el-dialog> </div> </template>

2. JS 逻辑

<script> import { useCrud } from '@/hooks/useCrud' import SearchForm from '@/components/SearchForm' import ProTable from '@/components/ProTable' export default { components: { SearchForm, ProTable }, data() { return { queryParams: { username: '' }, searchConfig: [{ label: '用户名', prop: 'username', type: 'input' }], tableColumns: [ { prop: 'username', label: '用户名' }, { prop: 'email', label: '邮箱' }, { prop: 'status', label: '状态', slot: true }, { prop: 'operation', label: '操作', fixed: 'right', width: 150 } ] } }, created() { const { crudState, crudMethods } = useCrud('/api/user') this.crudState = crudState this.crud = crudMethods.bind(this) // 绑定 this }, methods: { fetchTableData(params) { return request({ url: '/api/user/list', params }) }, submitApi(form) { const api = this.crudState.isEdit ? '/api/user/update' : '/api/user/add' return request({ url: api, method: 'post', data: form }) } } } </script>

五、总结与思考 💡

至此,我们已经搭建了一个非常强大的中后台开发模式:

  1. SearchForm:负责输入。

  2. ProTable:负责展示与分页。

  3. useCrud Hook:负责业务逻辑(增删改查)。

这种模式的优势:

  • 极高复用性:所有列表页几乎一模一样。

  • 关注点分离:页面只关心配置和数据流,不关心具体实现。

  • 易于维护:修改表格逻辑只需改ProTable,不用逐个页面查找。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 8:12:21

DeerFlow进阶教程:集成MCP服务,扩展你的AI助理工具箱

DeerFlow进阶教程&#xff1a;集成MCP服务&#xff0c;扩展你的AI助理工具箱 认识一下DeerFlow&#xff0c;你的个人深度研究助理。它已经内置了强大的网络搜索、Python代码执行和报告生成能力。但今天&#xff0c;我们要让它变得更强大——通过集成MCP&#xff08;Model Cont…

作者头像 李华
网站建设 2026/4/16 8:10:19

Qwen3.5-9B助力Visual Studio开发:C++项目调试与智能辅助

Qwen3.5-9B助力Visual Studio开发&#xff1a;C项目调试与智能辅助 1. 当C开发遇上AI助手 如果你是一名C开发者&#xff0c;一定经历过这样的痛苦时刻&#xff1a;面对满屏的编译错误不知所措&#xff0c;花几个小时追踪一个内存泄漏问题&#xff0c;或者在多线程调试中迷失方…

作者头像 李华
网站建设 2026/4/16 8:06:11

c语言指针盲区速记

1.int *p; 中的 * 是类型修饰符 → p 是指针变量2.*p 12; 中的 * 是解引用运算符 → 向 p 指向的位置写值3.int *p;*p 12; 不能这样做&#xff0c;指针&#xff08;无论全局还是局部&#xff09;必须初始化&#xff01;&#xff01;&#xff01;3.1 初始化解决方案1——指向已…

作者头像 李华
网站建设 2026/4/16 8:03:21

Pixel Script Temple 计算机视觉入门:OpenCV基础操作代码自动生成

Pixel Script Temple 计算机视觉入门&#xff1a;OpenCV基础操作代码自动生成 1. 为什么需要代码自动生成工具 刚接触计算机视觉的朋友们可能都有这样的经历&#xff1a;想实现一个简单的图像处理功能&#xff0c;却要花大量时间查阅OpenCV文档&#xff0c;反复调试参数。Pix…

作者头像 李华