使用ajax请求在浏览器中下载

Posted

技术标签:

【中文标题】使用ajax请求在浏览器中下载【英文标题】:Download in browser with ajax request 【发布时间】:2015-08-26 22:33:13 【问题描述】:

我正在使用 AngularJS,我正在尝试在 php 中生成 PDF。这就是我的 controller.js 中的内容:

$scope.downloadPDF = function()
    $http.post('/download-pdf',  fid: $routeParams.fid )
        .success(function(data, status, headers, config)
            console.log("success");
        )
        .error(function(data, status, headers, config)
            console.log("error");
        );
;

在我的 php 文件中,我有以下内容可以使用FPDF library 创建 PDF:

function download_pdf()

    $id = $_POST['fid'];

    $pdf = new FPDF();

    $pdf->AddPage();
    $pdf->SetFont('Arial','B',16);
    $pdf->Cell(40,10,'Hello World!');

    header('Content-Type: application/pdf');
    header('Content-Disposition: attachment; filename=' . $id . '.pdf');

    $pdf->Output('Order123.pdf', 'D');

但是请求是响应这个而不是打开保存对话框来保存我的 pdf。

%PDF-1.3 3 0 对象 结束对象 4 0 对象 溪流 x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W 端流 结束对象 1 0 对象 /X 对象 > 结束对象 6 0 对象 结束对象 7 0 对象 结束对象 外部参照 0 8 0000000000 65535 f 0000000228 00000 0000000416 00000 0000000009 00000 n 0000000087 00000 0000000315 00000 0000000520 00000 0000000595 00000 预告片 起始外部参照 644 %%EOF

我已经使用了PHPExcel 库,这很有效:

$objWriter = PHPExcel_IOFactory::createWriter($ea, 'Excel2007');

// We'll be outputting an excel file
header('Content-type: application/vnd.ms-excel');

// It will be called Submission on [date_now].xls
header('Content-Disposition: attachment; filename="' . $filename . '.xls' . '"');

// Write file to the browser
$objWriter->save('php://output');

现在我怎样才能使它适用于我的 PDF?

更新:

我已将我的代码编辑为:

$pdf = new FPDF();

$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');

$filename = DXS_VKGROUP_PLUGIN_LIB_DIR . 'uploads/' . $_POST['fid'] . '.pdf';

$pdf->Output($filename, 'F'); // Save file locally

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize($filename));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' . $filename . '"');

$handle = fopen($filename, 'rb');
fpassthru($handle);
fclose($handle);

文件保存在本地,但无法下载。它没有让我的对话框保存 pdf。我做错了什么?

更新 2

我现在尝试将application-download 更改为application/pdf。他将文件保存在本地,但我没有得到下载对话框。

响应看起来像这样(当我在 Chrome 中检查 Network 时):

%PDF-1.3 3 0 对象 结束对象 4 0 对象 溪流 x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W 端流 结束对象 1 0 对象 /X 对象 > 结束对象 6 0 对象 结束对象 7 0 对象 结束对象 外部参照 0 8 0000000000 65535 f 0000000228 00000 0000000416 00000 0000000009 00000 n 0000000087 00000 0000000315 00000 0000000520 00000 0000000595 00000 预告片 起始外部参照 644 %%EOF

【问题讨论】:

如果您的网络浏览器设置没有勾选“下载前询问每个文件的保存位置”(Chrome)选项或类似选项(Firefox - 始终询问保存文件的位置),那么浏览器将不会即使您将文件的标题设置为 application-downloadContent-Disposition: attachment ,也不会显示“另存为”对话框。该文件将自动开始下载。在这种情况下,github.com/eligrey/FileSaver.js 之类的东西可能会有所帮助 如果你使用header('Content-Type: application/octet-stream');而不是application/pdf会发生什么? @Darren 我检查了,这也导致文件下载过程自动启动而没有任何保存提示 您尝试了吗:this 或 this? 你为什么让赏金到期?没有一个答案有用,还是...? 【参考方案1】:

我是按以下方式完成的,它对我很有效:

$(document).ready(function () 
            $('#generaPdf').click(function (e) 
            e.preventDefault();
            var name = $('#name').val();
            $.ajax
                (
                    type: "POST",
                    url: "pdf.php",
                    data:  "name": name ,
                    success: function (data) 
                        alert("todo ok")
                        location.href = "pdf.php"
                        // $('.result').html(data);
                        // $('#contactform')[0].reset();
                    
                );
            );
        );

我的html:

<button type="submit" class="btn btn-primary" id="generaPdf"><i class="fas fa-link"></i> Generar documento para impresión</button>

【讨论】:

【参考方案2】:

让我建议你一件事。

仅通过 AJAX 您无法下载任何文件。 您需要先使用 ajax 调用创建 .zip 文件,然后获得该文件路径,然后您必须使用任何命令打开该文件。

这里有一个对我有用的例子:)

$http.post('www.abc.com/zipcreate', 'id': id).then(function (zipurl) 
                    $window.location = zipurl;
            );

当然,它有效。试一次。 :)

编辑:

或者你可以使用

$window.location = 'abc.com/link';

在“abc.com/link”中: 使用下面的代码:

  header("Content-type: application/octet-stream");
  header("Content-Disposition: attachment; filename=".$name);
  header("Pragma: no-cache");
  header("Expires: 0");
  readfile($file);
  exit;

您将在下载列表中获得您的文件。

【讨论】:

他们为什么需要创建一个 zip 文件?这不会让用户更难实际打开文档吗?【参考方案3】:

这对于(仅)AJAX 是不可能的。

您可以在此处使用两种主要技术,均不使用使用 javascript 来执行实际下载。一般的想法是您将浏览器重定向到将提供所需文档的资源;如果您要重定向到下载,它不会离开页面,而是显示“保存”对话框。 this question 中讨论了类似的主题。

GET 请求

location.href = '/download-pdf?fid=' + encodeURIComponent($routeParams.fid);

调整您的服务器端脚本以使用$_GET['fid']

POST 请求

服务器创建一个资源并返回一个可以找到该资源的 URL:

$http.post('/download-pdf',  fid: $routeParams.fid )
    .success(function(data, status, headers, config) 
        // follow location provided by server
        location.href = data.redirect_url;
        console.log("success");
    )
    .error(function(data, status, headers, config)
        console.log("error");
    );

调整您的服务器端脚本以在创建新资源后返回适当的 JSON 响应。

【讨论】:

【参考方案4】:

更新

使用 FileSaver.js 的方法也不起作用,似乎没有办法仅通过 JavaScript 强制调用本机另存为对话框,唯一的例外是 IE 的 saveAs execCommand。检查Does execCommand SaveAs work in Firefox?

在你的项目中包含FileSaver.js作为依赖项

修改downloadPDF函数如下

 $scope.downloadPDF = function() 
    $http.post('/download-pdf', 
        fid: $routeParams.fid
      , 
        responseType: 'arraybuffer'
      )
      .success(function(data, status, headers, config) 
        // Convert response data to Blob
        var file = new Blob([data], 
          type: 'application/pdf'
        );
        // Call the File Saver method to save the above Blob,this will show Save As dialog
        saveAs(file, "test.pdf");
      )
      .error(function(data, status, headers, config) 
        console.log("error");
      );

 ;

Blob 对象可以在大多数现代浏览器中工作,但对 IE https://github.com/eligrey/FileSaver.js#supported-browsers 以获取 polyfills

同时删除 Content-Disposition: attachmentContent-Type: application-download 标头,因为我们不希望浏览器原生处理下载过程

这是一个工作演示 http://plnkr.co/edit/9r1tehQ94t5vkqZfZuQC?p=preview,它显示了对 PDF 的 GET 请求

【讨论】:

试了demo:Chrome没有另存为,直接下载即可 这个答案只是提到了什么不起作用,为什么它仍然得到这么多的赞成票?【参考方案5】:

将文档发送到给定的目的地:浏览器、文件或字符串。在浏览器的情况下,可以使用插件(如果存在)或强制下载(“另存为”对话框)。

如果需要终止文档,该方法首先调用 Close()。

参数

姓名:

文件的名称。如果未指定,文档将被发送到名为 doc.pdf 的浏览器(目标 I)。

目的地

发送文档的目的地。它可以采用以下值之一:

我: 将文件内联发送到浏览器。如果使用该插件 可用的。选择“保存”时使用由名称给出的名称 as”选项在生成 PDF 的链接上。 D: 发送到浏览器并强制下载指定名称的文件 按名字。 F: 以 name 给出的名称保存到本地文件(可能 包括路径)。 S: 将文档作为字符串返回。名称被忽略。

【讨论】:

【参考方案6】:
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . basename($file_url) . "\"");

或使用 BLOB:

 $scope.downloadPDF = function() 
    $http.post('/download-pdf', 
        fid: $routeParams.fid
      , 
        responseType: 'arraybuffer'
      )
      .success(function(data, status, headers, config) 

        var file = new Blob([data], 
          type: 'application/pdf'
        );
        var url = URL.createObjectURL(blob);
        window.location.href = url;
      )
      .error(function(data, status, headers, config) 
        console.log("error");
      );

 ;

或者创建一个带有download属性的元素:

$scope.downloadPDF = function() 
    $http.get('http://46.101.139.188/demo.pdf', 
      , 
        responseType: 'arraybuffer'
      )
      .success(function(data, status, headers, config) 

        var file = new Blob([data], 
          type: 'application/pdf'
        );
        var url = URL.createObjectURL(file);
        var a = document.createElement('a');
        a.setAttribute('download', 'download');
        a.setAttribute('href', url);

         var event = new MouseEvent('click', 
          'view': window,
          'bubbles': true,
          'cancelable': true
        );
        a.dispatchEvent(event);
      )
      .error(function(data, status, headers, config) 
        console.log("error");
      );
 ;

plnkr demo

【讨论】:

对于大的 PDF 文档,URL 会不会太长?你测试了吗?【参考方案7】:

使用 ajax 是不可能的。 相反,您可以做的是通过 javascript 表单提交来执行您的操作。

例如,document.getElementById(formID).action= actionName

document.getElementById(formID).submit();  

这里的 formID 是您的表单 ID,actionName 将是您指定的 '/download-pdf'

在您的操作类中 - 设置响应标头

getResponse().setContentType("application/pdf");
getResponse().setHeader("Content-Disposition",  " attachment; filename= "+"pdffile.pdf");

这将导致您的文件以 filename = pdffile.pdf 下载。

【讨论】:

【参考方案8】:

根据documentation可以设置第二个参数为D

$pdf->Output('Order123.pdf', 'D');

如果您想使用保存的文件自己处理所有细节,这就是我将 PDF 呈现给浏览器以从 PHP 下载的方式:

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize('file.pdf'));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="file.pdf"');
$handle = fopen('file.pdf', 'rb');
fpassthru($handle);
fclose($handle);

更新:

请确认 PDF 文件确实正在制作中。 Web 服务器进程是否对该路径具有写入权限? FPDF 是否被正确包含?我在一个测试虚拟机上做了这个,它提供了一个文件1.pdf 供下载,它打开并包含“Hello World!”:

<?php

require('/var/www/html/fpdf17/fpdf.php');

$_POST = array('fid' => '1');

$pdf = new FPDF();

$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 16);
$pdf->Cell(40, 10, 'Hello World!');

$filename = '/tmp/' . $_POST['fid'] . '.pdf';

$pdf->Output($filename, 'F'); // Save file locally

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize($filename));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');

$handle = fopen($filename, 'rb');
fpassthru($handle);
fclose($handle);

unlink($filename);

?>

【讨论】:

我试过D参数,我的题目错了。但仍然输出与主题中所述相同的内容。 在这种情况下,尝试使用F 参数保存本地文件,然后按照我展示的方式将该文件传递给浏览器。最后您可以使用unlink($filename); 将其删除。 尝试header('Content-Disposition: attachment; filename="' . basename($filename) . '"');,因为您不能使用具有完整文件夹路径的文件名。您是否在该代码之前向浏览器发送任何标头? 还是一样的结果。不,我之前没有向浏览器发送任何标题.. 在 firefox/chrome 中尝试过 文件确实被保存了,我可以看到并打开它(当我没有取消链接时)。但是浏览器没有打开下载对话框... .

以上是关于使用ajax请求在浏览器中下载的主要内容,如果未能解决你的问题,请参考以下文章

Ajax请求导出Excel的问题

ajax响应时长怎样设置?

ajax为啥不能导出文件

node.js怎么模拟浏览器执行ajax

在 grails 控制器中识别 ajax 请求或浏览器请求

ajax请求请求数据缓存问题分析以及解决方案