el-table 动态表格的合并-二维数组

 图1
图1
el-table的表格合并都需要通过span-method方法来实现,无论简单的还是复杂的。上面的例子您做过吗?就算只是展示其实也是有一点难度的,更何况它是动态的,可添加、可删除,可以先思考一下,看看您是想怎么实现它呢?

方案分析

如果是纯展示那么我们只需要通过 数组 就可完成,如果还要支持添加、删除的话,用数组应该不是一个好方案,这里我们采用 树 来实现,树的结构如下:
图2
图2
采用树的结构,我们要对树进行添加、删除简直是太简单了,可是现在最大的问题是如何把 树转换成数组 并展示到页面上,在页面上点击添加、删除时又是如何把 数组转成树 并进行对应操作,这是两个比较难的问题,假设我们都已经解决了,那么接下来数组怎么在页面上展示呢?span-method方法应该怎样实现呢?这对于整个动态表格的合并来说是最难的问题,树和数组的转换我们在网上可以找到类似的案例,但这个 特定场景的表格合并 只能靠我们自己,这里我采用 二维数组 方案来实现,假设我们已经得到了这样的二维数组和span-method方法的实现
图3
图3
图1 可以得出,我们需要动态合并的 只有行,列并不需要我们太多关心,所以上图col的值当row值大于0时,col就显示即占1列,否则就和row一样隐藏,这里rowMergeArr代表的是行合并的二维数组。

我们结合 图1 往 图3 的二维数据里带入一下,中国合并8行,河北、河南各合并4行,石家庄、邢台、郑州、洛阳各合并2行,剩下各占1行,这样就完成了表格的合并展示对不对。

现在问题来了,我们怎么知道 中国、河北、河南等都各占几行呢?其实这里每一个问题都是有难度的,都是有一些算法在里面的,下面我们就通过一个例子来慢慢解释每一个问题的解决方案

注意: 这里我们的需求是最多有四级目录,所以这里二维数组内每一个数组的长度都是4,并且数组的最后一项肯定都是1,如果只有一级、二级目录,那数组的后两项我们都会置成1,代表占一个单元格

问题列表

  • 树的每个节点占几行
  • 合并单元格的二维数组如何生成
  • 树转成什么结构的数组
  • 数组如何转成指定结构的树

示例图

图4
图4
树的结构
图5
图5
注意: 下面每个问题都是通过这张图来讲解,一是这张图更全面,二是开阔大家思维,让大家理解更深刻。大家也可以通过下面的讲解去分析分析上面图1结构应该怎样去理解

树的每个节点占几行

const addItemColspan = (root) => {
  if (root == null) {
    return 0
  }
  if (!root.children || !root.children.length) {
    root['colspan'] = 1
    return 1
  }
  let sum = 0
  for (let i = 0; i < root.children.length; i++) {
    sum += addItemColspan(root.children[i])
  }
  root['colspan'] = sum
  return sum
}
  1. 递归树形结构,给树上每个对象添加colspan值,代表该节点所占行数
  2. 判断如果传入的节点是空,则返回0
  3. 如果节点没有children或长度为0,则代表是叶子结点,则添加当前节点colspan值为1,并返回1
  4. 不是叶子结点则递归调用当前函数,直到递归到叶子结点再一层层返回并累加到sum值上,再给当前节点添加colspan字段并返回sum值
  5. 最后返回传入的根root的colspan值,此值也是最大的colspan值,因为每一个父节点的colspan值都是子节点的colspan值之和

执行完成后,树会变成这个样子
图6
图6

注意: 形参root代表树的第一层节点 如:中国、美国

合并单元格的二维数组如何生成

创建一个空二维数组

  const create2DArr = (num) => {
    let arr: number[][] = []
    for (let i = 0; i < num; i++) {
      arr[i] = []
      for (let j = 0; j < 4; j++) {
        if (j === 3) {
          arr[i][j] = 1
        } else {
          arr[i][j] = 0
        }
      }
    }
    return arr
  }

二维数组的长度由传入的num值决定,内部每一个数组的长度都是4,角标3上赋值为1,其它位置都为0

生成合并单元格的二维数组

const getMergeRow2DItemArr = (root) => {
  let maxColspan = addItemColspan(root)
  let newArr = create2DArr(maxColspan)
  newArr[0][0] = maxColspan
  let m = 0
  let n = 0
  if (!root.children || !root.children.length) {
    newArr[0][1] = 1
    newArr[0][2] = 1
    return newArr
  }
  for (let i = 0; i < root.children.length; i++) {
    let node = root.children[i]
    newArr[m][1] = node.colspan
    m += node.colspan
    if (!node.children || !node.children.length) {
      newArr[n][2] = 1
      n++
      continue
    }
    for (let j = 0; j < node.children.length; j++) {
      let nextNode = node.children[j]
      newArr[n][2] = nextNode.colspan
      n += nextNode.colspan
    }
  }
  return newArr
}
  1. 给每个节点添加占表格的行数,并返回当前根节点占的行数,因每一个节点占用的行数都是子节点占用行数的和,所以这里返回的根节点就是占用最大的行数
  2. 创建一个合并单元格的二维数组,并每一项赋值给0
  3. 给二维数组第一项的角标0赋值最大的行数,接下来只对每个数组的角标1、角标2进行赋值(因为角标3一直是1)
  4. 定义两个变量 m、n,都代表要修改的二维数组的角标,m只配合修改每个数组的角标1使用,n只配合修改每个数组的角标2使用
  5. 判断根节点下是否有子节点,如果没有则给角标0上的数组的角标1、角标2赋值为1,并返回
  6. 有子节点,则循环并给newArr[m][1]赋值,
  7. 让m加上node.colspan, 目的是为了让m向后移动node.colspan步,下次赋值时,直接从移动后的m开始赋值,中间由0填充代表不占行数即隐藏
  8. 判断当前节点是否有子节点,如果没有则给newArr[n][2]赋值为1,代表占一行,并让n向后移动一步,因为当前行已经赋值完成
  9. 如果有子节点,则继续循环,给newArr[n][2]赋值,并让n向后移动nextNode.colspan步,下次赋值时,直接从移动后的n开始赋值,中间由0填充代表不占行数即隐藏
  10. 最后返回赋值好的合并单元格的二维数组

执行完成后,得到的二维数组
图7
图7

注意: 形参root代表树的第一层节点 如:中国、美国

span-method对应的方法

  const arraySpanMethod = ({ rowIndex, columnIndex }) => {
    if (!arraySpan.length) return { rowspan: 0, colspan: 0 }
    const row = arraySpan[rowIndex][columnIndex]
    const col = row > 0 ? 1 : 0
    return {
      rowspan: row,
      colspan: col,
    }
  }
  1. 这里arraySpan就是对应上方得到的二维数组,当长度为0时代表没有数据,直接返回空就可以
  2. 二维数组中每一个值都代表rowspan的值,即代表合并的行数
  3. 通过row值获取col值,当row有值时,col值为1,代表占一列,当row没值时,col值也为0,代表隐藏

获取到合并单元格的二维数组后,接下来就是如何把 树转换成数组 并显示到el-table中了

树转成什么结构的数组

图8
图8
el-table中要想显示每一项的数据,就必须要有具体对应的字段,而我们树中的字段都叫name,因此必须要想办法做区分,这里我是通过label后加对应角标做区分的,如label0代表第一列、label1代表第二列、label2代表第三列、label3代表第四列,再分别从各自label中取name值,这样就可以显示出来了。

注意: 细心的你可能已经发现了,这里在每一个对象里多了一个index,这个index代表每一个label对应树的位置,用于把数组转换成树时使用,这个index我们在后面会具体讲

const tranformTreeToArr = (arr) => {
  let result = []
  let num = 0
  function tranformTreeToArr1 (arr, num, obj, index) {
      for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i].children) && arr[i].children.length) {
          let tempObj = {
            ...arr[i],
          }
          delete tempObj.children
          let obj1 = {
            ...obj,
            [`label${num}`]: tempObj,
          }
          let newNum = num + 1
          let newIndex = `${index}-${i}`
          tranformTreeToArr1(arr[i].children, newNum, obj1, newIndex)
        } else {
          let newNum = num
          let newIndex = `${index}-${i}`
          let tempObj = {
            ...arr[i],
          }
          delete tempObj.children
          result.push({
              [`label${newNum}`]: tempObj,
              ...obj,
              index: newIndex,
            })
        }
      }
  }
  tranformTreeToArr1(arr, num, {}, '0')
  return result
}
  • 在外层定义一个数组,用于存放铺平的数据
  • 内部定义函数,arr:代表当前树,num:用于生成以label为开头的对象的key,obj:用于存放铺平的数据,index:代表每一个label[X]对象对应的树的位置
  • 内部开始判断是否为数组,如果是数组,则先获取当前对象,并赋值给label[num],但不需要它的children信息,所以要删除children,再和上层传递的obj进行合并后,再往下传递,传递的同时 newNum需要+1,newIndex也需要记录当前树转换为数组后每条数据对应树的位置
  • 如果不是数组,则代表已经循环到最后一层,生成新的对象tempObj,并赋值给label[num],然后再和上层传递过来的obj进行合并,并添加到result数组中,最后返回result数组

注意:

  • 这里在每一个对象里index的值是这样的如: “0-0-0-0-0”,第一个0-其实是没用的,所以最终转换成数组时我会把它去掉变成 “0-0-0-0” 并转成数组 [“0”,“0”,“0”,“0”],从而也就得到了我们看到的数组的结构
  • arr就是图5的树

通过上面的一系列操作,我们就可以显示出合并单元格的页面了,但还没完,我们还要动态的添加和删除行,添加或删除时我们的表格还要动态的变化,怎么办呢?
这里我的做法是每一次添加或删除行时,都先把数组再转化成树,然后再对树进行对应操作,操作完成后再转化成数组,这样就可以实现动态添加和删除行了,这里可能有人会觉得太浪费性能了,直接操作数组不可以吗?其实是不可以的,就算简单的写一个Demo,只通过数组操作就狠繁琐,更何况应用到具体的项目上,从可维护性、易读性上考虑我决定来转换成树来操作,只是降低了性能。但毕竟数据量不大,计算机应该是能忙过来的。

数组如何转成指定结构的树

把数组转换成树,这是一大难题,网上也有很多转换的方案,但大部分都是作为面试题出现的,真正应用到项目上的并不太多,大家应该还记得之前我提到的index,我说过它的存在就是帮助我们把数组转换成树的一个核心,这里我来说下它的具体原理,比如下面的数据

let table = [{
    "label0": {...}, "label1": {...}, "label2": {...}, "label3": {...},
    "index": ["0", "0", "0", "0"]
  }, {
    "label0": {...}, "label1": {...}, "label2": {...}, "label3": {...},
    "index": ["0", "0", "0", "1"]
  }, {
    "label0": {...}, "label1": {...}, "label2": {...}, "label3": {...},
    "index": ["0", "0", "1", "0"]
  }, {
    "label0": {...}, "label1": {...}, "label2": {...}, "label3": {...},
    "index": ["0", "0", "1", "1"]
  }, {
    "label0": {...}, "label1": {...},
    "index": ["1", "0"]
  }, {
    "label0": {...}, "label1": {...},
    "index": ["1", "1"]
  }]

转换的过程是这样的

  1. 先定义一个空数组(如:tree)用于存放接下来生成的树,其实树也是另一种形式的数组,只不过我们称之为树
  2. 循环数组,先拿到第一个对象的index值如:[“0”, “0”, “0”, “0”],数组的每一个角标都对应label[X]的值,这里的X代表的是数组的每一个角标,比如第一个0代表的是label0的值,第二个0代表的是label1的值,以此类推。数组中每一个值都代表是上一个值的第几个节点,如这里第一个0我就会拿到label0的值并把它添加到上方定义的数组中即:tree[0],并添加children属性,children属性的值是一个空数组,这样就完成了第一个节点的添加。第二个0我就会拿到label1的值并把它添加到第一个0对应对象的children内,因children是一个数组,所以添加到children数组的哪里是由第二个0决定的,这里就是添加到children的0角标上即tree[0].children[0];第三个0我就会拿到label2的值并把它添加到第二个0对应对象的children内,添加到children数组的哪里是由第三个0决定的即tree[0].children[0].children[0];第四个0我就会拿到label3的值并把它添加到第三个0对应对象的children内,添加到children数组的哪里是由第四个0决定的即tree[0].children[0].children[0].children[0];第一个对象内的index循环完以后空数组就会变成这样的 树 如:
  [{
    'name': '中国',
    'children': [{
      'name': '河北',
      'children': [{
          'name': '石家庄',
          'children': [{
              'name': '长安区',
               'children': []
            },
          ]
        },
      ]
    }]
  },
]
  1. 循环第二个对象的index 如 [“0”, “0”, “0”, “1”],拿到第一个0,再从之前生成的树获取0的位置是否有值如:tree[0],发现已经有值了则跳过继续循环,tree[0].children[0]发现也有值了,tree[0].children[0].children[0]发现也有值了都跳过,tree[0].children[0].children[0].children[1]发现没有值,则把label3对应的对象添加对应的位置上,现在数组会变成这个样子 如:
[{
    'name': '中国',
    'children': [{
      'name': '河北',
      'children': [{
          'name': '石家庄',
          'children': [{
              'name': '长安区',
               'children': []
            }, {
              'name': '裕华区',
              'children': []
            }
          ]
        },
      ]
    }]
  },
]

以此类推,把数组中每一项的index都循环完就会得到一个如图5的树

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现二维数组表格,可以使用el-table组件的嵌套表头功能,并根据二维数组的结构动态生成表格列和数据。 首先,需要定义二维数组的数据结构,例如: ```javascript data() { return { tableData: [ ['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I'] ] } } ``` 然后,在模板中使用el-table组件,并使用v-for指令动态生成表头和表格数据: ```html <el-table :data="tableData"> <el-table-column v-for="(row, rowIndex) in tableData" :key="rowIndex" label="Row"> <template slot-scope="{ row }"> <el-table-column v-for="(cell, cellIndex) in row" :key="cellIndex" :label="'Column ' + (cellIndex + 1)"> {{ cell }} </el-table-column> </template> </el-table-column> </el-table> ``` 在这个例子中,我们使用v-for指令遍历二维数组的每一行,并使用el-table-column动态生成每一行的表头。然后,再在每一行的slot-scope中使用v-for指令遍历该行的每一个单元格,并使用el-table-column动态生成每一列的表头和数据。 最终效果就是一个二维数组表格,每一行作为一个表头,每一列作为一个表格列,其中的数据来自于二维数组。 完整的代码示例: ```html <template> <el-table :data="tableData"> <el-table-column v-for="(row, rowIndex) in tableData" :key="rowIndex" label="Row"> <template slot-scope="{ row }"> <el-table-column v-for="(cell, cellIndex) in row" :key="cellIndex" :label="'Column ' + (cellIndex + 1)"> {{ cell }} </el-table-column> </template> </el-table-column> </el-table> </template> <script> export default { data() { return { tableData: [ ['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I'] ] } } } </script> ``` 这样就可以实现一个二维数组表格,每一行作为表头,每一列作为表格列,并显示相应的数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值