本文围绕 Vue3 表格中相同相邻行的合并实现,分别说明 Element Plus 的 el-table 与 Ant Design Vue 的 a-table 的代码写法。两者的共同前提是:先让用于判断合并的字段有序,使相同值相邻,再遍历数据计算每一行的合并跨度;合并规则不是自动识别全表相同值,而是基于相邻行比较。示例均以 name 字段作为合并依据,实际项目可替换为业务分组字段。
一、el-table 通过 span-method 合并首列
el-table 使用 span-method 属性接收方法,方法参数包含 columnIndex、rowIndex,返回 { rowspan, colspan }。示例维护 spanArr:首行 push(1) 并把 position 设为 0;后续行若 name 与前一行相同,则 spanArr[position] 加 1,并 push(0) 表示该单元格被合并;否则 push(1) 并更新 position 为当前索引。objectSpanMethod 只对 columnIndex === 0 返回计算值,其余列返回 { rowspan: 1, colspan: 1 }。onMounted 中调用 sortData,sortData 先用 name.localeCompare 排序,再调用 calculateSpanArr 重算 spanArr。若接口已排序,可省略排序,但仍需计算 spanArr。
- <template>
- <div class='about'>
- <h2>element-plus 合并行示例</h2>
- <el-table :data='tableData' :span-method='objectSpanMethod' border style='width: 100%'>
- <el-table-column prop='name' label='Name' />
- <el-table-column prop='age' label='Age' />
- <el-table-column prop='address' label='Address' />
- </el-table>
- </div>
- </template>
- <script setup>
- import { ref, onMounted } from 'vue'
- const spanArr = ref([])
- const tableData = ref([
- { key: '1', name: 'Mike', age: 30, address: 'Chicago' },
- { key: '2', name: 'Alice', age: 22, address: 'Phoenix' },
- { key: '3', name: 'John', age: 25, address: 'New York' },
- { key: '4', name: 'Mike', age: 31, address: 'Houston' },
- { key: '5', name: 'John', age: 26, address: 'Los Angeles' },
- { key: '6', name: 'jack', age: 22, address: 'aPhoenix' }
- ])
- const sortData = () => {
- tableData.value.sort((a, b) => a.name.localeCompare(b.name))
- calculateSpanArr()
- }
- const calculateSpanArr = () => {
- const tempArr = []
- let position = 0
- tableData.value.forEach((item, index) => {
- if (index === 0) {
- tempArr.push(1)
- position = 0
- } else if (item.name === tableData.value[index - 1].name) {
- tempArr[position] += 1
- tempArr.push(0)
- } else {
- tempArr.push(1)
- position = index
- }
- })
- spanArr.value = tempArr
- }
- const objectSpanMethod = ({ columnIndex, rowIndex }) => {
- if (columnIndex === 0) {
- return {
- rowspan: spanArr.value[rowIndex],
- colspan: spanArr.value[rowIndex] > 0 ? 1 : 0
- }
- }
- return { rowspan: 1, colspan: 1 }
- }
- onMounted(() => {
- sortData()
- })
- </script>
复制代码
注意:spanArr[rowIndex] 为 0 时 colspan 返回 0,用于隐藏被合并单元格;若 columnIndex 判断错误,会把合并应用到错误列。排序后重算很关键,数据变化后不重算会导致 rowspan 与行数据错位。
二、a-table 通过 columns.customCell 返回 rowSpan
a-table 的合并逻辑落在列定义的 customCell 中,返回 { rowSpan }。示例用 spanMap 保存每个索引的 span:计算方式与 el-table 类似,首行 span 为 1;与前一行 name 相同则累加 position 对应 span,并把当前索引 span 置 0;否则当前索引 span 为 1 并更新 position。customCell 的第二个参数是 index,据此从 spanMap 取 rowSpan。被合并行返回 rowSpan: 0 才会隐藏;dataSource 中需要 key 作为唯一标识。原文说明提到 customCell 和 customRender,具体示例的合并逻辑落在 customCell 的 rowSpan 返回值上。
- <template>
- <div class='about'>
- <h2>ant-design-vue 合并行示例</h2>
- <a-table :dataSource='tableData' :columns='columns' bordered />
- </div>
- </template>
- <script setup>
- import { ref, onMounted } from 'vue'
- const tableData = ref([
- { key: '1', name: 'Mike', age: 30, address: 'Chicago' },
- { key: '2', name: 'Alice', age: 22, address: 'Phoenix' },
- { key: '3', name: 'John', age: 25, address: 'New York' },
- { key: '4', name: 'Mike', age: 31, address: 'Houston' },
- { key: '5', name: 'John', age: 26, address: 'Los Angeles' },
- { key: '6', name: 'jack', age: 22, address: 'aPhoenix' }
- ])
- const spanMap = ref({})
- const calculateSpans = () => {
- const tempMap = {}
- let position = 0
- tableData.value.forEach((item, index) => {
- if (index === 0) {
- tempMap[index] = { span: 1 }
- position = 0
- } else if (item.name === tableData.value[index - 1].name) {
- tempMap[position].span += 1
- tempMap[index] = { span: 0 }
- } else {
- tempMap[index] = { span: 1 }
- position = index
- }
- })
- spanMap.value = tempMap
- }
- const sortData = () => {
- tableData.value.sort((a, b) => a.name.localeCompare(b.name))
- calculateSpans()
- }
- const columns = [
- {
- title: 'Name',
- dataIndex: 'name',
- customCell: (_, index) => {
- return { rowSpan: spanMap.value[index]?.span || 0 }
- }
- },
- { title: 'Age', dataIndex: 'age' },
- { title: 'Address', dataIndex: 'address' }
- ]
- onMounted(() => {
- sortData()
- })
- </script>
复制代码
排序方法同样在 onMounted 中执行,先排序再 calculateSpans。若只排序不重算 spanMap,或漏掉 rowSpan: 0,会出现合并错位、重复显示。合并遍历是一次 O(n) 计算,不要在 customCell 内重复全量遍历;在数据源更新后重新计算 spanMap 更稳妥。
三、两种表格合并的差异与排查点
el-table 与 a-table 的差异主要在 API:el-table 返回 rowspan/colspan(小写),通过 span-method 统一处理;a-table 在 columns 的 customCell 返回 rowSpan(大写),按列配置。共同点是都要先排序让相同值相邻,再计算跨度;合并依据字段、排序字段和显示列要保持一致。适用场景是报表、分组列表等首列同名数据展示;如果合并字段不是相邻同名,示例逻辑不会自动合并。排查时优先检查四点:数据是否已按合并字段排序;spanArr 或 spanMap 是否在排序后重新计算;el-table 的 columnIndex 是否指向正确列;a-table 被合并行是否返回 rowSpan: 0。接口已排序时可以省略排序步骤,但合并跨度仍需计算。 |