ant design vue:Table的行合并

  1. 目标:能够方便的实现
    图中的样式
  2. 想要的使用方式:
<div>
  <a-table
    :columns="mcolRight"
    :dataSource="dataRight"
    :striped="true"
    :bordered="true"
    :show-index-column="false"
    :pagination="pagination"
    @change="handlePaginationChange"
  />
</div>
const mcolRight = useTableMerge({
  mergeFields: ["name", "age", "salary"], //指定要合并的列,都一样的数据才合并
  columns: colRight, // 全部的列
  dataSource: dataRight, // 表格的数据
  mergeIndex: true, // 指定是否合并序号列,不填默认为false
  pagination, // 如果指定合并序号列,需要传递分页信息
});
  1. 主要方法:
// 表格数据分组函数:根据指定列的值相同进行分组
// 返回格式: { start: number, end: number, id: 0 }[]
// 数据经过合并后,start表示开始的索引,end表示结束的索引,id表示合并后的组号,在这里全部设置为0
export function groupTableData(
  dataSource: DataSource,
  groupFields: string[]
): Array<{ start: number, end: number, id: 0 }> {
  if (!dataSource.length || !groupFields.length) return [];

  const groups: Array<{ start: number, end: number, id: 0 }> = [];
  let currentGroupStart = 0;
  let currentGroupValues = getGroupValues(dataSource[0], groupFields);

  for (let i = 1; i < dataSource.length; i++) {
    const rowValues = getGroupValues(dataSource[i], groupFields);

    // 如果当前行与组内值不匹配,结束当前组并开始新组
    if (!isValuesEqual(rowValues, currentGroupValues)) {
      groups.push({
        start: currentGroupStart,
        end: i - 1,
        id: 0,
      });
      currentGroupStart = i;
      currentGroupValues = rowValues;
    }
  }
}
// 辅助函数:获取行中指定分组字段的值
function getGroupValues(
  row: TableRecord,
  fields: string[]
): (string | number)[] {
  return fields.map((field) => row[field]);
}
// 辅助函数:比较两组值是否相等
function isValuesEqual(
  a: (string | number)[],
  b: (string | number)[]
): boolean {
  if (a.length !== b.length) return false;
  return a.every((val, index) => val === b[index]);
}

如果不需要将序号列合并,用这几个方法,再加上一个最终合并的方法就够了。
行合并最主要的就是设置 rowSpan 的值,设置为 0 时就是不渲染,会和上一个合并,不为 0,设置成几就是合并下几行。
合并方法:

// 合并单元格的配置函数
function createMergeCell(
  fields: string[],
  data: DataSource
): (record: TableRecord, index: number) => { rowSpan: number } {
  // 仅计算一次分组信息
  const groups = groupTableData(data, fields);
  return (record, index) => {
    const group = groups.find((g) => g.start <= index && g.end >= index); //
    if (!group) return { rowSpan: 1 }; //默认返回{ rowSpan: 1 },表示单独一行
    return {
      // 如果当前行在分组中的第一项,那么rowSpan设置为当前分组数量大小(合并整个分组),
      // 其余项设置为0(合并到其中)。
      rowSpan: index === group.start ? group.end - group.start + 1 : 0,
    };
  };
}

如果需要将序号列合并,目前采用的方法是:
在配置表格的时候不配置序号列,生成一个序号列,然后返回。分组计算的时候需要根据分页信息来计算。
举例:一页 5 条数据,数据源一共 13 条数据,经过groupTableData方法处理后分成数量为 1,3,4,5 的四组数据,对应生成的序号列就是 1,2,3,4。
由于一页只展示 5 条数据,需要将这四组数据再次分组,确保每一组的序号在翻页后任然是连续正确的。
处理后的分组数量应该是:
1,
3,
1,(第一页数据结束)
3,
2,(第二页数据结束)
3
这样对应的序号就是正确的,1,2,3,4,5,6。

// 分割组函数:根据pageSize分割跨页的组
function splitGroups(
  groups: Array<{ start: number, end: number, id: number }>,
  pagination?: Ref<Pagination>
): Array<{ start: number, end: number, id: number }> {
  const { pageSize } = pagination?.value || { pageSize: 0 };
  if (!groups.length || !pageSize || pageSize <= 0) return groups;

  const splitResult: Array<{ start: number, end: number, id: number }> = [];
  let currentId = 1; // ID从1开始分配

  groups.forEach((group) => {
    const { start, end } = group;
    // 计算组的起始页和结束页
    const startPage = Math.floor(start / pageSize);
    const endPage = Math.floor(end / pageSize);

    // 如果组不跨页,直接添加
    if (startPage === endPage) {
      splitResult.push({
        start,
        end,
        id: currentId++,
      });
      return;
    }

    // 跨页情况,计算第一页的结束索引
    const firstPageEnd = (startPage + 1) * pageSize - 1;
    // 添加第一部分
    splitResult.push({
      start,
      end: firstPageEnd,
      id: currentId++,
    });

    // 添加中间页(如果有)
    const middlePages = endPage - startPage - 1;
    for (let i = 0; i < middlePages; i++) {
      const middleStart = (startPage + 1 + i) * pageSize;
      const middleEnd = (startPage + 2 + i) * pageSize - 1;
      splitResult.push({
        start: middleStart,
        end: middleEnd,
        id: currentId++,
      });
    }

    // 添加最后一部分
    const lastPageStart = endPage * pageSize;
    splitResult.push({
      start: lastPageStart,
      end,
      id: currentId++,
    });
  });

  return splitResult;
}

经过这样处理后,可以为每一组数据添加序号列,序号列里的序号就是 id,再创建合并单元格的配置函数给序号列的 customCell。
样例代码:

    const groups = groupTableData(data, fields);
    const aftersplit = splitGroups(groups, pagination);
    // 创建合并单元格的配置函数
    function createMergeCellWithIndex(
    groups: Array<{ start: number; end: number; id: number }>,
    pagination?: Ref<Pagination>,
    ): (record: TableRecord, index: number) => { rowSpan: number } {
    // 为确保返回值类型符合要求,添加默认返回值
    return (record, index) => {
        // 分组内的第一项rowSpan设置为要合并的数,其他项rowSpan设置为0
        // 默认设置成1,单行显示
        index =
        (pagination?.value.current - 1) * pagination?.value.pageSize + index;
        const temp = groups?.find(
        (item) => item.start <= index && item.end >= index,
        );
        if (temp) {
        if (index === temp.start) {
            return {
            rowSpan: temp.end - temp.start + 1,
            };
        } else {
            return {
                rowSpan: 0,
                };
        }
        } else {
            return {
                rowSpan: 1,
            };
        }
    };
    }

    // 创建序号列配置
    const indexColumn: BasicColumn = {
      title: '序号',
      dataIndex: 'index',
      width: 60,
      fixed: 'left',
      className: 'merge-table-background-color',
      customRender: ({ index }) => {
        const cur_i =
          index + (pagination.value.current - 1) * pagination.value.pageSize;
        const nowgroup = aftersplit.find(
          (item) => item.start <= cur_i && item.end >= cur_i,
        );
        return nowgroup?.id;
      },
    };
    // 添加合并单元格配置
    indexColumn.customCell = createMergeCellWithIndex(aftersplit, pagination);//

    // 将序号列添加到columns的最前面
    return [
      indexColumn,
      ...columns.map((col) => {
        if (!fields.includes(col.dataIndex as string)) { // 如果不是指定的要合并的列,正常返回
          return col;
        }

        return {
          ...col,
          customCell: createMergeCellWithIndex(aftersplit, pagination), // 需要合并的列
        };
      }),
    ];

综上,最后的逻辑代码类似:

export function useTableMerge(
  options: UseTableMergeOptions,
): ComputedRef<BasicColumn[]> {
  const {
    mergeFields,
    dataSource,
    columns,
    mergeIndex = false,
    pagination,
  } = options;

  // 使用computed确保响应式
  return computed(() => {
    // 确保dataSource是响应式的
    const data = unref(dataSource);
    // 确保columns是响应式的
    const currentColumns = unref(columns);

    return createMergeColumns(
      currentColumns,
      mergeFields,
      data,
      mergeIndex,
      pagination,
    );
  });
}
function createMergeColumns(
  columns: BasicColumn[],
  fields: string[],
  data: DataSource,
  mergeIndex = false,
  pagination?: Ref<Pagination>,
): BasicColumn[] {
  if (mergeIndex) {
    // 计算每行的实际序号
    const groups = groupTableData(data, fields);
    const aftersplit = splitGroups(groups, pagination);

    // 创建序号列配置
    const indexColumn: BasicColumn = {
      title: '序号',
      dataIndex: 'index',
      width: 60,
      fixed: 'left',
      className: 'merge-table-background-color',
      customRender: ({ index }) => {
        const cur_i =
          index + (pagination.value.current - 1) * pagination.value.pageSize;
        const nowgroup = aftersplit.find(
          (item) => item.start <= cur_i && item.end >= cur_i,
        );
        return nowgroup?.id;
      },
    };

    // 添加合并单元格配置
    indexColumn.customCell = createMergeCell(aftersplit, pagination);

    // 将序号列添加到columns的最前面
    return [
      indexColumn,
      ...columns.map((col) => {
        if (!fields.includes(col.dataIndex as string)) {
          return col;
        }

        return {
          ...col,
          customCell: createMergeCell(aftersplit, pagination),
        };
      }),
    ];
  } else {
    // 不添加序号列,直接处理columns
    return columns.map((col) => {
      if (!fields.includes(col.dataIndex as string)) {
        return col;
      }

      return {
        ...col,
        customCell: createMergeCellOLD(fields, data),
      };
    });
  }
}

在指定序号列也合并的时候,有没有更方便的方法呢?

posted on 2025-06-17 17:03  机械羊  阅读(264)  评论(0)    收藏  举报