解决DataTable合并的行跨页(合并行和分页冲突造成)显示不全问题
需求
最近在项目中有这样一个需求,需要将DataTable中的某些行合并,展示为单行对应多行格式:

技术栈是BootStrap3.x+DataTable1.x且不可随意变更(一分钱难倒英雄汉)
起手
刚开始做时前端隐藏行采用了博主@雨萱521 的思路
文章地址:https://www.cnblogs.com/yuxuan525/p/9088814.html
后端
在后端拼接对应的行的rowspan数值:
private List getRowDemos(List demos){//逆序Collections.reverse(demos);//循环的上一个idString previousId = "";//重复次数int index = 1;//是否重复且需要置rowspan为1boolean isRepeat = false;boolean isNotContinue = true;for (int i = 0; i < demos.size(); i++) {Demo demo = demos.get(i);if (demo.getId().equals(previousId)) {index++;demo.setRowspan(index);}else {previousId = demo.getId();index = 1;}}//逆序Collections.reverse(demos);for (int i = 0; i < demos.size(); i++) {Demo demo = demos.get(i);if (isNotContinue) {if (demo.getRowspan() != 1) {isRepeat = true;isNotContinue = false;continue;}}if (isRepeat) {if (demo.getRowspan() == 1) {demo.setRowspan(0);isRepeat = false;isNotContinue = true;continue;}demo.setRowspan(0);}}return demos;}
其中Demo类实例的默认rowspan是 1。
前端
DataTable部分TS使用columnDefs
"columnDefs": [{targets: [1,3,4,5,7,8,9],createdCell: function (td, cellData, rowData, row, col) {let rowspan = rowData.rowspan;if (rowspan > 1) {$(td).attr('rowspan', rowspan)$(td).attr('style', 'vertical-align:middle;text-align:center');}if (rowspan == 0) {//$(td).remove();$(td).attr('style', 'display:none;');}}}],
测试
完成测试发现有个很无语的问题,如果被合并的单元格是第10行和第11行,分页选项是每页10条数据,那么11行显示在下一页时只能显示未被隐藏的列:
如图 10条数据时:

如图 5条数据时:

可以看到5条数据的第二页没法正常显示了。
做到这里正好下班,苦恼一直没法解决这个问题,于是坛子发了个贴,走人了,至此也没找到合理的思路。
世界最大同性交友网站的技术方案
在DataTable官网,github,stackoverflow转了几圈,参考了几种插件,github找到了前端JS的一种解决思路。
废话不多说,上JS:
(function ($) {ShowedDataSelectorModifier = {order: 'current',page: 'current',search: 'applied',}GroupedColumnsOrderDir = 'asc';var RowspanPro = function (dt, columnsForGrouping) {this.table = dt.table();this.columnsForGrouping = columnsForGrouping;this.orderOverrideNow = false;this.mergeCellsNeeded = false;this.order = []var self = this;dt.on('order.dt', function (e, settings) {if (!self.orderOverrideNow) {self.orderOverrideNow = true;self._updateOrderAndDraw()} else {self.orderOverrideNow = false;}})dt.on('preDraw.dt', function (e, settings) {if (self.mergeCellsNeeded) {self.mergeCellsNeeded = false;self._mergeCells()}})dt.on('column-visibility.dt', function (e, settings) {self.mergeCellsNeeded = true;})this._updateOrderAndDraw();};RowspanPro.prototype = {_getOrderWithGroupColumns: function (order, groupedColumnsOrderDir) {if (groupedColumnsOrderDir === undefined)groupedColumnsOrderDir = GroupedColumnsOrderDirvar self = this;var groupedColumnsIndexes = this.columnsForGrouping.map(function (columnSelector) {return self.table.column(columnSelector).index()})var groupedColumnsKnownOrder = order.filter(function (columnOrder) {return groupedColumnsIndexes.indexOf(columnOrder[0]) >= 0})var nongroupedColumnsOrder = order.filter(function (columnOrder) {return groupedColumnsIndexes.indexOf(columnOrder[0]) < 0})var groupedColumnsKnownOrderIndexes = groupedColumnsKnownOrder.map(function (columnOrder) {return columnOrder[0]})var groupedColumnsOrder = groupedColumnsIndexes.map(function (iColumn) {var iInOrderIndexes = groupedColumnsKnownOrderIndexes.indexOf(iColumn)if (iInOrderIndexes >= 0)return [iColumn, groupedColumnsKnownOrder[iInOrderIndexes][1]]elsereturn [iColumn, groupedColumnsOrderDir]})groupedColumnsOrder.push.apply(groupedColumnsOrder, nongroupedColumnsOrder)return groupedColumnsOrder;},_getInjectedMonoSelectWorkaround: function (order) {if (order.length === 1) {// got mono order - workaround herevar orderingColumn = order[0][0]var previousOrder = this.order.map(function (val) {return val[0]})var iColumn = previousOrder.indexOf(orderingColumn);if (iColumn >= 0) {return [[orderingColumn, this._toogleDirection(this.order[iColumn][1])]]}}return order;},_mergeCells: function () {var columnsIndexes = this.table.columns(this.columnsForGrouping, ShowedDataSelectorModifier).indexes().toArray()var showedRowsCount = this.table.rows(ShowedDataSelectorModifier)[0].lengththis._mergeColumn(0, showedRowsCount - 1, columnsIndexes)},_mergeColumn: function (iStartRow, iFinishRow, columnsIndexes) {var columnsIndexesCopy = columnsIndexes.slice()currentColumn = columnsIndexesCopy.shift()currentColumn = this.table.column(currentColumn, ShowedDataSelectorModifier)var columnNodes = currentColumn.nodes()var columnValues = currentColumn.data()var newSequenceRow = iStartRow,iRow;for (iRow = iStartRow + 1; iRow <= iFinishRow; ++iRow) {if (columnValues[iRow] === columnValues[newSequenceRow]) {$(columnNodes[iRow]).hide()} else {$(columnNodes[newSequenceRow]).show()$(columnNodes[newSequenceRow]).attr('rowspan', (iRow - 1) - newSequenceRow + 1)if (columnsIndexesCopy.length > 0)this._mergeColumn(newSequenceRow, (iRow - 1), columnsIndexesCopy)newSequenceRow = iRow;}}$(columnNodes[newSequenceRow]).show()$(columnNodes[newSequenceRow]).attr('rowspan', (iRow - 1) - newSequenceRow + 1)if (columnsIndexesCopy.length > 0)this._mergeColumn(newSequenceRow, (iRow - 1), columnsIndexesCopy)},_toogleDirection: function (dir) {return dir == 'asc' ? 'desc' : 'asc';},_updateOrderAndDraw: function () {this.mergeCellsNeeded = true;var currentOrder = this.table.order();currentOrder = this._getInjectedMonoSelectWorkaround(currentOrder);this.order = this._getOrderWithGroupColumns(currentOrder)this.table.order($.extend(true, Array(), this.order))this.table.draw()},};$.fn.dataTable.RowspanPro = RowspanPro;$.fn.DataTable.RowspanPro = RowspanPro;$(document).on('init.dt', function (e, settings) {if (e.namespace !== 'dt') {return;}var api = new $.fn.dataTable.Api(settings);if (settings.oInit.RowspanPro ||$.fn.dataTable.defaults.RowspanPro) {options = settings.oInit.RowspanPro ?settings.oInit.RowspanPro :$.fn.dataTable.defaults.RowspanPro;new RowspanPro(api, options);}});}(jQuery));
由于这个JS太多了,所以抽离出来了,可以封装包压缩之后引入,也可以拿出来直接塞进主体JS中。
使用炒鸡简单:
let table = $('#example').DataTable({columns: [{data:'id',name:'id',title: 'id',},{data:'name',name:'name',title: 'name',}],data: data,RowspanPro: ['id:name','name:name']
});
用rowsGroup作为属性引入即可,如Demo所示,先对id进行分组,然后对name分组,这样id不同name相同的列就不会把name全部合在一起了。
但是这个js有两个致命缺陷,一是作者多年未更新,对高版本dt支持很不好,第二是参与合并的属性无法做隐藏处理。
个人思路:
后端分页,将分页的数据组装出rowspan然后传到前台,前台datatables 的 columnDefs来处理rowspan对应的行。
构建思路:每页N条数据,将需要合并的行数据第一条设置rowspan = 1,不需要合并的设置rowspan = 0
后端构建rowspan代码:
/*** 构建datatable合并行的标志* @param demos* @return*/private List getRowDemos(List demos) {Collections.reverse(demos);//循环的上一个idString previousId = "";//重复次数int index = 1;//是否重复且需要置rowspan为1boolean isRepeat = false;boolean isNotContinue = true;for (int i = 0; i < demos.size(); i++) {Demo Demo = demos.get(i);if (Demo.getId().equals(previousId)) {index++;Demo.setRowspan(index);} else {previousId = Demo.getId();index = 1;}}Collections.reverse(demos);for (int i = 0; i < demos.size(); i++) {Demo Demo = demos.get(i);if (isNotContinue) {if (Demo.getRowspan() != 1) {isRepeat = true;isNotContinue = false;continue;}}if (isRepeat) {if (Demo.getRowspan() == 1) {Demo.setRowspan(0);isRepeat = false;isNotContinue = true;continue;}Demo.setRowspan(0);}}return demos;}
前端:
private initBlockStoneTable() {let currentClass = this;currentClass.configMap.blockStoneTable = currentClass.jqueryMap.$blockStoneTable.DataTable({"dom": 'rt<"row"<"col-md-3"<"pull-left"i><"pull-left"l>><"col-md-3"<"pull-left">><"col-md-6"p>><"clear">',"sPaginationType": "full_numbers",//设置分页按钮"searching": true,"destroy": true,"reset": true,"serverSide": true,"ordering": false,"bAutoWidth": false,"lengthMenu": [10, 20, 50, 100],"ajax": {"url": currentClass.configMap.path + '/transfer/initQuarryStoneData',"type": "POST","contentType" : 'application/json; charset=utf-8',"data": function (d) {for(let key in d){if(key.indexOf("columns")==0||key.indexOf("order")==0||key.indexOf("search")==0){delete d[key];}}let searchParams= {companyName: currentClass.jqueryMap.$companyName.val(),beginTime: moment(currentClass.jqueryMap.$myContainer.find("#dtp_input_start").val() + " 00:00:00").valueOf(),endTime: moment(currentClass.jqueryMap.$myContainer.find("#dtp_input_end").val() + " 00:00:00").valueOf(),missionType:'stone',driverName: currentClass.jqueryMap.$driver_name.val(),isComplete: currentClass.configMap.is_complete_id,toCompanyName: currentClass.configMap.toCompanyName};//附加查询参数if(searchParams){$.extend(d,searchParams);}return JSON.stringify(d);},"dataType" : "JSON","dataFilter": function (json) {json = JSON.parse(json);let returnData = {draw:'',recordsTotal:'',recordsFiltered:'',data:{}};returnData.draw = json.draw;returnData.recordsTotal = json.total;returnData.recordsFiltered = json.total;returnData.data = json.list;currentClass.configMap.totalVol = json.totalVolume;currentClass.configMap.size = json.total;return JSON.stringify(returnData);}},"columns": [{name: 'number',data: 'number',width: 70},{name: 'companyName',data: 'company_name'},{name: 'q_companyName',data: 'q_company_name'},{name: 'driverName',data: 'driver_name'},{name: 'volume',data: 'volume'},{name: 'registerTime',data: 'register_time',render:function (data) {return moment(data).format('YYYY-MM-DD')}},{name: 'qIsComplete',data: 'qIsComplete',render: function (data) {if (data == 1) {return '已完成';} else {return '未完成';}}},{name: 'isComplete',data: 'isComplete',render: function (data) {return currentClass.configMap.viewBtn_html;}},{name: 'statBtn',"render": function (data, type, row) {return currentClass.configMap.statBtn_html;}},{data: 'id',name: 'id'}],"createdRow": function (row, data, index) {$(row).children('td').eq(9).attr('style', 'display:none');},"columnDefs": [{targets: [1,3,4,5,7,8,9],createdCell: function (td, cellData, rowData, row, col) {let rowspan = rowData.rowspan;if (rowspan > 1) {$(td).attr('rowspan', rowspan)$(td).attr('style', 'vertical-align:middle;text-align:center');}if (rowspan == 0) {$(td).attr('style', 'display:none;');}}}],"drawCallback": function (row) { // 数据加载完成后执行/*this.api().column(0).nodes().each(function (cell, i) {cell.innerHTML = i + 1;});*/let tootipContainer = $('[data-toggle="tooltip"]');let editContainer = $('[data-type="bs_edit"]');let viewContainer = $('[data-type="bs_view"]');let statContainer = $('[data-type="bs_stat"]');if (tootipContainer.length > 0) {tootipContainer.tooltip();}if (editContainer.length > 0) {editContainer.off('click').on('click', function () {currentClass.mission_edit(currentClass, $(this))});}if (viewContainer.length > 0) {viewContainer.off('click').on('click', function () {currentClass.mission_view(currentClass, $(this));});}if (statContainer.length > 0) {statContainer.off('click').on('click', function () {currentClass.stat_view(currentClass, $(this));});}currentClass.jqueryMap.$myContainer.find('#mission_table_wrapper').find('.pull-left').eq(2).html('总计:' + currentClass.configMap.size + '条' + ' ' + '合计运输量:' + currentClass.configMap.totalVol.toFixed(1) + 'm³');currentClass.jqueryMap.$myContainer.find('#mission_table_wrapper').find('.pull-left').eq(2).addClass('dataTables_info');}});
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
