VXE-Table 4.x 实战规范:列配置 + 合并单元格 + 虚拟滚动,避坑卡顿 / 错乱 / 合并失效|表单与表格规范篇

【VXE-Table】+【中后台大数据表格】:从列配置、单元格合并到虚拟滚动,一站式掌握实战配置规范,避开卡顿、错乱、合并失效高频坑!
本文基于 VXE-Table 4.x 编写,部分 API 在不同小版本中可能有差异,请以项目实际使用的版本文档为准。
VXE-Table 官方文档

📑 文章目录

  • [一、开篇:为什么要单独讲 VXE-Table?](#一、开篇:为什么要单独讲 VXE-Table?)
  • [二、列配置(Column Config):日常怎么选、怎么配](#二、列配置(Column Config):日常怎么选、怎么配)
    • [2.1 列配置的两种写法](#2.1 列配置的两种写法)
    • [2.2 核心字段速查表](#2.2 核心字段速查表)
    • [2.3 固定列(fixed)规范与坑](#2.3 固定列(fixed)规范与坑)
    • [2.4 columnConfig:表格级别的列行为](#2.4 columnConfig:表格级别的列行为)
  • [三、合并单元格:mergeCells 与 spanMethod 怎么选](#三、合并单元格:mergeCells 与 spanMethod 怎么选)
    • [3.1 两种方式对比(必读)](#3.1 两种方式对比(必读))
    • [3.2 mergeCells 规范写法](#3.2 mergeCells 规范写法)
    • [3.3 spanMethod 写法(适用于动态合并逻辑)](#3.3 spanMethod 写法(适用于动态合并逻辑))
    • [3.4 合并单元格的常见坑](#3.4 合并单元格的常见坑)
  • 四、虚拟滚动:什么时候开、怎么开、有哪些限制
    • [4.1 虚拟滚动的本质](#4.1 虚拟滚动的本质)
    • [4.2 怎么启用虚拟滚动](#4.2 怎么启用虚拟滚动)
    • [4.3 旧版 API 与新版的区别](#4.3 旧版 API 与新版的区别)
    • [4.4 虚拟滚动的限制(避坑必读)](#4.4 虚拟滚动的限制(避坑必读))
    • [4.5 完整示例:带虚拟滚动的表格](#4.5 完整示例:带虚拟滚动的表格)
  • [五、综合实战:列配置 + 合并 + 虚拟滚动](#五、综合实战:列配置 + 合并 + 虚拟滚动)
  • 六、避坑速查表
  • 七、小结
  • [🔍 系列模块导航](#🔍 系列模块导航)

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。


一、开篇:为什么要单独讲 VXE-Table?

业务里但凡遇到「数据量大、列多、要合并、要虚拟滚动」的表格,Element Plus 的 el-table 常常不够用。VXE-Table 是专门为此设计的 Vue 表格方案,但 API 多、约束也多,一旦配置不当就会卡顿、空白、合并失效。

本文聚焦三个核心能力:列配置合并单元格虚拟滚动,按「怎么配 → 为什么这么配 → 常见坑」的结构讲清楚,方便你直接照着写、照着避坑。

[⬆ 返回目录](#⬆ 返回目录)


二、列配置(Column Config):日常怎么选、怎么配

2.1 列配置的两种写法

VXE-Table 支持两种列定义方式:

方式 适用场景 优点 缺点
模板写法 <vxe-column> 列固定、少量、交互简单 直观、好读 动态列要写很多 v-if
配置写法 columns 后端返列、动态显隐、列很多 灵活、易维护 要理解字段含义

规范建议

  • 列基本固定的 → 用模板;
  • 列由后端配置或需要列设置、导出配置的 → 用 columns 配置。

[⬆ 返回目录](#⬆ 返回目录)

2.2 核心字段速查表

js 复制代码
// columns 数组里每列常见字段
const columns = [
  {
    type: 'seq',        // 列类型:seq序号 | checkbox | radio | expand | html
    field: 'id',        // 字段名,对应 data 里的属性(越深性能越差,避免 a.b.c.d)
    title: 'ID',        // 列标题
    width: 80,          // 列宽,数字或 'auto'
    minWidth: 60,       // 最小宽度
    resizable: true,    // 是否可拖拽调整列宽
    visible: true,      // 是否显示(配合列设置)
    fixed: 'left',      // 固定:'left' | 'right' | ''(空字符串表示不固定)
    align: 'center',    // 对齐:'left' | 'center' | 'right'
    sortable: true,     // 是否可排序
    filters: [...],     // 筛选配置
    formatter: ({ cellValue }) => cellValue,  // 格式化显示
    slots: { default: 'customSlot' },        // 插槽名
  }
]

说明

  • field 越浅越好,a.b.ca 更耗性能。
  • fixed 必须是字符串 'left''right''',不能是 true/false(后面会讲坑)。

[⬆ 返回目录](#⬆ 返回目录)

2.3 固定列(fixed)规范与坑

正确写法:

html 复制代码
<template>
  <vxe-table :columns="columns" :data="tableData" />
</template>

<script setup>
const columns = [
  { field: 'name', title: '姓名', width: 120, fixed: 'left' },
  { field: 'age', title: '年龄', width: 80 },
  { field: 'address', title: '地址', width: 200 },
  { field: 'action', title: '操作', width: 150, fixed: 'right' }
]
</script>

常见坑 1:后端返回 true/false 导致样式错乱

js 复制代码
// ❌ 错误:后端返回 boolean
const columns = res.data.columns.map(col => ({
  ...col,
  fixed: col.fixed  // 可能是 true,vxe-table 期望 'left'
}))

// ✅ 正确:统一转成字符串
const columns = res.data.columns.map(col => ({
  ...col,
  fixed: col.fixed === true ? 'left' : (col.fixed === 'right' ? 'right' : '')
}))

常见坑 2:分组表头时 fixed 要设在 colgroup 上

html 复制代码
<!-- ❌ 错误:在 column 上设 fixed,分组表头会错乱 -->
<vxe-colgroup title="基本信息">
  <vxe-column field="name" title="姓名" fixed="left" />
  <vxe-column field="age" title="年龄" fixed="left" />
</vxe-colgroup>

<!-- ✅ 正确:fixed 写在 colgroup 上 -->
<vxe-colgroup title="基本信息" fixed="left">
  <vxe-column field="name" title="姓名" />
  <vxe-column field="age" title="年龄" />
</vxe-colgroup>

规范结论

  • 固定列一定要用 'left' / 'right' 字符串;
  • 有分组表头时,fixed 配置在 vxe-colgroup 上。

[⬆ 返回目录](#⬆ 返回目录)

2.4 columnConfig:表格级别的列行为

html 复制代码
<vxe-table
  :column-config="{ resizable: true }"
  :data="tableData"
>

常用配置:

属性 类型 说明
resizable Boolean 是否允许拖拽调整列宽
useKey Boolean 是否用 field 作为列唯一 key,动态列时建议开

[⬆ 返回目录](#⬆ 返回目录)


三、合并单元格:mergeCells 与 spanMethod 怎么选

3.1 两种方式对比(必读)

对比项 mergeCells spanMethod
用法 配置数组,指定行号、列号、合并范围 函数,根据行列返回合并信息
虚拟滚动 ✅ 支持(官方做了适配) ❌ 跨行合并时纵向虚拟滚动不可用
大数据量 ✅ 适合 ❌ 容易卡顿
动态合并 需在数据变更后重新赋值 每次渲染都会执行函数
适用场景 固定规则、可计算的合并 复杂、高度依赖行列数据的合并

规范建议

  • 能用 mergeCells 的,优先用 mergeCells
  • 只有「必须根据单元格内容动态决定合并」时,才用 spanMethod,并接受虚拟滚动受限。

[⬆ 返回目录](#⬆ 返回目录)

3.2 mergeCells 规范写法

基本结构:

js 复制代码
// 每个合并项:从 (row, col) 开始,合并 rowspan 行、colspan 列
mergeCells: [
  { row: 0, col: 1, rowspan: 3, colspan: 1 },  // 第 0 行第 1 列,向下合并 3 行
  { row: 2, col: 2, rowspan: 1, colspan: 2 }   // 第 2 行第 2 列,向右合并 2 列
]

完整示例:按部门合并「部门」列

html 复制代码
<template>
  <vxe-table
    ref="tableRef"
    :columns="columns"
    :data="tableData"
    :merge-cells="mergeCells"
  />
</template>

<script setup>
import { ref, computed } from 'vue'

const tableData = ref([
  { dept: '研发部', name: '张三', role: '前端' },
  { dept: '研发部', name: '李四', role: '后端' },
  { dept: '研发部', name: '王五', role: '测试' },
  { dept: '销售部', name: '赵六', role: '销售' },
  { dept: '销售部', name: '钱七', role: '销售' },
])

const columns = [
  { field: 'dept', title: '部门', width: 120 },
  { field: 'name', title: '姓名', width: 100 },
  { field: 'role', title: '角色', width: 100 },
]

// 根据数据计算合并:同一部门连续多行时,合并部门列
const mergeCells = computed(() => {
  const list = tableData.value
  const result = []
  let startRow = 0

  for (let i = 1; i <= list.length; i++) {
    const needMerge = i < list.length && list[i].dept === list[i - 1].dept
    if (!needMerge) {
      const count = i - startRow
      if (count > 1) {
        result.push({ row: startRow, col: 0, rowspan: count, colspan: 1 })
      }
      startRow = i
    }
  }
  return result
})
</script>

注意rowcol 从 0 开始,对应数据行和 columns 的下标。

[⬆ 返回目录](#⬆ 返回目录)

3.3 spanMethod 写法(适用于动态合并逻辑)

js 复制代码
// spanMethod 返回 { rowspan, colspan } 或 false(不合并)
const spanMethod = ({ row, rowIndex, column, columnIndex }) => {
  if (column.field === 'dept') {
    // 与上一行部门相同则被合并,不单独返回
    if (rowIndex > 0 && tableData.value[rowIndex - 1].dept === row.dept) {
      return { rowspan: 0, colspan: 0 }  // 0 表示被上一行合并
    }
    const count = 计算连续相同部门数量(rowIndex)
    return { rowspan: count, colspan: 1 }
  }
  return false
}

⚠️ 重要限制

  • 使用 spanMethod 做跨行合并时,不能开启纵向虚拟滚动,否则会错乱或空白;
  • 数据量大时 spanMethod 执行频繁,易卡顿。

[⬆ 返回目录](#⬆ 返回目录)

3.4 合并单元格的常见坑

坑 1:数据更新后合并没变

mergeCells 是「快照」,数据变化后要重新计算并赋值。若用 refmergeCells,记得在 tableData 更新后重新赋值:

js 复制代码
watch(tableData, () => {
  mergeCells.value = calcMergeCells(tableData.value)
}, { deep: true })

坑 2:用 Grid 时 setMergeCells 再次赋值不生效

在 VXE-Grid 等场景,setMergeCells() 可能在 columns 更新后被重置。解决方式:在数据加载完成的回调里用 nextTick 再设一次:

js 复制代码
querySuccess: ({ response }) => {
  tableData.value = response.items
  nextTick(() => {
    const merges = calcMergeCells(response.items)
    gridApi.grid.setMergeCells(merges)
  })
}

坑 3:合并 + 虚拟滚动 + 冻结列一起用

同时使用这三个功能,容易出现滚动卡顿、固定列留白。建议:

  • 合并 + 虚拟滚动:可以,用 mergeCells
  • 合并 + 冻结列:可以,但注意表格宽度;
  • 三者同时:尽量避免,或降低数据量、减少合并范围。

[⬆ 返回目录](#⬆ 返回目录)


四、虚拟滚动:什么时候开、怎么开、有哪些限制

4.1 虚拟滚动的本质

只渲染可视区域内的行,其余行不渲染,从而在万级、十万级数据时仍能保持流畅。代价是:必须固定行高、固定表格高度,且部分高级能力不可用。

[⬆ 返回目录](#⬆ 返回目录)

4.2 怎么启用虚拟滚动

必要条件:

  1. 表格有固定高度:height="400"height="100%"(父容器有高度)
  2. 行高固定:row-config="{ height: 50 }"(虚拟滚动不支持动态行高)

基础配置示例(VXE-Table 4.x):

html 复制代码
<template>
  <vxe-table
    height="500"
    :scroll-y="{ enabled: true, gt: 20 }"
    :row-config="{ height: 50 }"
    :data="tableData"
  >
    <vxe-column type="seq" width="60" />
    <vxe-column field="name" title="姓名" width="120" />
    <vxe-column field="age" title="年龄" width="80" />
  </vxe-table>
</template>

参数说明:

属性 说明 推荐值
enabled 是否启用 true
gt 数据行数超过此值才启用虚拟滚动 50~200 即可,避免小数据也开虚拟

[⬆ 返回目录](#⬆ 返回目录)

4.3 旧版 API 与新版的区别

不同版本可能看到不同写法,对照如下:

js 复制代码
// 旧版(部分 3.x / 4.x 早期)
:optimization="{ scrollY: { gt: 200 } }"

// 新版(4.x 中后期)
:scroll-y="{ enabled: true, gt: 200 }"

以你项目中的文档为准,本文以 scroll-y 写法为主。

[⬆ 返回目录](#⬆ 返回目录)

4.4 虚拟滚动的限制(避坑必读)

限制 说明
不支持动态行高 必须用 row-config.height 固定行高
与 spanMethod 跨行合并冲突 跨行合并时不能开纵向虚拟滚动
树形、展开行 部分场景下表现异常,需实测
合并 + 虚拟 + 冻结列 三者同开易卡顿,能避免就避免

规范结论

  • 数据量 < 500:可不开虚拟滚动;
  • 500~5000:建议开,gt 设 200~500;

5000:强烈建议开,且用 mergeCells 做合并,不要用 spanMethod

[⬆ 返回目录](#⬆ 返回目录)

4.5 完整示例:带虚拟滚动的表格

html 复制代码
<template>
  <div class="table-wrap">
    <vxe-table
      ref="tableRef"
      height="500"
      :scroll-y="{ enabled: true, gt: 100 }"
      :row-config="{ height: 48 }"
      :column-config="{ resizable: true }"
      :columns="columns"
      :data="tableData"
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const columns = [
  { type: 'seq', width: 60, fixed: 'left' },
  { field: 'name', title: '姓名', width: 120, fixed: 'left' },
  { field: 'dept', title: '部门', width: 150 },
  { field: 'role', title: '角色', width: 120 },
  { field: 'address', title: '地址', minWidth: 200 },
  { field: 'action', title: '操作', width: 120, fixed: 'right' },
]

const tableData = ref([])

onMounted(async () => {
  const res = await fetch('/api/list')
  const data = await res.json()
  tableData.value = data.items
})
</script>

<style scoped>
.table-wrap {
  height: 500px;
}
</style>

[⬆ 返回目录](#⬆ 返回目录)


五、综合实战:列配置 + 合并 + 虚拟滚动

下面是一个同时用到「动态列、合并部门列、虚拟滚动」的完整示例,可直接参考。

html 复制代码
<template>
  <div class="demo-table">
    <vxe-table
      ref="tableRef"
      height="500"
      :scroll-y="{ enabled: true, gt: 50 }"
      :row-config="{ height: 48 }"
      :column-config="{ resizable: true }"
      :columns="columns"
      :data="tableData"
      :merge-cells="mergeCells"
    />
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'

// 列配置:姓名、部门左固定,操作右固定
const columns = ref([
  { type: 'seq', width: 60, fixed: 'left' },
  { field: 'name', title: '姓名', width: 100, fixed: 'left' },
  { field: 'dept', title: '部门', width: 120 },
  { field: 'role', title: '角色', width: 100 },
  { field: 'address', title: '地址', minWidth: 180 },
  { field: 'action', title: '操作', width: 120, fixed: 'right' },
])

const tableData = ref([])

// 合并部门列:同一部门连续多行合并
const mergeCells = computed(() => {
  const list = tableData.value
  const result = []
  const deptColIndex = columns.value.findIndex(c => c.field === 'dept')
  if (deptColIndex < 0) return result

  let startRow = 0
  for (let i = 1; i <= list.length; i++) {
    const needMerge = i < list.length && list[i].dept === list[i - 1].dept
    if (!needMerge) {
      const count = i - startRow
      if (count > 1) {
        result.push({
          row: startRow,
          col: deptColIndex,
          rowspan: count,
          colspan: 1,
        })
      }
      startRow = i
    }
  }
  return result
})

onMounted(async () => {
  // 模拟接口返回
  const mockData = Array.from({ length: 1000 }, (_, i) => ({
    id: i + 1,
    name: `用户${i + 1}`,
    dept: ['研发部', '销售部', '产品部'][i % 3],
    role: ['前端', '后端', '测试', '销售'][i % 4],
    address: `地址${i + 1}`,
    action: '',
  }))
  tableData.value = mockData
})
</script>

<style scoped>
.demo-table {
  padding: 16px;
  height: 500px;
}
</style>

[⬆ 返回目录](#⬆ 返回目录)


六、避坑速查表

场景 推荐做法 避免
固定列 fixed: 'left' / 'right' 字符串 使用 true/false
分组表头 + 固定列 vxe-colgroup 上配置 fixed 只在 vxe-column 上配置
合并单元格 优先 mergeCells 大数据量用 spanMethod 跨行合并
虚拟滚动 设置 heightrow-config.height 动态行高、未设高度
合并 + 虚拟 mergeCells,控制合并范围 合并 + spanMethod + 虚拟
动态 mergeCells 数据更新后重新计算并赋值,必要时 nextTick 只赋一次值不更新

[⬆ 返回目录](#⬆ 返回目录)


七、小结

  1. 列配置 :分清模板和配置写法;fixed 用字符串;分组表头时 fixed 写在 colgroup 上。
  2. 合并单元格 :能 mergeCellsmergeCellsspanMethod 只留给必须动态合并且数据量不大的场景。
  3. 虚拟滚动:必须固定高度和行高;大数据优先开,注意与合并、冻结列的兼容性。

按这些规范来配,大部分卡顿、错乱、合并失效问题都能避免。

[⬆ 返回目录](#⬆ 返回目录)

🔍 系列模块导航

📝 表单与表格规范篇

一、《Vue3 + Element Plus 表单开发实战:防重复提交、校验、重置、loading 统一|表单与表格规范篇》
二、《Vue3 + Element Plus 表单校验实战:规则复用、自定义校验、提示语统一,告别混乱避坑|表单与表格规范篇》
三、《Vue3 + Element Plus 表格查询规范:条件管理、分页联动 + 避坑,标准化写法|表单与表格规范篇》
四、《Vue3 + Element Plus 表格实战:批量操作、行内编辑、跨页选中逻辑统一|表单与表格规范篇》

五、《VXE-Table 4.x 实战规范:列配置 + 合并单元格 + 虚拟滚动,避坑卡顿 / 错乱 / 合并失效|表单与表格规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。

更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
孟祥_成都1 小时前
前端下午茶:这 3 个网页特效建议收藏(送源码)
前端·javascript·css
xushichao19891 小时前
高性能密码学库
开发语言·c++·算法
偷懒下载原神1 小时前
【linux操作系统】信号
linux·运维·服务器·开发语言·c++·git·后端
小涛不学习1 小时前
Java面试全攻略(基础 + 集合 + 并发 + JVM + 框架)
java·开发语言
m0_518019481 小时前
C++代码混淆与保护
开发语言·c++·算法
m0_569881471 小时前
C++中的智能指针详解
开发语言·c++·算法
爱丽_2 小时前
AQS 原理主线:state、CLH 队列、独占/共享与实战排查
java·开发语言·jvm
火车叼位2 小时前
Volta 下 `corepack` 失踪之谜:问题不在 Node,而在命令入口
前端
cmd2 小时前
别再用错!5种JS类型判断方法,从原理到实战一文吃透
前端·javascript