antd Table通过onCell属性实现表格自定义合并

前段时间迭代不是很忙,抽空写了个表格合并的函数。这边对此进行简单记录一下。

主要实现的功能如下:

(ps:该函数需要搭配antd Table使用)

1.表格数据处理:按照某列处理为相同数据展示在一起

2.单行列的表格数据合并,对于某列进行指定行范围的列合并, 或对于某行进行指定列范围的行合并

3.整体表格的数据合并,对于指定的两个区域,分别进行行合并和列合并
 
原理说明如下:
通过 colSpan,rowSpan 进行行列合并。
需要合并的列需添加onCell属性,rowSpan命名格式为:record['rowSpan' + 列key], colSpan命名格式为:record['colSpan' + 列key]
给Table传入columns时,可通过如下columnsTitle给columns添加onCell属性(<Table columns={this.columnsTitle(columns)} />)
columnsTitle = (column = []) => {
  // 给columns根据key动态增加onCell属性
  return column.map(item => {
    item.onCell = record => ({
      rowSpan: record['rowSpan' + item.key],
      colSpan: record['colSpan' + item.key]
    });
    return item;
  });
};

 

一些传参说明:

| 属性项      | 说明                              | type   | 可选 | required | default | 详细说明                                            |
| :---------- | :-------------------------------- | :----- | :--- | :------- | :------ | :-------------------------------------------------- |
| data        | 表格数据 Table-dataSource         | Array  | -    | true     | -       |
| columns     | 表格列配置 Table-columns          | Array  | -    | true     | -       |
| mergeMethod | 表示操作类型 行/列合并 或处理数据 | String | -    | true     | -       | 'column' / 'row' / 'handleData' / 'handleMultiData' |
| markNum     | 指定合并的行列                    | Number | -    | false    | -       | 单数据合并及处理数据时必填(从0初始)                          |
| mergeScope  | 合并的范围,指定行列范围          | Array  | -    | - false  | -       | 单数据及多数据合并时必填                            |
| rowRefer    | 正在合并的行数                    | Number | -    | - false  | -       | 行合并 (mergeMethod = 'row')合并时必填            |

对于mergeScope的详细说明:

- 单列('column'): 表示按照从 mergeScope[0]行至 mergeScope[1]行的列合并 例如:[2,5]
- 单行('row'): 表示按照从 mergeScope[0]列至 mergeScope[1]列的行合并 例如: [2,5]
- 多行列合并('handleMultiData'): mergeScope 内元素也是数组形式[[], []]
  例如: mergeScope: [[1,2,1,4], [3,5,1,5]]

- - 四个值分别表示起始列,结束列,起始行,结束行
- - mergeScope[0]表示列合并范围:第一列至第二列进行列合并(从第一行到第四行的列合并)
- - mergeScope[1]表示行合并范围:第一行至第五行进行行合并(从第三列到第五列的行合并);
- - 当只需要进行多列合并,第二个参数可传[], eg: [[1,2,1,4], []];
- - 当只需要进行多行合并,第一个参数可传[], eg: [[], [3,5,1,5]]

对于markNum的说明:

markNum:

- 单列及处理数据时传需要处理的列数
- 单行合并时传合并范围的起始列,eg:mergeScope: [2,5] -> markNum: 2

 

传参参考:

mergeMethod合并类型: 'column' 'row' 'handleData' 'handleMultiData'1.'handleData'- 按照某列进行数据处理  - rowSpanData({data, columns, mergeMethod, markNum})
2.'column'- 某列进行指定行范围数据合并  - rowSpanData({data, columns, mergeMethod, markNum, mergeScope})
3.'row'  - 某行进行指定列范围数据合并 - rowSpanData({data, columns, mergeMethod, markNum, mergeScope, rowRefer})
4.'handleMultiData' - 同时进行多行列数据的合并 - rowSpanData({data, columns, mergeMethod, mergeScope})

 

---------------------------------------------------------------------------手动分割线---------------------------------------------------------------------------------------------------------------------

代码实现如下:

只要引入rowSpanData函数即可进行使用

// 对于表格数据进行计算处理

// 列表合并相同列
// 表头只支持列合并,使用 column 里的 colSpan 进行设置。
// 表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。
// 表格的合并操作主要就是通过设置colSpan属性&rowSpan属性
const mergeRows = (data: any[], colName: string, mergeScope: any[]): any[] => {
  const startMergeSite = mergeScope[0];
  const endMergeSite = mergeScope[1];
  let idx = 0;
  let handledata = data.slice(startMergeSite, endMergeSite + 1);
  try {
    const filterData = handledata.find(
      // 判断是否已经进行过该colName的行列合并,不接受某行列进行同时行列合并
      item =>
        item['colSpan' + colName] ||
        item['colSpan' + colName] === 0 ||
        item['rowSpan' + colName] ||
        item['rowSpan' + colName] === 0
    );
    if (filterData) {
      handledata = data.slice(startMergeSite, endMergeSite + 1);
      throw Error('');
    } else {
      data[startMergeSite]['rowSpan' + colName] = 1;
      handledata = data.slice(startMergeSite + 1, endMergeSite + 1);
      handledata = handledata.reduce(
        (mergedRows, item, index) => {
          if (item[colName] === mergedRows[idx][colName]) {
            mergedRows[idx]['rowSpan' + colName]++;
            item['colSpan' + colName] = 0;
          } else {
            item['rowSpan' + colName] = 1;
            idx = index + 1;
          }
          return [...mergedRows, item];
        },
        [data[startMergeSite]]
      );
    }
  } catch (e) {
    console.log('该行列已被合并过,不支持列合并');
  }
  // 进行原始数组的拼接
  data.splice(startMergeSite, endMergeSite - startMergeSite + 1, ...handledata);
  return data;
};

// 行合并
const mergeCells = (
  data: any[],
  colName: string,
  rowRefer: number,
  mergeScope: number[],
  columnList: string[]
): any[] => {
  const startMergeSite = mergeScope[0];
  const endMergeSite = mergeScope[1];
  if (Array.isArray(data) && data.length === 0) return [];
  const dataArr = [...data[rowRefer]]; // 需要处理的该行数据
  let handleData = dataArr;
  try {
    const handledata = dataArr.find(
      // 异常行列合并捕获
      item =>
        item['colSpan' + colName] ||
        item['colSpan' + colName] === 0 ||
        item['rowSpan' + colName] ||
        item['rowSpan' + colName] === 0
    );
    if (handledata) {
      throw Error('');
    } else {
      handleData = dataArr.reduce((result, item) => {
        let idx = 1;
        const handleColumnList = columnList.slice(startMergeSite + 1, endMergeSite + 1);
        Object.entries(item).forEach(item1 => {
          if (item1[0] !== colName && item1[1] === item[colName] && handleColumnList.includes(item1[0])) {
            item['rowSpan' + item1[0]] = 0;
            idx++;
            item['colSpan' + colName] = idx; // 需要占据的表格个数
          }
        });
        return [...result, ...item];
      }, []);
    }
  } catch (e) {
    console.log('该行列已被合并过,不支持行合并');
  }
  data.splice(rowRefer, 1, ...handleData);

  return data;
};

// 数据处理
const classifyRows = (data: any[], classifyRule: string): any[] => {
  // 对于表格数据根据key进行排序处理
  const keys = [classifyRule];
  const classified = data.slice(1).reduce(
    (ordered, row) => {
      // 从已经排好序的行中寻找一行 row1,如果这行 row1 的 keys 值分别等于当前遍历到的行的 keys 值,就把当前遍历到的这一行插入到 row1 之后
      // 否则把当前遍历到的行放在所有排好序的行的最后一行
      const index = ordered.findIndex(orderedRow =>
        keys.reduce((boolean, curKey) => boolean && orderedRow[curKey] === row[curKey], true)
      );
      if (index !== -1) {
        return [...ordered.slice(0, index + 1), row, ...ordered.slice(index + 1)];
      } else {
        return [...ordered, row];
      }
    },
    [data[0]]
  );
  return classified;
};

interface TableParams {
  data?: any[];
  mergeMethod?: string;
  markNum?: string | number;
  mergeScope?: any[];
  columns?: any[];
  rowRefer?: number;
}

const rowSpanData = ({
  data = [],
  columns,
  mergeMethod = 'column',
  markNum,
  mergeScope,
  rowRefer
}: TableParams = {}): any[] => {
  //  列表合并相同行 rowSpanData: (data, colName)
  //  列表合并相同行 rowSpanData: (data, colName) ————————》升级一下:row 一行某几列合并、col 一列 某几行合并、columns 表头多级处理 (只是对数据处理)
  let result;
  // 获取表格columns数组的key集合
  const columnList = Array.isArray(columns) && columns.map(item => item.key);
  const colName = columnList && columnList[markNum];
  if (mergeMethod === 'column') {
    result = mergeRows(data, colName, mergeScope);
  } else if (mergeMethod === 'row') {
    result = mergeCells(data, colName, rowRefer, mergeScope, columnList);
  } else if (mergeMethod === 'handleData') {
    result = classifyRows(data, colName);
  } else if (mergeMethod === 'handleMultiData') {
    // 表格整体处理

    const colMergeScope = mergeScope[0];
    const rowMergeScope = mergeScope[1];
    let handleMergeData = data;
    // 先进行列合并
    // 为【】时不进行列处理
    if (Array.isArray(colMergeScope) && colMergeScope.length) {
      const startColMergeCol = colMergeScope[0];
      const endColMergeCol = colMergeScope[1];
      const startRowMergeCol = colMergeScope[2];
      const endRowMergeCol = colMergeScope[3];
      for (let i = startColMergeCol; i <= endColMergeCol; i++) {
        handleMergeData = mergeRows(handleMergeData, columnList[i], [startRowMergeCol, endRowMergeCol]);
      }
    }

    // 再进行行合并
    // 为【】时不进行行处理
    if (Array.isArray(rowMergeScope) && rowMergeScope.length) {
      const startColMergeRow = rowMergeScope[0];
      const endColMergeRow = rowMergeScope[1];
      const startRowMergeRow = rowMergeScope[2];
      const endRowMergeRow = rowMergeScope[3];
      for (let i = startRowMergeRow; i <= endRowMergeRow; i++) {
        handleMergeData = mergeCells(
          handleMergeData,
          columnList[startColMergeRow],
          i,
          [startColMergeRow, endColMergeRow],
          columnList
        );
      }
    }
    result = handleMergeData;
  }
  return result;
};
export default rowSpanData;

实现的效果如下:

 

 

个人总结:

基本大致如上,虽然只是一个简单的函数完成,却也花了一点时间,菜是原罪。

在不忙的时候开始写,刚准备写的时候就开始版本迭代,陆陆续续的开发,到开完完成之后,写完给同事看看会不会用,又被同事伙伴找出了几个需要优化的点并且给了我很好的意见,对于参数的传递需要贴切用户而不是便于开发者,对于错误的异常抛出避免表格错乱等等问题。改进了一波,终于写完了,完结撒花。

posted @ 2022-08-16 16:13  千亿昔  阅读(5561)  评论(0编辑  收藏  举报