解决DataTable合并的行跨页(合并行和分页冲突造成)显示不全问题,分页怎么合并


需求

最近在项目中有这样一个需求,需要将DataTable中的某些行合并,展示为单行对应多行格式:

技术栈是BootStrap3.x+DataTable1.x且不可随意变更(一分钱难倒英雄汉)

起手

刚开始做时前端隐藏行采用了博主@雨萱521 的思路

文章地址:https://www.cnblogs.com/yuxuan525/p/9088814.html

后端

在后端拼接对应的行的rowspan数值:

private List<Demo> getRowDemos(List<Demo> demos){
        //逆序
        Collections.reverse(demos);
        //循环的上一个id
        String previousId = "";
        //重复次数
        int index = 1;
        //是否重复且需要置rowspan为1
        boolean 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 = GroupedColumnsOrderDir

            var 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]]
                else
                    return [iColumn, groupedColumnsOrderDir]
            })

            groupedColumnsOrder.push.apply(groupedColumnsOrder, nongroupedColumnsOrder)
            return groupedColumnsOrder;
        },

        _getInjectedMonoSelectWorkaround: function (order) {
            if (order.length === 1) {
                // got mono order - workaround here
                var 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].length
            this._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<Demo> getRowDemos(List<Demo> demos) {
        Collections.reverse(demos);
        //循环的上一个id
        String previousId = "";
        //重复次数
        int index = 1;
        //是否重复且需要置rowspan为1
        boolean 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 '<span >未完成</span>';
                    }
                }
            },
            {
                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 + '条' + '&nbsp;&nbsp;&nbsp;&nbsp;' + '合计运输量:' + currentClass.configMap.totalVol.toFixed(1) + 'm³');
            currentClass.jqueryMap.$myContainer.find('#mission_table_wrapper').find('.pull-left').eq(2).addClass('dataTables_info');

        }
    });
}

 

 

 

 

 

 

 

 

 

 

 

 

文章最后发布于: 2019-10-24 19:15:16

相关内容

    暂无相关文章