使用服务器端处理从DataTables导出全部数据?

12

我有一些表格在我的网站上使用DataTables的服务器端处理来显示。 我希望能够“导出全部”并且所有行都被导出,而不仅仅是显示的那些行。 由于有60000多行和65多列,因此必须使用服务器端处理来完成。

我已经尝试了一些方法,但到目前为止还没有什么效果。

我尝试过这个:

{ extend: 'excel',
    text: 'Export Current Page',
    exportOptions: {
        modifier: {
            page: 'current'
        }
    },
    customize: function (xlsx)
    {
        var sheet = xlsx.xl.worksheets['sheet1.xml'];
        $('row:first c', sheet).attr('s', '7');
    }
}

只导出显示在页面上的行。

我尝试过这个:

{
    text: 'Export All to Excel',
    action: function (e, dt, button, config)
    {
        dt.one('preXhr', function (e, s, data)
        {
            data.length = -1;
        }).one('draw', function (e, settings, json, xhr)
        {
            var excelButtonConfig = $.fn.DataTable.ext.buttons.excelHtml5;
            var addOptions = { exportOptions: { 'columns': ':all'} };

            $.extend(true, excelButtonConfig, addOptions);
            excelButtonConfig.action(e, dt, button, excelButtonConfig);
        }).draw();
    }
}
这将整个表的数据发送到屏幕,而不是使用分页并将整个数据集发送到Excel文件中。
我在Google和这里(指stackoverflow)搜索了一下,但没有找到可行的解决方案。
我也应该提到,我想基于表格上设置的当前过滤器导出所有内容。以便最终用户仅获得他们正在搜索的那些行的导出。它们通常将其限制为30k - 40k行,仍带有65+列。我(还)不允许删除/隐藏列。
编辑 / 更新:
这是一个次要的考虑因素:如果我无法从服务器响应中全部导出,则可以在服务器上构建Excel文件吗?我的服务器没有安装Excel,我仍然希望我的最终用户获得该文件。我相信我必须找到一种方法将Excel放入我的服务器中,但我如何将任何创建的文件传输到最终用户,并且是否比仅向用户计算机发送整个数据集的响应更快?
编辑:
建议我尝试jquery的“$.ajax()”使其工作。如果有人可以给我一个关于如何做到这一点的想法,我将尝试第三个按钮。
我已经可以提取所有数据,具有用户添加的相同过滤器和排序,可以通过按钮执行此操作。上面的第二次尝试确实是这样做的,但将其发送到屏幕。我有PHPExcel和一个可以创建Excel工作表的文件。如何将我在第二个按钮中获得的内容发送到其他文件以创建Excel工作表?我认为使用jquery的“$.ajax()”可能有效,只是我不知道如何实现。我确实知道我必须使用“$ _POST”,因为数据可能太大而无法使用“$ _GET”将数据发送到PHPExcel文件。
我已经可以导出CSV,但我需要带有一些格式的导出,CSV没有这个功能。这就是为什么我要费心使用PHPExcel的原因。
编辑III:
我正在尝试这个,虽然它还没有起作用:
{
    text: 'Export all to Excel II',
    action: function (e, dt, button, config)
    {
        dt.one('preXhr', function (e, s, data)
        {
            data.length = -1;
        }).one('export', function (e, settings, json, xhr)
        {
            var excelButtonConfig = $.fn.DataTable.ext.buttons.excelHtml5;
            var addOptions = { exportOptions: { 'columns': ':all'} };

            $.extend(true, excelButtonConfig, addOptions);
            excelButtonConfig.action(e, dt, button, excelButtonConfig);
        })
    }
}

编辑4

希望这是最后一次编辑。

我知道我需要做三件事才能使它工作:

  1. 获取当前的排序和筛选
  2. 获取设置为-1的数据集
  3. 将其发送到PHPExcel文件进行处理和创建Excel文件 我可以像这样创建一个按钮:

    { text: '将所有数据导出到Excel', action: }

我只是不知道操作需要是什么。

我上面的第二次尝试提取了我需要的整个数据集,但将其发送到屏幕而不是我的PHPExcel文件 (ExportAllToExcel.php)。

我一直在试图弄清楚这一点,但没有取得太大进展。有人告诉我需要使用 $.ajax() 来做到这一点,有人告诉我不需要使用。我已经尝试过有和没有,并没有进展。

我也尝试过使用以下方法但没有效果:

$.fn.dataTable.ext.buttons.export =
{
    className: 'buttons-alert',
    "text": "Export All Test",
    action: function (e, dt, node, config)
    {
        var SearchData = dt.search();
        var OrderData = dt.order();
        alert("Test Data for Searching: " + SearchData);
        alert("Test Data for Ordering: " + OrderData);
    }
};

在php.ini文件中增加您的内存以处理错误。为什么要这样做或以这种方式导出? - BetaDev
@ShaileshSingh 我以前尝试过那个方法。这不是可行的解决方案,因为没有任何量的内存可以让它工作。无论我设置多大的限制,它总是会达到限制。 - Mike
1
我不再遇到关于内存限制的错误了。我觉得它只是偶然出现的。 - Mike
不,你不能在客户端使用服务器端导出,因为你没有所有的行。在服务器上导出很容易,只需创建一个 CSV 文件并下载,Excel 可以打开它。 - Chris Caviness
@ChrisCaviness 我不能使用CVS,因为我需要在创建文件时添加格式,而CVS不允许这样做。此外,我能够在我上面的第二次尝试中获取所需的数据,只是我不知道如何将其发送到我拥有的文件中,以便使用PHPExcel创建Excel文件。 - Mike
只需使用这里的答案。 非常好用。https://dev59.com/rlwY5IYBdhLWcg3wYW1X#57335685 - Priyesh Doshi
4个回答

20

首先在 DataTable 中添加以下代码

"dom": 'Blfrtip',
                    "buttons": [
                        {
                            "extend": 'excel',
                            "text": '<button class="btn"><i class="fa fa-file-excel-o" style="color: green;"></i>  Excel</button>',
                            "titleAttr": 'Excel',
                            "action": newexportaction
                        },
                    ],

然后在$(document).ready()函数内添加此函数

function newexportaction(e, dt, button, config) {
         var self = this;
         var oldStart = dt.settings()[0]._iDisplayStart;
         dt.one('preXhr', function (e, s, data) {
             // Just this once, load all data from the server...
             data.start = 0;
             data.length = 2147483647;
             dt.one('preDraw', function (e, settings) {
                 // Call the original action function
                 if (button[0].className.indexOf('buttons-copy') >= 0) {
                     $.fn.dataTable.ext.buttons.copyHtml5.action.call(self, e, dt, button, config);
                 } else if (button[0].className.indexOf('buttons-excel') >= 0) {
                     $.fn.dataTable.ext.buttons.excelHtml5.available(dt, config) ?
                         $.fn.dataTable.ext.buttons.excelHtml5.action.call(self, e, dt, button, config) :
                         $.fn.dataTable.ext.buttons.excelFlash.action.call(self, e, dt, button, config);
                 } else if (button[0].className.indexOf('buttons-csv') >= 0) {
                     $.fn.dataTable.ext.buttons.csvHtml5.available(dt, config) ?
                         $.fn.dataTable.ext.buttons.csvHtml5.action.call(self, e, dt, button, config) :
                         $.fn.dataTable.ext.buttons.csvFlash.action.call(self, e, dt, button, config);
                 } else if (button[0].className.indexOf('buttons-pdf') >= 0) {
                     $.fn.dataTable.ext.buttons.pdfHtml5.available(dt, config) ?
                         $.fn.dataTable.ext.buttons.pdfHtml5.action.call(self, e, dt, button, config) :
                         $.fn.dataTable.ext.buttons.pdfFlash.action.call(self, e, dt, button, config);
                 } else if (button[0].className.indexOf('buttons-print') >= 0) {
                     $.fn.dataTable.ext.buttons.print.action(e, dt, button, config);
                 }
                 dt.one('preXhr', function (e, s, data) {
                     // DataTables thinks the first item displayed is index 0, but we're not drawing that.
                     // Set the property to what it was before exporting.
                     settings._iDisplayStart = oldStart;
                     data.start = oldStart;
                 });
                 // Reload the grid with the original page. Otherwise, API functions like table.cell(this) don't work properly.
                 setTimeout(dt.ajax.reload, 0);
                 // Prevent rendering of the full data to the DOM
                 return false;
             });
         });
         // Requery the server with the new one-time export settings
         dt.ajax.reload();
     }

这对我有用。你可以使用 data.length = -1 而不是 data.length = 2147483647; - tharanga-dinesh
它可以工作,但不能保证在表的大小增长或服务器资源意外减少时不会遇到 OOME。 - IdahoB

4
我遇到了这个问题,并想出了一种替代解决方案。
在DataTable选项中添加以下内容:
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]

这将允许用户选择所有行,并在“length”查询字符串参数中发送-1到服务器。在服务器端,您需要处理负数并允许在接收到-1时返回所有行。
这将显示表格中的所有行,并导出它们全部。
我知道对于50-60K行来说可能不太适用,但对于较小的数据集,无需在服务器端和客户端都实现任何额外的代码即可使用此功能。

1
你说得没错,这对于小数据集是可行的,但是这个特定的数据集可能超过100K行,因此将其打印到屏幕上需要>5分钟,这是不可接受的。不过我已经在菜单中有了这个选项,因为它允许用户选择他们想要看到的内容。当少于1K行时似乎效果很好,但是每行有近200列,所以它可能适用于行数较高但列数较低的较小数据集。这也没有回答我的问题,即从第一页开始打印所有内容而不需要显示所有内容。 - Mike
1
好的,Mike。我明白你的意思了。我只是发表了答案,以防万一有人在这里遇到相同的问题,但行列数较少。 - Prashant Gupta
最简单的解决方案 - mercury

3
我已经基本完成了这个任务。现在它因为数据量过大而超时,但这是另一个问题,与目前的工作无关。对于小数据集,它可以完美运行。
这是我创建按钮的方式(这里使用的是“导出”按钮):
"buttons": [{
                extend: 'collection',
                text: 'Selection',
                buttons: ['selectAll', 'selectNone']
            }, {
                extend: 'collection',
                text: 'Export',
                buttons: ['export', 'excel', 'csv', 'pdf', { extend: 'excel',
                    text: 'Export Current Page',
                    exportOptions: {
                        modifier: {
                            page: 'current'
                        }
                    },
                    customize: function (xlsx)
                    {
                        var sheet = xlsx.xl.worksheets['sheet1.xml'];
                        $('row:first c', sheet).attr('s', '7');
                    }
                }]
            }
            ]

这是上面创建的按钮的初始化:
```

这是上面创建的按钮的初始化:

```
$.fn.dataTable.ext.buttons.export =
{
    className: 'buttons-alert',
    id: 'ExportButton',
    text: "Export All Test III",
    action: function (e, dt, node, config)
    {
        var SearchData = dt.rows({ filter: 'applied' }).data();
        var SearchData1 = dt.search();
        console.log(SearchData);
        var OrderData = dt.order();
        console.log(SearchData1);
        var NumCol = SearchData[0].length;
        var NumRow = SearchData.length;
        var SearchData2 = [];
        for (j = 0; j < NumRow; j++)
        {
            var NewSearchData = SearchData[j];
            for (i = 0; i < NewSearchData.length; i++)
            {
                NewSearchData[i] = NewSearchData[i].replace("<div class='Scrollable'>", "");
                NewSearchData[i] = NewSearchData[i].replace("</div>", "");
            }
            SearchData2.push([NewSearchData]);
        }

        for (i = 0; i < SearchData2.length; i++)
        {
            for (j = 0; j < SearchData2[i].length; j++)
            {
                SearchData2[i][j] = SearchData2[i][j].join('::');
            }
        }
        SearchData2 = SearchData2.join("%%");
        window.location.href = './ServerSide.php?ExportToExcel=Yes';
    }
};

以下是 ServerSide.php 文件的一部分,用于获取数据并将其发送到服务器进行处理:

require('FilterSort.class.php');

if (isset($_GET['ExportToExcel']) && $_GET['ExportToExcel'] == 'Yes')
{
    $request = @unserialize($_COOKIE['KeepPost']);
    $DataReturn = json_encode(FilterSort::complex($request,$sqlConnect,$table,$primaryKey,$ColumnHeader));
    require './ExportAllToExcel.php';
}
else
{
    echo json_encode(FilterSort::complex($request,$sqlConnect,$table,$primaryKey,$ColumnHeader));
}

这是我设置用于保留搜索和排序条件的cookie的方法:
if(isset($_POST['draw']))
{
    $KeepPost = $_POST;    
    $KeepPost['length'] = -1;
    $PostKept = serialize($KeepPost);
    setcookie("KeepPost",$PostKept,time() + (60*60*24*7));
}

所有这些都结合在一起,将正确的标准发送到 FilterSort.class.php 中,该文件应处理标准并将数据集返回给 ExportAllToExcell.php,然后创建 Excel 文件。但现在我正在发送巨大的报告,它超时了。

更新

我稍微改变了做法:

这是新的按钮组:

"buttons": [{
    extend: 'collection',
    text: 'Export',
    buttons: ['export', { extend: 'csv',
        text: 'Export All To CSV',              //Export all to CSV file
        action: function (e, dt, node, config)
        {
            window.location.href = './ServerSide.php?ExportToCSV=Yes';
        }
    }, 'csv', 'pdf', { extend: 'excel',
        text: 'Export Current Page',            //Export to Excel only the current page and highlight the first row as headers
        exportOptions: {
            modifier: {
                page: 'current'
            }
        },
        customize: function (xlsx)
        {
            var sheet = xlsx.xl.worksheets['sheet1.xml'];
            $('row:first c', sheet).attr('s', '7');
        }
    }]
}
]

这里是我创建“导出全部到Excel”按钮的方法:
$.fn.dataTable.ext.buttons.export =
{
    className: 'buttons-alert',                         //Adds the "Export all to Excel" button
    id: 'ExportButton',
    text: "Export All To Excel",
    action: function (e, dt, node, config)
    {
        window.location.href = './ServerSide.php?ExportToExcel=Yes';
    }
};

现在它们将数据发送到我之前使用的同一个ServerSide.php文件:

require('FilterSort.class.php');
if (isset($_GET['ExportToExcel']) && $_GET['ExportToExcel'] == 'Yes')
{
    include 'Helper/LogReport.php';
    $GetSQL = "Select Value from PostKept where UserName = '" .$_COOKIE['UserName']. "'";
    $KeepResult = $conn->query($GetSQL);
    $KeepResults = $KeepResult->fetchALL(PDO::FETCH_ASSOC);

    $request = unserialize($KeepResults[0]['Value']);

    $DataReturn = json_encode(FilterSort::complex($request,$sqlConnect,$table,$primaryKey,$ColumnHeader,1));
    require './ExportAllToExcel.php';

我还改变了查询的方式,现在它也保存了表名用户名,就像这样:

include 'DBConn.php';
$KeepPost = $_POST;                                     //POST holds all the data for the search
$KeepPost['length'] = -1;                               //-1 means pulling the whole table
$PostKept = serialize($KeepPost);                       //This takes the array of data and turns it into a string for storage in SQL
$SQLCheck = "select distinct UserName from PostKept";   //Gets all the distinct Usernames of users that have used the Report Dashboard.
$sth = $conn->query($SQLCheck);
$CheckedUser = $sth->fetchALL(PDO::FETCH_ASSOC);
foreach($CheckedUser as $User)
{
    foreach($User as $Index => $Who)
    {
        $FoundUsers[] = $Who;                           //Taking all the found users and placing them into a simpler array for searching later

    }
}

if(isset($_COOKIE['UserName']) && in_array($_COOKIE['UserName'],$FoundUsers))   //If the user already has an entry update it with new information
{
    $TSQL = "UPDATE PostKept set Value = '" .$PostKept. "', TableName = '" .$TableName. "' where UserName = '" .$_COOKIE['UserName']. "'";
}
else
{
    if(isset($_COOKIE['UserName']))     //If this is a new user
    {
        $TSQL = "INSERT into PostKept(Value, TableName, UserName) select '" .$PostKept. "','" .$TableName. "','" .$_COOKIE['UserName']. "'";
    }
    else        //If this is on the Prod site and the User info is not yet kept
    {
        $TSQL = "INSERT into PostKept(Value, TableName) select '" .$PostKept. "','" .$TableName. "'";
    }
}

$sth = $conn->prepare($TSQL);
$sth->execute();

现在的情况是所有内容都被组合起来发送到我拥有的ExportAllToExcel.php文件中,然后这个文件会生成相应的文件。

1
在按钮中:
action: function (e, dt, node, config) {

var formData = 'yourfilters';
formData.begin = '0';
formData.length = 'yourTotalSize';

$http({
    url: 'yourURL',
    method: 'POST',
    data: JSON.stringify(formData)
}).then(function (ajaxReturnedData) {

    dt.rows.add(ajaxReturnedData.data).draw();
    $.fn.dataTable.ext.buttons.excelHtml5.action.call(this, e, dt, node, config);

});}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接