使用“AJAX”下载CSV文件

27

我正在尝试完成一个相对简单的任务,为我的网站,但我不确定如何去做。我希望用户可以查看一个表格,然后点击按钮,在此之后用户可以将该表格内容保存为CSV文件。由于这个请求有时会非常复杂,因此我生成了进度页面来提示用户。

除了实际生成CSV文件,我基本上已经想清楚了大部分事情。(我使用jQuery和PHP)

jQuery代码在点击时运行:

hmis_query_csv_export: function(query_name) {
    $.uiLock('<p>Query Loading.</p><img src="/images/loading.gif" />')
    $.get({
        url: '/php_scripts/utils/csv_export.php',
        data: {query_name: query_name},
        success: function(data) {
            $.uiUnlock();
        }
    });}

相关的 PHP 代码:

header("Content-type: text/x-csv");
header("Content-Disposition: attachment; filename=search_results.csv");
//
//Generate csv
//
echo $csvOutput
exit();

这个做法是将文本作为PHP文件发送,但它并不会生成下载文件。我做错了什么?

6个回答

48
如果你正在强制下载一个文件,你可以将当前页面重定向到下载链接。由于该链接会生成一个下载对话框,因此当前页面(及其状态)将保持不变。
基本方法:
$('a#query_name').click(function(){
    $('#wait-animation').show();
    document.location.href = '/php_scripts/utils/csv_export.php?query_name='+query_name;
    $('#wait-animation').hide();
});

更加复杂的情况:

$('a#query_name').click(function(){
    MyTimestamp = new Date().getTime(); // Meant to be global var
    $('#wait-animation').show();
    $.get('/php_scripts/utils/csv_export.php','timestamp='+MyTimestamp+'&query_name='query_name,function(){
        document.location.href = '/php_scripts/utils/csv_export.php?timestamp='+MyTimestamp+'&query_name='+query_name;
        $('#wait-animation').hide();
    });
});

在 PHP 脚本中:

@header("Last-Modified: " . @gmdate("D, d M Y H:i:s",$_GET['timestamp']) . " GMT");
@header("Content-type: text/x-csv");
// If the file is NOT requested via AJAX, force-download
if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') {
    header("Content-Disposition: attachment; filename=search_results.csv");
}
//
//Generate csv
//
echo $csvOutput
exit();

为了欺骗浏览器保存副本到缓存中,而不是在document.location.href开始新的下载,两个请求的URL必须相同。虽然我不完全确定,但这似乎非常可行。


非常感谢您的建议。我可以很容易地让它工作,但我想知道如何在实现屏幕锁定和进度动画的同时使用它。我只是担心用户可能会感到困惑,因为根据查询,这个过程可能需要3到4分钟。 - The_Denominater
1
如果创建CSV文件需要那么长时间,你可能需要稍微改变方向,改用AJAX来启动一个创建临时磁盘文件的PHP脚本;一旦PHP脚本返回(带有文件名),AJAX回调函数就可以像Ast Derek所示进行重定向。 - godheadatomic
你的编辑看起来应该可以工作,但似乎语句是异步运行的。第三个语句不等待下载完成,因此等待屏幕会立即出现然后消失。有没有办法强制它同步运行? - The_Denominater
1
进度对话框很棘手。我在这里遇到了类似的问题:https://dev59.com/2XNA5IYBdhLWcg3wEZaT 但从未真正解决它。 - JW.
“等待”动画/对话框是为了让用户等待下载开始而设计的,而不是等待下载完成。如果没有采用更复杂的方法,您将无法确定下载是否已完成。 - Ast Derek
显示剩余3条评论

5

编辑 我刚刚尝试了一个大小为10MB的文件,似乎val()太慢了,无法插入数据。唉。


好吧,我再试一次。这可能完全是疯狂的!想法是发出一个AJAX请求来创建数据,然后使用回调将数据插入到当前页面上具有第三个“下载”页面操作的隐藏表单中;插入后,表单会自动提交,下载页面发送标头并回显POST,完成下载。

同时,在原始页面上,您可以看到文件正在准备中,并且在完成时更新指示器。

注意:此测试代码未经过广泛测试,并且没有任何真正的安全检查(或者根本没有)。我使用一个大小为1.5MB的CSV文件进行了测试,速度还不错。

Index.html

<a id="downloadlink" href="#">Click Me</a>
<div id="wait"></div>
<form id="hiddenform" method="POST" action="download.php">
    <input type="hidden" id="filedata" name="data" value="">
</form>

test.js

$(document).ready(function(){
  $("#downloadlink").click(function(){       // click the link to download
      lock();                                // start indicator
      $.get("create.php",function(filedata){ // AJAX call returns with CSV file data
          $("#filedata").val(filedata);      // insert into the hidden form
          unlock();                          // update indicator
          $("#hiddenform").submit();         // submit the form data to the download page
      });
  });

  function lock(){
      $("#wait").text("Creating File...");
  }

  function unlock(){
      $("#wait").text("Done");
  }
});

create.php

<?php
//create $data
print $data;
?>

download.php

<?php
header("Pragma: public");
header("Expires: 0"); 
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Type: text/x-csv");
header("Content-Disposition: attachment;filename=\"search_results.csv\""); 

if($_POST['data']){
    print $_POST['data'];
}
?>

5
实现此目的的最佳方法是使用Data URI,具体如下:
  1. 按照正常流程向服务器发起AJAX调用
  2. 在服务器端生成CSV
  3. 返回数据(裸数据或包含在JSON结构中)
  4. 使用返回的数据在Javascript中创建Data URI
  5. 将window.location.href设置为Data URI
请参见此链接中的说明(特别是第3段):http://en.wikipedia.org/wiki/Data_URI_scheme 这样一来,您无需在服务器上保存任何文件,也不需要使用iframe或隐藏表单元素或其他任何黑客技巧。

1

我认为你不能使用 AJAX/JS 请求来让浏览器下载。尝试使用一个隐藏的 iframe,导航到生成 CSV 的页面。


0

使用AJAX的目的是避免页面的可见重新加载。如果您想要下载,您需要相反的操作-浏览器发出全新的请求。我建议,只需创建一个指向您的php页面的简单按钮。


好的,如果我这样做,我该如何将加载动画和UI锁集成进去呢?这不是使用AJAX的原因吗?另外,我对此还很陌生,不确定在将GET参数集成到页面请求时最佳的方法是什么。 - The_Denominater
@The_Denominator 使用一个 iframe。 - delete me

0

为了回应并扩展其他人所说的,你实际上不能使用AJAX发送文件。其中一个原因是(如果我在此方面有错请指出),当前页面已经发送了其内容头;即使使用AJAX请求(这就是您的PHP文件尝试执行的操作),也无法再次将它们发送到同一窗口。

在以前的项目中,我所做的是提供一个链接(带有target="_blank"或JavaScript重定向)到单独的下载PHP页面。如果您正在使用Apache,请查看mod_xsendfile。


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