在 element-ui 和 antv 中都有表格合并,但如何确定哪几行要合并呢? 在随机给定数据的情况下,如何实现自动合并呢?本文将一一解答这些问题。
并在延伸中,谈到了,如何将本文的方法应用到element-ui和antv中。
① 自动行合并
② 当两列为层级关系的时候,合并要有层级关系。
在举例之前,我们先规定一下展示数据。
展示的是不同 app 在不同手机型号下的下载量。
数据为:
- // 行信息
- const columns = [
- {
- key:'app',
- label:'app',
- },
- {
- key:'phone',
- label:'手机类型',
- },
- {
- key:'phoneType',
- label:'手机型号',
- },
- {
- key:'downloadCount',
- label:'下载量',
- },
- ];
-
- //数据信息
- const data = [
- {
- app:'微信',
- phone:'iphone',
- phoneType:'iphone11',
- downloadCount:'138,999,999'
- },
- {
- app:'微信',
- phone:'iphone',
- phoneType:'iphone12',
- downloadCount:'138,999,999'
- },
- {
- app:'微信',
- phone:'huawei',
- phoneType:'mate40',
- downloadCount:'138,999,999'
- },
- {
- app:'微信',
- phone:'huawei',
- phoneType:'mate40pro',
- downloadCount:'138,999,999'
- },
- {
- app:'抖音',
- phone:'huawei',
- phoneType:'mate40',
- downloadCount:'138,999,999'
- },
- {
- app:'抖音',
- phone:'iphone',
- phoneType:'iphone12',
- downloadCount:'138,999,999'
- },
- {
- app:'抖音',
- phone:'iphone',
- phoneType:'iphone11',
- downloadCount:'138,999,999'
- },
- ]
常规表格展现结果为:
APP | 手机类型 | 手机型号 | 下载量 |
---|---|---|---|
微信 | iphone | iphone11 | 138,999,999 |
微信 | iphone | iphone12 | 138,999,999 |
微信 | huawei | mate40 | 138,999,999 |
微信 | huawei | mate40pro | 138,999,999 |
抖音 | huawei | mate40 | 138,999,999 |
抖音 | iphone | iphone12 | 138,999,999 |
抖音 | iphone | iphone11 | 138,999,999 |
产品最终要的结果是:
这里要注意的是:
虽然手机类型都是华为,但如果是不同 app,也不能合并。
虽然手机型号都是 mate40 pro,也要依据 app 是否相同才能进行合并。
这里我们以原生html实现这种合并功能。
先来看看,html 实现表格的行合并的方式,代码如下。
- <table border="1">
- <tr>
- <th>First Nameth>
- <th>Bill Gatestd>
- tr>
- <tr>
- <td rowspan="2">Telephone:th>
- <td>555 77 854td>
- tr>
- <tr>
- <td style="display:none;">Telephone:th>
- <td>555 77 855td>
- tr>
- table>
效果图如下:
可以得知,原生html 是通过 在列上设置 rowspan 来实现行合并。rowspan 的 数值为几则合并几行。
但在实际需求中,表格的渲染一定是批量完成的。
以上述手机下载量数据为例,在vue中,渲染这个表格的代码为:
- <table>
- <tr>
- <td v-for="column in columns">
- {{column.label}}
- td>
- tr>
- <tr v-for="(item, index) in data">
- <td v-for="column in columns">
- {{item[column.key]}}
- td>
- tr>
- table>
由上述信息可知,为了实现行合并,我们需要知道,每一列数据中,
①开始合并的行,在开始合并的行添加 rowspan 和 数值
②结束合并的行,在开始合并行和结束合并行之间的所有行style 添加 display :none。
将上述三个需要计算的数值可以抽象为:
- /** 合并行 */
- interface MergeRow {
- /** 开始合并的行 */
- start: number;
-
- /** 开始合并的行 */
- end: number;
-
- /** 一共合并的行数: end - start + 1 */
- count: number;
- }
计算以上三个数值,可以抽象为: 在数组中,找到连续重复的数。
这是一个很简单的OJ问题,需要一个变量记录个数即可。 那么,把这个问题稍稍扩展一下,变成,找到数组中有几组连续重复的数,并记录开始重复,结束重复和重复数量。
那么解法就变成了如下代码:
- const {data, columns} = table;
- const newColumns = columns.map((column)=>{
- /** 当前列的key */
- const valKey = column.key;
-
- /** 当前列中需要 行合并的信息 */
- const merge:MergeRow[] = [];
-
- //第一行数据
- let lastVal = data[0][valKey];
- let valNum = 0;
- let startIndex = 0;
-
- data.forEach((item,index)=>{
- /** 当前行,当前列对应的数值 */
- const curValue = item[valKey];
-
- // 当前值和上一行的值相等,则计数+1
- if(curValue === lastVal) {
- valNum ++;
- }
-
- // 如果当前值和上一行的值不相等,并且计数大于1的话,则上几行存在相邻相等的数值,需要记录
- if(curValue !== lastVal) {
- merge.push({
- start:startIndex,
- end: index - 1,
- count: valNum,
- });
- // 如果当前值和上一行的值不相等,并且计数小于等于1. 则上一行没有数据需要记录,进行覆盖
- lastVal = curValue;
- valNum = 1;
- startIndex = index;
- }
-
- if(index === data.length -1) {
- merge.push({
- start:startIndex,
- end: index - 1,
- count: valNum,
- });
- }
-
-
- });
- column.merge = merge;
- return column;
- });
注意,在在上面的代码中,我把每一列中,存在的合并信息,存到了merge属性中。
这是为了渲染的时候可以读取这些信息准备的。
渲染的代码如下:
- <table>
- <tr>
- <td v-for="column in columns"> {{column.label}}td>
- tr>
- <tr v-for="(item,index) in data">
- <td v-for="column in columns"
- display="display:`${colum.merge ? colum.merge.find(m=>m.start == index) ? 'auto':'none':'auto'}`"
- rowspan="`${colum.merge && colum.merge.find(m=>m.start == index) ? colum.merge.find(m=>m.start == index).count : 1}`"
- >{{item[column.key]}}td>
- tr>
- table>
主要是添加了display 和 rowspan的逻辑。
display这里使用了两次三元运算符,
第一次,判断当前列的数据中是否存在merge ,如果不存在merge,则当前行正常渲染;
第二次,当存在merge的时候,判断是否是开始合并行,如果是,则正常渲染,不是则隐藏当前面行。
rowspan 只使用了一次三元判断,判断是否是开始合并行,如果是则读取count, 不是则为1.
看似我们已经实现了自动行合并,但,实际还有一个问题,上述方法,每一列的行合并是独立的。
再看这个图,手机类型这一列,有三行都是huawei,但是,不隶属于同一个app,所以不能合并。
为了解决这个问题,我们需要在计算合并的时候,打一个补丁:
判断一下,当这两行相等的时候,上一列的这两行是否也相等。
友情提示: 可看补丁部分。
- const newColumns = columns.map((column,colIndex)=>{
- /** 当前列的key */
- const valKey = column.key;
- // 补丁:上一列的 key
- const prevColKey = colIndex -1 >= 0 ? columns[colIndex - 1].key : valKey;
-
- /** 当前列中需要 行合并的信息 */
- const merge:MergeRow[] = [];
-
- let lastVal = data[0][valKey];
- // 补丁:上一列当前行的值
- let lastPrevColVal = data[0][prevColKey];
- let valNum = 0;
- let startIndex = 0;
-
- data.forEach((item,index)=>{
- /** 当前行,当前列对应的数值 */
- const curValue = item[valKey];
- const curPrevColValue = item[prevColKey];
-
- // 当前值和上一行的值相等,则计数+1
- if(curValue === lastVal) {
- // 补丁: 判断上一列是否相等
- if(lastPrevColVal === curPrevColValue) {
- valNum ++;
- } else {
- merge.push({
- start:startIndex,
- end: index - 1,
- count: valNum,
- });
- // 如果当前值和上一行的值不相等,并且计数小于等于1. 则上一行没有数据需要记录,进行覆盖
- lastVal = curValue;
- // 这里也要重新赋值
- lastPrevColVal = curPrevColValue;
- valNum = 1;
- startIndex = index;
- }
- }
-
- // console.log('this is index', curValue, lastVal, valNum);
- // 如果当前值和上一行的值不相等,并且计数大于1的话,则上几行存在相邻相等的数值,需要记录
- if(curValue !== lastVal) {
- merge.push({
- start:startIndex,
- end: index - 1,
- count: valNum,
- });
- // 如果当前值和上一行的值不相等,并且计数小于等于1. 则上一行没有数据需要记录,进行覆盖
- lastVal = curValue;
- // 这里也要重新赋值
- lastPrevColVal = curPrevColValue;
- valNum = 1;
- startIndex = index;
- }
-
- if(index === data.length -1) {
- merge.push({
- start:startIndex,
- end: index - 1,
- count: valNum,
- });
- }
-
-
- });
- merge.length && (column.merge = merge);
- return column;
- });
到此为止,也就实现了表格的自动级联行合并。
其实预计这是一篇很短就可以说清楚的问题。但没想到写了整整半天。
在写的过程中,遇到的第一个问题是:如何把问题界定清楚?
也就是文章的第一部分。 为什么这么难?因为在跟产品讨论时,不需要上下文,天然我们明白彼此的问题。但读者并不清楚需求上下文的,所以我需要站到一个产品的角度去描述这个需求的边界。
第二个问题是,如何告诉阐述思考的过程?
这也是一定要写这篇文章的原因,在工作的这一年里,我第一次把学生时代的内容进行了实战,发现了很多落地的实践。 但在现在的技术博客和当今的大学计算机教学中,都极少见到类似文章。所以,就想通过本文展示如何将具象问题抽象成一个教科书问题。
即是希望给看到这篇文章的学生们一点工程视野,也想听听各位大佬的看法,看我思考问题是否有所偏差,这个问题有没有更好的解决方法。
那么,针对这个问题,我最终确定了如下思路:
① 用demo 界定问题。
② 放置前置知识(table 行合并 和vue 渲染表格)
③ 拆解行合并问题,并进一步抽象细化成数组重复问题。
④ 解决数组重复问题
⑤ 向上包装,解决行合并。
⑥ 向上包装,解决级联行合并。
⑦ 实现 vue 渲染。
这个过程可以说是一个经典的洋葱式思考,层层抽丝剥茧,找到问题的根源。 然后,再一层层往上包装。
私以为,这种解决问题的方式和算法中的分治法异曲同工,其精髓都是问题细化,逐步击破。
这一部分稍稍谈一下,如何把计算得到的合并参数应用到element-ui中。
通过给
table
传入span-method
方法可以实现合并行或列,方法的参数是一个对象,里面包含当前行row
、当前列column
、当前行号rowIndex
、当前列号columnIndex
四个属性。该函数可以返回一个包含两个元素的数组,第一个元素代表rowspan
,第二个元素代表colspan
。 也可以返回一个键名为rowspan
和colspan
的对象。引用自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 寻找重复数