☰
vxe-table可编辑表格实战:增删改查与必填校验全解析
2026/10/1 4:25:56 网站建设 项目流程

搞后台管理系统,最磨人的就是表格那一摊子事。尤其是那种"用户直接在格子里面改数据"的需求,你以为是省事了,结果做起来全是细节。vxe-table这个组件库我从两个项目之前开始用,一开始也就是图它快,后来越用越觉得它在可编辑表格这块儿是下了功夫的。今天这篇就把我用vxe-table做可编辑表格、实现增删改查和必填校验的完整过程拆开聊,从设计思路到踩坑点,尽量说透。

这篇文章适合谁看?主要是给那些用Vue做中后台项目、正在纠结"要不要自己用input + table拼一个可编辑表格"的同仁。同时也适合已经摸过vxe-table但卡在校验、增删行这些细节上的人。我会先把核心思路讲清楚,再给完整步骤和代码,最后把我在真实项目里踩过的坑和优化经验列出来,你直接照着抄能少走不少弯路。

1. 动手之前,先想清楚需求和方案

1.1 为什么选vxe-table,而不是自己拼表格

很多朋友一上来就选择用el-table或者原生table再加一堆input去拼,说实话,数据少的时候还能忍,等数据量上来,或者校验规则复杂起来,你会有一种"所有状态都得自己管"的无力感。你要维护哪些单元格处于编辑态、哪些校验没通过、哪些数据变更过、保存时要收集哪些数据——这些东西叠加在一起,代码很快就失控了。

vxe-table的好处在于,它在组件层面就把"可编辑"这个能力做成了原生功能。你不用自己监听每个input的change事件,不用手动给每个单元格加编辑状态,也不需要一个个绑校验字段。它有一种edit-config的配置方式,配合列上的edit-render和edit-rules,能直接把整个表格变成一个"表单"。这意味着适配器、状态管理、校验体系,它全给你内置好了。

1.2 这个项目的最小闭环是什么

这个项目标题虽然就几个字,但拆开看其实是四个相互独立又彼此关联的能力:

  • 增:新增一行或者多行,数据从空状态变成可编辑状态。
  • 删:删除选中的行,同时要和后端数据保持同步。
  • 改:直接在单元格里改内容,相当于替代了传统的表单编辑页面。
  • 查:查询后端数据并回显到表格,且保证数据格式和列配置匹配。
  • 必填校验:比如"姓名""手机号""金额"这些字段不能空着,提交时统一校验。

这里有个很多人容易漏掉的点:这四项操作不是各做各的,而是共用一个表格实例和数据源。你新增一行,表格要进入可编辑状态;你点击保存,要给整张表做校验;校验不过,要能准确定位到具体哪个格子。所以方案设计的第一原则就是:数据和状态要收敛在组件内部,而不是散落在一堆ref和watcher里面。

1.3 功能结构拆解

我用一张思维导图式的结构来规划(虽然没有图,但逻辑是这么理的):

  • 表格配置层:列配置(columns)、编辑配置(editConfig)、校验规则(editRules)
  • 数据层:tableData(前端数据源)
  • 操作层:新增、删除、保存、查询这些方法
  • 反馈层:校验不过的提示、删除确认、操作结果的toast

这个结构界定清楚之后,后面写代码就不用东一榔头西一棒子了。

2. 从零搭一个能用的vxe-table基础表格

2.1 安装与引入

先说我用的环境:Vue 2 + vxe-table 3.x,这个组合比较成熟,文档也最全。如果你用Vue 3,那对应的是vxe-table 4.x,API风格差异不大,但个别配置名有变化,我文中的写法以3.x为主。

npm install vxe-table@3

在main.js里全局引入:

import Vue from 'vue' import VXETable from 'vxe-table' import 'vxe-table/lib/style.css' Vue.use(VXETable)

这里有个小建议:如果你只是用表格,其实可以不用全量引入,按需引入更省打包体积。但项目里如果表格用得多,直接全局引入反而省心,因为按需引入要用到babel插件,配置起来也是一个成本。我一般是在工具类和组件库之间做一个平衡:UI组件按需,表格这种重度使用的就全局引。

2.2 列配置和数据源绑定

vxe-table的核心是columns配置。我把数据定义成响应式的:

export default { data() { return { tableData: [], columns: [ { type: 'checkbox', width: 50 }, { type: 'seq', title: '序号', width: 60 }, { field: 'name', title: '姓名', minWidth: 140 }, { field: 'mobile', title: '手机号', minWidth: 160 }, { field: 'age', title: '年龄', minWidth: 100 }, { field: 'amount', title: '金额', minWidth: 140 }, { field: 'remark', title: '备注', minWidth: 200 } ] } } }

然后模板:

<vxe-table ref="xTable" :data="tableData" :columns="columns" border show-overflow :edit-config="{ trigger: 'click', mode: 'cell', showStatus: true }" > <template #edit_name="{ row }"> <vxe-input v-model="row.name" /> </template> </vxe-table>

先解释一下edit-config这几个参数:

  • trigger:触发编辑的方式,click是点击单元格进入编辑,dblclick是双击。大部分业务场景用click更顺手。
  • mode:cell表示按单元格维度编辑,还有row是整行编辑。按单元格编辑更灵活,但校验粒度也更细。
  • showStatus:开启之后,变更过的单元格左侧会有一个小标记,这个在视觉上非常有用,用户能一眼看出来这一行被改过。

2.3 别忽略列和模板的对应关系

在vxe-table里,如果你要让某个列可编辑,必须在columns里也配好对应的render,或者在模板里写同名slot。我踩过的一个坑是:columns里配了field: 'name',但模板里忘了写#edit_name,结果点击单元格进去只能看到纯文本,根本没法输入。这俩必须配套,缺一个,vxe-table就退化成只读表格。

如果你不想在模板里写太多slot,也可以直接在columns里用editRender的方式:

{ field: 'name', title: '姓名', minWidth: 140, editRender: { name: 'input' } }

这种方式更简洁,适合输入框这种单一控件。但如果校验或联动逻辑复杂,还是在模板里写slot更灵活、可读性更高。

3. 必填校验:别让用户蒙着头提交

3.1 edit-rules怎么配

校验是编辑表格最有价值的点。vxe-table提供了specific-config校验方案,你只需要在columns里配上editRules,或者在表格级别用edit-config里的rules统一配。

我习惯把规则直接写在columns的每一项上,这样每个字段的校验职责是内聚的:

columns: [ { field: 'name', title: '姓名', minWidth: 140, editRender: { name: 'input' }, editRules: [ { required: true, message: '姓名不能为空' }, { min: 2, max: 10, message: '姓名长度在2到10个字符之间' } ] }, { field: 'mobile', title: '手机号', minWidth: 160, editRender: { name: 'input' }, editRules: [ { required: true, message: '手机号不能为空' }, { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' } ] } ]

这里有个直观的体会:校验规则写在columns里,等于你把"这张表长什么样"和"这张表怎么才能合法"放在了一起。后面改需求时,只要找到那列,改起来非常顺手。

3.2 手动触发整表校验

光配好规则还不够,提交时必须手动校验。vxe-table的实例方法validate()会遍历当前所有处于编辑模式的字段,并返回一个Promise:

async handleSave() { const $table = this.$refs.xTable const errMap = await $table.validate().catch(err => err) if (errMap) { this.$XModal.message({ message: '请先修改表单中标记的错误项', status: 'error' }) return } // 校验通过,走保存逻辑 await saveApi() }

这里validate()返回的如果是一个errMap对象,说明有校验失败项;如果是true或undefined,说明通过了。不同版本返回值可能不太一样,我建议你在项目里先打印一下看看再写后续逻辑,别想当然。

3.3 校验时机:什么时候该触发

很多初学者只在点保存的时候才校验,导致用户体验很差——用户填了半天,最后保存时才被告知一堆错误。我一般会做两个层面的校验:

  • 失焦校验:每个单元格编辑完成、光标离开时,触发单行或单列校验。vxe-table支持在edit-closed事件里去校验当前行。
  • 提交校验:保存时全表校验。

单行校验的写法:

handleEditClosed({ row }) { this.$refs.xTable.validateRow(row).then(() => { // 当前行校验通过 }).catch(() => { // 当前行校验失败,不用处理,错误样式会自动标红 }) }

综合体验下来,失焦校验能即时反馈,提交校验兜底,这套组合是最舒服的。

3.4 自定义校验的扩展技巧

内置的required、pattern、min/max其实已经覆盖了大部分场景。但业务里总有"恶心"需求,比如"金额必须大于0且小于100000"、"备注不能超过50字且不能带特殊字符"。这时候用validator自定义校验函数:

{ field: 'amount', title: '金额', editRules: [ { required: true, message: '金额不能为空' }, { validator: ({ cellValue }) => { if (Number(cellValue) <= 0 || Number(cellValue) > 100000) { return new Error('金额需在0到100000之间') } return true } } ] }

这个validator接收一个对象,你可以拿到cellValue、row、column等上下文,非常灵活。注意返回值:合法返回true,不合法返回new Error('提示信息'),不要返回字符串,因为vxe-table内部会自己组装错误对象。

4. 增删改查的实现细节

4.1 新增行:别只是push一条空数据

新增行最容易犯的错是只往数组里push一条空对象,然后发现表格能编辑,但保存时所有字段都是undefined。我建议在新增时就为行数据塞好默认值,并给一个临时的唯一标识,方便后面删除和保存操作对应到具体行:

handleAddRow() { const newRow = { id: Date.now(), // 临时id,方便前端区分 name: '', mobile: '', age: null, amount: null, remark: '', _isNew: true // 标记是新增的行,提交时区分新增还是修改 } this.tableData.push(newRow) // 让新增的行滚动到可视区,并且第一个可输入单元格处于编辑状态 this.$nextTick(() => { this.$refs.xTable.setCurrentRow(newRow) }) }

这种默认值的处理,后面保存接口要区分"新增"和"修改"时就只需要看_isNew标记了,非常省事。

4.2 删除行:确认弹窗是底线

删除操作建议加确认弹窗。我在前一个项目里就遇到过用户手滑点错行,又没有确认步骤,一下子删掉一整条业务数据的情况。vxe-table有配套的VXETable.modal,可以这么写:

handleDeleteRows() { const $table = this.$refs.xTable const selectRecords = $table.getCheckboxRecords() if (!selectRecords.length) { this.$XModal.message({ message: '请先勾选要删除的数据', status: 'info' }) return } this.$XModal.confirm(`确定删除选中的 ${selectRecords.length} 条数据吗?`).then(() => { // 前端先删,提交时再批量删除 selectRecords.forEach(row => { const idx = this.tableData.indexOf(row) if (idx > -1) { this.tableData.splice(idx, 1) } }) }).catch(() => {}) }

有一点要特别提醒:如果表格数据是分页的,删除最后一行后当前页可能为空,这时候应该跳回上一页或者刷新列表。这个细节很多人会漏,我后面也专门踩过这个坑。

4.3 修改与保存:提交前做一次数据快照

保存是整个增删改查的核心出口。你不能只把tableData原样丢给后端,毕竟表格里可能混着新增的行、删除的行、修改的行。我的习惯是把操作分类,差异化提交:

async handleSave() { const $table = this.$refs.xTable const errMap = await $table.validate().catch(err => err) if (errMap) { return } const newList = [] const updateList = [] const deleteList = [] this.tableData.forEach(row => { if (row._isNew) { newList.push(this.formatRow(row)) } else { updateList.push(this.formatRow(row)) } }) // 这里deleteList是之前收集的待删除行 const params = { newList, updateList, deleteList } await saveApi(params) this.$XModal.message({ message: '保存成功', status: 'success' }) this.refreshTable() }

这里值得注意的一个细节是formatRow——很多字段在表格里是字符串,但后端要的是数字,比如年龄、金额。提交前统一做一次类型转换,能避免后端接口报"参数类型错误"这种问题。权衡之下,在前端做一个简单的format比交给后端判断要快得多。

4.4 查询:数据回显时处理格式问题

查询的话,一般就是请求接口,拿到数据后赋值给tableData:

async handleQuery() { const params = { keyword: this.keyword, pageNo: this.pageNo, pageSize: this.pageSize } const res = await getListApi(params) this.tableData = res.data.records }

但这里有个容易忽略的点:回显的数据里,如果某些字段是null或undefined,直接显示在可编辑单元格里有时会报错或者显示很丑。比如金额是null,vxe-input在绑定时正常,但如果你用了输入框的type="number",null值会导致显示为空。我的做法是在接口返回后做一次数据归一化:

res.data.records.forEach(row => { row.amount = row.amount == null ? '' : row.amount row.age = row.age == null ? null : row.age row._isNew = false })

这一点非常实用,不然你会发现明明数据有值,编辑时却怪怪的。

5. 实战排坑与优化建议

5.1 表格数据量大时的性能优化

vxe-table本身性能已经不错,但那个是建立在合理使用的前提下。如果你在可编辑表格里放了一堆实时更新的状态,或者用很深的对象引用,照样会卡。我总结了三招:

  • 大数据量时开启虚拟滚动:在表格上配置scroll-y="{ enabled: true, gt: 200 }",当行数超过200条时启用纵向虚拟滚动,不然几千行数据直接渲染,浏览器直接掉帧。
  • 避免在模板里写复杂方法调用:比如{{ formatTime(row.date) }}这种,每一行渲染时都会重新执行,数据一多性能立刻变差。尽量在数据源里提前格式好。
  • 校验规则别太频繁触发:validator里如果做了异步接口调用,等于每次失焦都会请求一次,这很危险。异步校验要做好防抖,或者只在提交时做。

5.2 保存后刷新表格的时机

提交成功之后,如果是在弹窗里的场景,我一般直接关闭弹窗并让父组件刷新列表;如果是在页面里的场景,则重新请求当前分页数据。这里有个心得:刷新完数据后,要把tableData重新指向一个全新的数组,不要在原有数组上push或修改相同引用,否则vxe-table内部的一些状态(比如哪些行处于选中状态)不会被正确重置。

this.tableData = [...res.data.records]

而不是:

this.tableData.splice(0, this.tableData.length, ...res.data.records)

后者的写法在某些版本中会导致残留编辑状态或选中状态。

5.3 校验错误信息的位置优化

vxe-table默认的错误提示是显示在单元格下方,如果表格行高比较矮,可能显示不全。我调整过两个地方:一个是在edit-config里设置activeMethod控制某些单元格不可编辑,另一个是给表格加class-name="editable-table",然后自定义错误提示的样式,比如让提示内容往上弹出而不是往下遮挡。

.editable-table .vxe-cell--invalid { background: #fef0f0; }

这个类名可以自定义,调底色、加边框都行。用户体验上,错误提示要有,但不能太"炸",一个浅红色背景加上一行小字提示就够了。

5.4 常见报错自查表

这里列几个我遇到过的比较典型的报错和解决办法:

现象可能原因解决办法
点击单元格进入不了编辑状态没配edit-config或列上没有对应editRender/slot检查表格是否配了edit-config,列上是否配了editRender或同名slot
校验不生效editRules没写在对应列配置上,或字段名对不上确认field和editRules所在的列是同一列
保存后表格显示旧数据没有重新赋值新数组引用刷新后用this.tableData = [...res.data]整体替换
新增的行不显示编辑控件新增的行里没有对应字段属性push数据时给字段都赋好默认值
validate返回undefined但表单明明有错校验规则没生效,或者没有触发校验的字段处于可见范围检查是否有列字段没有配置editRender,有时纯文本列不参与校验

这些基本都是"配置不全"或者"字段对不上"导致的,只要你把columns、editRender、editRules这三者的字段名对齐,绝大多数问题都能解决。

5.5 交互细节打磨:让可编辑表格更像表单

表格一旦可编辑,用户对"保存"的心理预期就和普通表单一样。所以有几个交互细节值得做:

  • 保存成功后给一个明确的成功提示,并且清空选中状态。
  • 表格底部固定一行按钮栏,显示"新增""删除""保存",不需要用户自己滚动到页面顶部去找按钮。
  • 删除和保存在高风险操作时加loading,防止用户连点多次提交。
  • 可编辑列的表头加一个视觉标记,比如星号或"必填"小标签,这样用户一进页面就知道哪些字段不能空。vxe-table的表头可以直接用slots自定义,我通常会加一个红色小星号。

这些细节不复杂,但做了之后,测试和用户都会觉得"这表格确实做完整了",而不是"能用但很糙"。

根据我自己做过的几个中后台项目的经验,vxe-table的可编辑表格在功能层面已经能覆盖掉80%的"表格里直接改数据"的场景,剩下的20%基本要靠对业务的理解和交互细节去补。如果你第一次接触它,我建议不要上来就追求把所有能力一次性用完,先把这个最小闭环跑通:基础表格 -> 列配置 -> 可编辑单元格 -> 必填校验 -> 保存提交。跑通之后再慢慢加复杂特性,比如动态列、行拖拽、跨行合并这些,你会发现vxe-table的能力其实远超你的预期。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询