• 保姆级教程 | 表格自动行合并实现


    element-ui 和 antv 中都有表格合并,但如何确定哪几行要合并呢? 在随机给定数据的情况下,如何实现自动合并呢?本文将一一解答这些问题。

    并在延伸中,谈到了,如何将本文的方法应用到element-ui和antv中。

    一、需求描述及样例展示

    ① 自动行合并

    ② 当两列为层级关系的时候,合并要有层级关系。

    在举例之前,我们先规定一下展示数据。

    展示的是不同 app 在不同手机型号下的下载量。

    数据为:

    1. // 行信息
    2. const columns = [
    3. {
    4. key:'app',
    5. label:'app',
    6. },
    7. {
    8. key:'phone',
    9. label:'手机类型',
    10. },
    11. {
    12. key:'phoneType',
    13. label:'手机型号',
    14. },
    15. {
    16. key:'downloadCount',
    17. label:'下载量',
    18. },
    19. ];
    20. //数据信息
    21. const data = [
    22. {
    23. app:'微信',
    24. phone:'iphone',
    25. phoneType:'iphone11',
    26. downloadCount:'138,999,999'
    27. },
    28. {
    29. app:'微信',
    30. phone:'iphone',
    31. phoneType:'iphone12',
    32. downloadCount:'138,999,999'
    33. },
    34. {
    35. app:'微信',
    36. phone:'huawei',
    37. phoneType:'mate40',
    38. downloadCount:'138,999,999'
    39. },
    40. {
    41. app:'微信',
    42. phone:'huawei',
    43. phoneType:'mate40pro',
    44. downloadCount:'138,999,999'
    45. },
    46. {
    47. app:'抖音',
    48. phone:'huawei',
    49. phoneType:'mate40',
    50. downloadCount:'138,999,999'
    51. },
    52. {
    53. app:'抖音',
    54. phone:'iphone',
    55. phoneType:'iphone12',
    56. downloadCount:'138,999,999'
    57. },
    58. {
    59. app:'抖音',
    60. phone:'iphone',
    61. phoneType:'iphone11',
    62. downloadCount:'138,999,999'
    63. },
    64. ]

    常规表格展现结果为:

    APP 手机类型手机型号下载量
    微信 iphone iphone11138,999,999
    微信iphone iphone12138,999,999
    微信huaweimate40138,999,999
    微信huaweimate40pro138,999,999
    抖音huaweimate40138,999,999
    抖音iphoneiphone12138,999,999
    抖音iphoneiphone11

    138,999,999

    产品最终要的结果是:

    这里要注意的是:

    虽然手机类型都是华为,但如果是不同 app,也不能合并。

    虽然手机型号都是 mate40 pro,也要依据 app 是否相同才能进行合并。

    二、需求剖析

    2.1 合并功能原生实现

    这里我们以原生html实现这种合并功能。

    先来看看,html 实现表格的行合并的方式,代码如下。

    1. <table border="1">
    2. <tr>
    3. <th>First Nameth>
    4. <th>Bill Gatestd>
    5. tr>
    6. <tr>
    7. <td rowspan="2">Telephone:th>
    8. <td>555 77 854td>
    9. tr>
    10. <tr>
    11. <td style="display:none;">Telephone:th>
    12. <td>555 77 855td>
    13. tr>
    14. table>

    效果图如下:

     可以得知,原生html 是通过 在列上设置 rowspan 来实现行合并。rowspan 的 数值为几则合并几行。

    2.2 vue 实现表格渲染

    但在实际需求中,表格的渲染一定是批量完成的。

    以上述手机下载量数据为例,在vue中,渲染这个表格的代码为:

    1. <table>
    2. <tr>
    3. <td v-for="column in columns">
    4. {{column.label}}
    5. td>
    6. tr>
    7. <tr v-for="(item, index) in data">
    8. <td v-for="column in columns">
    9. {{item[column.key]}}
    10. td>
    11. tr>
    12. table>

    2.3 vue 实现表格行合并渲染

    由上述信息可知,为了实现行合并,我们需要知道,每一列数据中,

    ①开始合并的行,在开始合并的行添加 rowspan 和 数值

    ②结束合并的行,在开始合并行和结束合并行之间的所有行style 添加 display :none。

    将上述三个需要计算的数值可以抽象为:

    1. /** 合并行 */
    2. interface MergeRow {
    3. /** 开始合并的行 */
    4. start: number;
    5. /** 开始合并的行 */
    6. end: number;
    7. /** 一共合并的行数: end - start + 1 */
    8. count: number;
    9. }

    2.3.1 计算行合并

    计算以上三个数值,可以抽象为: 在数组中,找到连续重复的数。

    这是一个很简单的OJ问题,需要一个变量记录个数即可。 那么,把这个问题稍稍扩展一下,变成,找到数组中有几组连续重复的数,并记录开始重复,结束重复和重复数量

    那么解法就变成了如下代码:

    1. const {data, columns} = table;
    2. const newColumns = columns.map((column)=>{
    3. /** 当前列的key */
    4. const valKey = column.key;
    5. /** 当前列中需要 行合并的信息 */
    6. const merge:MergeRow[] = [];
    7. //第一行数据
    8. let lastVal = data[0][valKey];
    9. let valNum = 0;
    10. let startIndex = 0;
    11. data.forEach((item,index)=>{
    12. /** 当前行,当前列对应的数值 */
    13. const curValue = item[valKey];
    14. // 当前值和上一行的值相等,则计数+1
    15. if(curValue === lastVal) {
    16. valNum ++;
    17. }
    18. // 如果当前值和上一行的值不相等,并且计数大于1的话,则上几行存在相邻相等的数值,需要记录
    19. if(curValue !== lastVal) {
    20. merge.push({
    21. start:startIndex,
    22. end: index - 1,
    23. count: valNum,
    24. });
    25. // 如果当前值和上一行的值不相等,并且计数小于等于1. 则上一行没有数据需要记录,进行覆盖
    26. lastVal = curValue;
    27. valNum = 1;
    28. startIndex = index;
    29. }
    30. if(index === data.length -1) {
    31. merge.push({
    32. start:startIndex,
    33. end: index - 1,
    34. count: valNum,
    35. });
    36. }
    37. });
    38. column.merge = merge;
    39. return column;
    40. });

    注意,在在上面的代码中,我把每一列中,存在的合并信息,存到了merge属性中。 

    这是为了渲染的时候可以读取这些信息准备的。

    2.3.2 vue 渲染行合并

    渲染的代码如下:

    1. <table>
    2. <tr>
    3. <td v-for="column in columns"> {{column.label}}td>
    4. tr>
    5. <tr v-for="(item,index) in data">
    6. <td v-for="column in columns"
    7. display="display:`${colum.merge ? colum.merge.find(m=>m.start == index) ? 'auto':'none':'auto'}`"
    8. rowspan="`${colum.merge && colum.merge.find(m=>m.start == index) ? colum.merge.find(m=>m.start == index).count : 1}`"
    9. >{{item[column.key]}}td>
    10. tr>
    11. table>

    主要是添加了display 和 rowspan的逻辑。

    display这里使用了两次三元运算符,

    第一次,判断当前列的数据中是否存在merge ,如果不存在merge,则当前行正常渲染;

    第二次,当存在merge的时候,判断是否是开始合并行,如果是,则正常渲染,不是则隐藏当前面行。

    rowspan  只使用了一次三元判断,判断是否是开始合并行,如果是则读取count, 不是则为1.

    2.3.3 级联渲染

    看似我们已经实现了自动行合并,但,实际还有一个问题,上述方法,每一列的行合并是独立的。

    再看这个图,手机类型这一列,有三行都是huawei,但是,不隶属于同一个app,所以不能合并。

    为了解决这个问题,我们需要在计算合并的时候,打一个补丁:

    判断一下,当这两行相等的时候,上一列的这两行是否也相等。 

    友情提示: 可看补丁部分。

    1. const newColumns = columns.map((column,colIndex)=>{
    2. /** 当前列的key */
    3. const valKey = column.key;
    4. // 补丁:上一列的 key
    5. const prevColKey = colIndex -1 >= 0 ? columns[colIndex - 1].key : valKey;
    6. /** 当前列中需要 行合并的信息 */
    7. const merge:MergeRow[] = [];
    8. let lastVal = data[0][valKey];
    9. // 补丁:上一列当前行的值
    10. let lastPrevColVal = data[0][prevColKey];
    11. let valNum = 0;
    12. let startIndex = 0;
    13. data.forEach((item,index)=>{
    14. /** 当前行,当前列对应的数值 */
    15. const curValue = item[valKey];
    16. const curPrevColValue = item[prevColKey];
    17. // 当前值和上一行的值相等,则计数+1
    18. if(curValue === lastVal) {
    19. // 补丁: 判断上一列是否相等
    20. if(lastPrevColVal === curPrevColValue) {
    21. valNum ++;
    22. } else {
    23. merge.push({
    24. start:startIndex,
    25. end: index - 1,
    26. count: valNum,
    27. });
    28. // 如果当前值和上一行的值不相等,并且计数小于等于1. 则上一行没有数据需要记录,进行覆盖
    29. lastVal = curValue;
    30. // 这里也要重新赋值
    31. lastPrevColVal = curPrevColValue;
    32. valNum = 1;
    33. startIndex = index;
    34. }
    35. }
    36. // console.log('this is index', curValue, lastVal, valNum);
    37. // 如果当前值和上一行的值不相等,并且计数大于1的话,则上几行存在相邻相等的数值,需要记录
    38. if(curValue !== lastVal) {
    39. merge.push({
    40. start:startIndex,
    41. end: index - 1,
    42. count: valNum,
    43. });
    44. // 如果当前值和上一行的值不相等,并且计数小于等于1. 则上一行没有数据需要记录,进行覆盖
    45. lastVal = curValue;
    46. // 这里也要重新赋值
    47. lastPrevColVal = curPrevColValue;
    48. valNum = 1;
    49. startIndex = index;
    50. }
    51. if(index === data.length -1) {
    52. merge.push({
    53. start:startIndex,
    54. end: index - 1,
    55. count: valNum,
    56. });
    57. }
    58. });
    59. merge.length && (column.merge = merge);
    60. return column;
    61. });

    三、总结

    到此为止,也就实现了表格的自动级联行合并。

    其实预计这是一篇很短就可以说清楚的问题。但没想到写了整整半天。

    在写的过程中,遇到的第一个问题是:如何把问题界定清楚?

    也就是文章的第一部分。 为什么这么难?因为在跟产品讨论时,不需要上下文,天然我们明白彼此的问题。但读者并不清楚需求上下文的,所以我需要站到一个产品的角度去描述这个需求的边界。

    第二个问题是,如何告诉阐述思考的过程?

    这也是一定要写这篇文章的原因,在工作的这一年里,我第一次把学生时代的内容进行了实战,发现了很多落地的实践。 但在现在的技术博客和当今的大学计算机教学中,都极少见到类似文章。所以,就想通过本文展示如何将具象问题抽象成一个教科书问题。

    即是希望给看到这篇文章的学生们一点工程视野,也想听听各位大佬的看法,看我思考问题是否有所偏差,这个问题有没有更好的解决方法。

    那么,针对这个问题,我最终确定了如下思路:

    ① 用demo 界定问题。

    ② 放置前置知识(table 行合并 和vue 渲染表格)

    ③ 拆解行合并问题,并进一步抽象细化成数组重复问题

    ④ 解决数组重复问题

    ⑤ 向上包装,解决行合并。

    ⑥ 向上包装,解决级联行合并。

    ⑦ 实现 vue 渲染。

    这个过程可以说是一个经典的洋葱式思考,层层抽丝剥茧,找到问题的根源。 然后,再一层层往上包装。

    私以为,这种解决问题的方式和算法中的分治法异曲同工,其精髓都是问题细化,逐步击破。

    四、延伸

    这一部分稍稍谈一下,如何把计算得到的合并参数应用到element-ui中。

    通过给table传入span-method方法可以实现合并行或列,方法的参数是一个对象,里面包含当前行row、当前列column、当前行号rowIndex、当前列号columnIndex四个属性。该函数可以返回一个包含两个元素的数组,第一个元素代表rowspan,第二个元素代表colspan。 也可以返回一个键名为rowspancolspan的对象。

    引用自element-ui官网:Element - The world's most popular Vue UI framework

    实际上,2.3.3 的代码中,item 就是当前行,column就是当前列。

    剩下的,相信你一定可以!

    那,为什么,我没有直接从element-ui的这个方法开始讲述呢?

    因为我的实际是服务端渲染,用的模板语言,只能用原生htm了,呜呜呜。

    五、参考资料

    剑指Offer面试题03-找出数组中重复的数字(5种方法)_JohnArchie的博客-CSDN博客_剑指offer 寻找重复数

    分治法_百度百科

  • 相关阅读:
    MySQL 安装详细步骤
    # 注解------01
    Java学习day06:面向对象基础,构造方法,成员/局部变量
    CAPL如何对以太网报文的长度字段和校验和字段设置错误值
    Web后端-请求响应
    帝国cms漏洞分析前台XSS漏洞
    10.第十部分 Scrapy框架
    动态修改日志级别,太有用了!
    VScode快捷键(win + mac)
    基于antd实现动态修改节点的Tree组件
  • 原文地址:https://blog.csdn.net/qq_34539486/article/details/126521864