会研究 Bootstrap-table 合并相同单元格是因为我负责了团队的「度量系统」项目,这个系统简单说就是将任意已经格式化的数据通过图表展现出来,数据的处理和图表的类型等均放开给用户自主配置,是否合并相同单元格是其中的一个小功能。
下面就开始分享我是如何实现合并相同单元格的,Bootstrap-table 官方提供了合并单元格方法 mergeCells,它根据四个参数可以合并任意个单元格,我们要做的只是告诉它怎么合并。
要合并同一列相同的单元格,无非两种办法,一种是一边遍历一边合并,另一种是遍历完了再合并。我采用的是第二种办法,需要注意的是这里不需要遍历后端传过来的所有数据,因为用户只能看到当前页面展示的数据,所以只遍历当前页的数据会更加节省时间。
下面是我实现的获取合并信息算法,最终返回的是一个哈希表,比如下面的这个表格,如果要对「性别」这一列进行合并,很明显前面两个“男”需要合并成一个单元格,再去看下 Bootstrap-table 提供的 API,它需要的是从哪个单元格开始,合并多少个单元格,也就是它需要的是两个数值类型的参数。
姓名 | 性别 | 年龄 |
---|---|---|
张三 | 男 | 23 |
李四 | 男 | 19 |
王二 | 女 | 20 |
麻子 | 男 | 21 |
分析之后我把如何合并的信息采用哈希表存储,键存的是索引,值存的是从这个索引开始后面连续有多少个和它一样的单元格,那么上述表格性别这一列所得到的合并信息哈希表就为:
{ 0: 2, 2: 1, 3: 1 }
下面是生成这个上述哈希表的算法,算法的逻辑非常简单,使用两个指针遍历指定的列,如果两个指针所指向的数据相同,那么就将键所对应的值进行加一操作,整个方法只会对该列数据遍历一边,所以时间复杂度为 O(n)。
let getMergeMap = function (data, index: number) { let preMergeMap = {}; // 第 0 项为表头,索引从 2 开始为了防止数组越界 for (let i = 2; i < data.length; i++) { let preText = $(data[i-1]).find('td')[index].innerText; let curText = $(data[i]).find('td')[index].innerText; let key = i - 2; preMergeMap[key] = 1; while ((preText == curText) && (i < data.length-1)) { preMergeMap[key] = parseInt(preMergeMap[key]) + 1; i++; preText = $(data[i - 1]).find('td')[index].innerText; curText = $(data[i]).find('td')[index].innerText; } // while循环跳出后,数组最后一项没有判断 if (preText == curText) { preMergeMap[key] = parseInt(preMergeMap[key]) + 1; } } return preMergeMap; }
通过上述算法得到了单列数据的合并信息,那么下一步就是根据这个信息进行相同单元格的合并了,因此我封装了下面的方法,根据指定哈希表进行相同单元格合并。
let mergeCells = function (preMergeMap: Object, target, fieldName: string) { for (let prop in preMergeMap) { let count = preMergeMap[prop]; target.bootstrapTable('mergeCells', { index: parseInt(prop), field: fieldName, rowspan: count }); } }
到目前为止,我们实现的都仅仅是对单列数据进行合并,如果要实现对多列数据进行合并,只需要对所有列都进行相同的操作即可。
export let mergeCellsByFields = function (data: Object[], target, fields) { for (let i = 0; i < fields.length; i++) { let field = fields[i]; // 保证 field 与 i 是相对应的 let preMergeMap = getMergeMap(data, i); let table = target.bootstrapTable(); mergeCells(preMergeMap, table, field); } }
因为我在程序中做了一点处理,保证了fields
中每个值得索引与对应表头的索引是一样的,因此不需要额外传入索引信息。简单来说就是我所实现的表格会根据fields
的顺序,实现列之间的动态排序。你需要注意的是这一点很可能和你不一样。
到现在已经能够合并所有的列了,查看 Bootstrap-table 的配置信息发现,它有个属性是 onPostBody 它会在 table body 加载完成是触发,所以把这个属性配置成我们的合并单元格方法即可。
// groups 为要合并的哪些列 onPostBody: function () { mergeCellsByFields($('#table' + ' tr'), $('#table'), groups); }
再说一点不太相关的,我实现的是让用户可以自己选可以合并多少列,即用了一个可多选的下拉列表框供用户选择,根据用户选择的数量去合并,所以传入了一个groups
参数。
最后推荐一个排序插件 thenBy,你可以用它进行多字段排序,比如用在合并相同单元格的场景,在绘制表格前先对数据进行排序,那么最后合并的结果就是把所有相同的数据聚合到一起了,并且还将它们合并到一起了,起到了一个隐形的过滤查询功能。