ant-design-vue合并列表(一)

538 阅读7分钟

本篇介绍的是表格的简单合并,后续会介绍一下通过表单变动动态设置合并表格, 完整代码请查参考连接

先上效果

image.png

法一

数组方式合并

基本规则

首先看看ant-design-vue基本的合并规则,合并行的方式如下

export const columns = [
  {
    title: 'num',
    dataIndex: 'num',
    customCell: (_, index) => {
      // 将第二行与第三行进行合并
      if (index === 2) {
        return { rowSpan: 2 }
      }
      if (index === 3) {
        return { rowSpan: 0 }
      }
    },
  },
  {
    title: 'other',
    dataIndex: 'other',
  },
]
export const list = Array(5)
  .fill({})
  .map((_, index) => ({ num: index, other: index }))

也就是说当需要合并时我们要设置合并行的customCell返回{rowSpan:n}(n为合并的行数),而被合并的地方则需要返回{rowSpan:0},而默认的则为{rowSpan:1} 所以接下来我们将对column做操作

整体思路

创建一个对象,key为对应行的dataIndex,值为需要返回的合并行数组

eg:假设像一开始有5行数据,我们需要1、2行,3、4、5行合并,则构建出一个这样的数组

const columnConfig = {scheme:[2,0,3,0,0]}

接着设置好对应列的customCell

eg

const colums=[{
    title: '套餐',
    dataIndex: 'scheme',
    width: 120,
    customCell:(_, index)=>({
        rowSpan: columnConfig.scheme[index],
      }),
  }] 

原始数据

首先和日常一样准备一份原始数据实现如下表格

数据如下

import type { TColumnProps, EItem } from './data'
export const columns: Array<TColumnProps> = [
  {
    title: '套餐',
    dataIndex: 'scheme',
    width: 120,
  },
  {
    title: '内存',
    dataIndex: 'attr1',
    width: 120,
  },
  {
    title: '颜色',
    dataIndex: 'attr2',
    width: 120,
  },
  {
    title: '运行内存',
    dataIndex: 'attr3',
    width: 120,
  },
  {
    title: '进价',
    dataIndex: 'price',
    width: 120,
  },
  {
    title: '售价',
    dataIndex: 'price2',
    width: 120,
  },
]
​
export const list: Record<EItem, string>[] = [
  {
    scheme: '普通套餐',
    attr1: '35G',
    attr2: '天蓝色',
    attr3: '64G',
    price: '1000',
    price2: '2000',
  },
  {
    scheme: '普通套餐',
    attr1: '35G',
    attr2: '鹦鹉绿',
    attr3: '64G',
    price: '1000',
    price2: '2000',
  },
  {
    scheme: '普通套餐',
    attr1: '35G',
    attr2: '兰花紫',
    attr3: '64G',
    price: '1000',
    price2: '2000',
  },
  {
    scheme: '耳机套餐',
    attr1: '35G',
    attr2: '天蓝色',
    attr3: '64G',
    price: '1001',
    price2: '2001',
  },
  {
    scheme: '耳机套餐',
    attr1: '35G',
    attr2: '鹦鹉绿',
    attr3: '64G',
    price: '1001',
    price2: '2001',
  },
  {
    scheme: '耳机套餐',
    attr1: '35G',
    attr2: '兰花紫',
    attr3: '64G',
    price: '1001',
    price2: '2001',
  },
  {
    scheme: '耳机套餐',
    attr1: '35G',
    attr2: '兰花紫',
    attr3: '32G',
    price: '1001',
    price2: '2001',
  },
]

得到如下表格

image.png

开发

获取项数组

将配置好的columns中的dataIndex提取处理,用于将来构造配置

const columnSpans = getSpansColumn(columns)
//需要合并的项
function getSpansColumn(sellColumns: ColumnProps[]): TColumn[] {
  const spans: TColumn[] = []
  for (let i = 0; i < sellColumns.length; i++) {
    const key = sellColumns[i].dataIndex
    spans.push(key as TColumn)
  }
  return spans
}

接着得出配置数据(核心)

思路

如何计算需要合并的列

法一:

表格(list)数据从后往前,如果上一行同当前行数值一样,则当前行数值清0,累计数值加1,直到上一行与当前行不一样

法二:(本案例所使用)

表格(list)从上到下执行,start为当前行数,当前行为i

遇到下一行与当前行不一样则

  • 将配置数组中的当前行start所在元素置为i - start+ 1
  • 将start到i过程中的所有值置为0
  • 设置start为i+1 代码如下
// 计算每一列需要的rowsSpans
function calculateRowSpans(columnSpans: EItem[]): Record<EItem, number[]> {
  const rows = list
  // 收集跨行映射
  const spanConfig: Record<string, number[]> = {}
  const spansData = new Array(rows.length).fill(0)
  columnSpans.forEach((columnSpan) => {
    const spans: number[] = [...spansData]
    let currentSpanStart = 0
    for (let i = 0; i < rows.length; i++) {
      // 遇到不同组或最后一行时结算跨度
      if (
        i === rows.length - 1 ||
        rows[i][columnSpan] !== rows[i + 1][columnSpan]
      ) {
        spans[currentSpanStart] = i - currentSpanStart + 1
        for (let j = currentSpanStart + 1; j <= i; j++) {
          spans[j] = 0
        }
        currentSpanStart = i + 1
      }
    }
    spanConfig[columnSpan] = spans
  })
  return spanConfig
}

以开头1、2行,3、4、5行合并,得到结果 columnConfig = {scheme:[2,0,3,0,0]}为例

开始start为0,遍历表格,直到第二行与第三行数据不一样,此时i=1

所以

1、将start所在元素值为i-start+i即1-0+1=2

即scheme:[2]

2、将start到i过程中的所有值置为0

就是i=0之后到i=3之前的值置为0

即scheme:[2,0]

3、设置start为i+1

重复上述过程得到[2,0,3,0,0]

拿我给的数据按这个思路做的话会得到如下表格

image.png

我的老天鹅!!!这是个什么东西

所有相同类型的行都合并到了一块,这不是我们想要的,所以要改一下合并到一起的判断条件

例如表格中运行内存列第一行与第二行是否合并不是取决于是否值相同,而应该取决于前面所有数据加当前数据是否相同即

第一行:普通商品+35G+天蓝色+64G

第二行:普通商品+35G+鹦鹉绿+64G

这两行不一致,故而就是一样为64G也不应该合并

所以应该遍历每一列,将每一行当前列的值累加起来

例如,遍历到第一列时是(避免特殊情况加了个_)

columnValues=["普通套餐_", "普通套餐_", "普通套餐_", "耳机套餐_", "耳机套餐_", "耳机套餐_", "耳机套餐_"]

此时普通套餐合并为一行,耳机套餐合并为一行

第二列

columnValues=[
  "普通套餐_35G_","普通套餐_35G_","普通套餐_35G_","耳机套餐_35G_","耳机套餐_35G_","耳机套餐_35G_","耳机套餐_35G_"
]

此时普通套餐_35G合并为一行,耳机套餐_35G合并为一行

第三列

[  "普通套餐_35G_天蓝色_",  "普通套餐_35G_鹦鹉绿_",  "普通套餐_35G_兰花紫_",  "耳机套餐_35G_天蓝色_",  "耳机套餐_35G_鹦鹉绿_",  "耳机套餐_35G_兰花紫_",  "耳机套餐_35G_兰花紫_"]

此时只有最后两行合并

此时前三列效果如下

代码如下

// 计算每一列需要的rowsSpans
function calculateRowSpans(columnSpans: TColumn[]): Record<TColumn, number[]> {
  const rows = list
  // 收集跨行映射
  const spanConfig: Record<string, number[]> = {}
  const spansData = new Array(rows.length).fill(0)
  //遍历到当前列为止的行值
  const columnValues: string[] = new Array(rows.length).fill('')
  columnSpans.forEach((columnSpan) => {
    const spans: number[] = [...spansData]
    columnValues[0] += rows[0][columnSpan] + '_'
    let currentSpanStart = 0
    for (let i = 0; i < rows.length; i++) {
      if (i < rows.length - 1) {
        columnValues[i + 1] += rows[i + 1][columnSpan] + '_'
      }
      // 遇到不同组或最后一行时结算跨度
      if (i === rows.length - 1 || columnValues[i + 1] !== columnValues[i]) {
        spans[currentSpanStart] = i - currentSpanStart + 1
        for (let j = currentSpanStart + 1; j <= i; j++) {
          spans[j] = 0
        }
        currentSpanStart = i + 1
      }
    }
    spanConfig[columnSpan] = spans
  })
  return spanConfig
}

改造columns

const resultColumns = generateMergedColumns(columnConfig)
//生成带合并配置的列定义
function generateMergedColumns(columnConfig: Record<TColumn, number[]>) {
  return columns.map((column) => {
    if (!columnConfig[column.dataIndex]) return column
​
    return {
      ...column,
      customCell: (_, index: number) => ({
        rowSpan: columnConfig[column.dataIndex][index],
      }),
    }
  })
}

小优化

案例中我们只合并属性,后面进价与售价是不可能合并在一起的,所以应该在一开始拿表格项时拿到运行内存列即可,实际开发工具场景调节

//截取需要合并的项
function getSpansColumn(sellColumns: ColumnProps[]): TColumn[] {
  const spans: TColumn[] = []
  for (let i = 0; i < sellColumns.length; i++) {
    const key = sellColumns[i].dataIndex
    spans.push(key as TColumn)
    if (key === 'attr3') {
      return spans
    }
  }
  return spans
}
const columnSpans = getSpansColumn(columns.slice(0,4))

法二

这种方式更加适合用于静态column,即column不是动态变化的情况下,准确来来说是不需要一直修改column

所以创建一个staticColumns如下

export const staticColumns: TColumnProps = [
  {
    title: '套餐',
    dataIndex: EItem.scheme,
    width: 120,
    customCell: (record) => {
      return {
        rowSpan: record['column_span_scheme']
      }
    }
  },
  {
    title: '内存',
    dataIndex: EItem.attr1,
    width: 120,
    customCell: (record) => {
      return {
        rowSpan: record['column_span_attr1']
      }
    }
  },
  {
    title: '颜色',
    dataIndex: EItem.attr2,
    width: 120,
    customCell: (record) => {
      return {
        rowSpan: record['column_span_attr2']
      }
    }

  },
  {
    title: '运行内存',
    dataIndex: EItem.attr3,
    width: 120,
  },
  {
    title: '进价',
    dataIndex: 'price',
    width: 120,
  },
  {
    title: '售价',
    dataIndex: 'price2',
    width: 120,
  },
]

也就是说将column_span_[key]的值注入到list中,这样就不需要再columns中一直判断,也可以把数据分隔到一个单独的ts文件中(如config.ts)

与法一不同的是再计算rowSpan的时候我们不再需要使用spanConfig,参考法一思路,做如下修改

// 计算每一列需要的rowsSpans 要合并的columnSpan
function calculateRowSpans(columnSpans: string[], list: any[]): void {
  const rows = list;
  ...
  columnSpans.forEach(columnSpan => {
    columnValues[0] += rows[0][columnSpan] + '_';
    let currentSpanStart = 0;
    for (let i = 0; i < rows.length; i++) {
      if (i < rows.length - 1) {
        columnValues[i + 1] += rows[i + 1][columnSpan] + '_';
      }
      // 遇到不同组或最后一行时结算跨度
      if (i === rows.length - 1 || columnValues[i + 1] !== columnValues[i]) {
        rows[currentSpanStart][prefix + columnSpan] = i - currentSpanStart + 1;
        for (let j = currentSpanStart + 1; j <= i; j++) {
          rows[j][prefix + columnSpan] = 0;
        }
        currentSpanStart = i + 1;
      }
    }
  });
}