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

33

我正在使用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库创建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 obj <> endobj 4 0 obj <> stream x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W endstream endobj 1 0 obj < endobj 5 0 obj < endobj 2 0 obj << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI] /Font << /F1 5 0 R > /XObject << > > endobj 6 0 obj << /Producer (FPDF 1.7) /CreationDate (D:20150611094522) > endobj 7 0 obj << /Type /Catalog /Pages 1 0 R > endobj xref 0 8 0000000000 65535 f 0000000228 00000 n 0000000416 00000 n 0000000009 00000 n 0000000087 00000 n 0000000315 00000 n 0000000520 00000 n 0000000595 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R > startxref 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中检查“网络”时):(代码省略)

3
如果你的网络浏览器的设置中没有勾选“在下载前询问每个文件的保存位置”(Chrome)或类似的选项(Firefox中的“始终询问要保存文件的位置”),即使你将文件的头部设置为 application-downloadContent-Disposition: attachment ,浏览器也不会显示“另存为”对话框。文件将自动开始下载。在这种情况下,像 https://github.com/eligrey/FileSaver.js 这样的东西可能会有所帮助。 - Prayag Verma
如果您使用 header('Content-Type: application/octet-stream'); 而不是 application/pdf 会发生什么? - Darren
@Darren,我已经检查过了,这也会自动启动文件下载过程,而没有任何提示来保存它。 - Prayag Verma
你尝试过这个:this 或者 this 吗? - Denys Denysiuk
你为什么让赏金过期了?难道没有一个答案是有用的,或者...? - Ja͢ck
8个回答

6

更新

使用FileSaver.js的方法也不起作用,似乎没有办法仅通过JavaScript强制调用本地“另存为”对话框,唯一的例外是IE的saveAs execCommand。请查看Firefox中是否支持execCommand SaveAs?

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<10的支持有限,请查看https://github.com/eligrey/FileSaver.js#supported-browsers获取polyfills。
此外,我们需要删除Content-Disposition: attachmentContent-Type: application-download头,因为我们不希望浏览器本地处理下载过程。
这里有一个可工作的演示http://plnkr.co/edit/9r1tehQ94t5vkqZfZuQC?p=preview,它显示了对PDF的GET请求。

尝试了演示:Chrome中没有“另存为”选项,只能直接下载。 - mike.k
3
这个答案只是提到了什么不起作用,为什么它仍然获得如此多的赞成票? - Ja͢ck

3

让我给你提个建议。

仅通过 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文件呢?这不会让用户打开文档更加困难吗? - Ja͢ck

2

使用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");

这将导致您的文件被下载时,文件名为pdffile.pdf。

2
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


URL太长会影响大型PDF文档的操作,你测试过吗? - Ja͢ck

2
根据文档,您可以将第二个参数设置为D
$pdf->Output('Order123.pdf', 'D');

如果您想使用保存的文件自己处理所有细节,这就是我如何通过PHP向浏览器呈现PDF以供下载的方式:
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参数,但我的主题是错误的。但输出仍然与主题中所述相同。 - nielsv
在这种情况下,尝试使用F参数保存本地文件,然后按照我展示的方式将该文件传递给浏览器。最后,您可以使用unlink($filename);来移除它。 - mike.k
尝试使用 header('Content-Disposition: attachment; filename="' . basename($filename) . '"');,因为您不能使用完整文件夹路径的文件名。在此代码之前,您是否向浏览器发送任何标头? - mike.k
使用我在答案中提供的最后一组标头,Firefox 34和IE 11都会显示“另存为”选项,只有Chrome似乎没有这个选项并且会自动保存,也许这就是它的唯一行为方式,我已经看到其他人也遇到了这个问题。 - mike.k
刚刚看到 Chrome 在其设置中有一个选项,可以询问下载位置:chrome://settings/search#download - mike.k
显示剩余4条评论

1

仅使用 AJAX 不可能实现这一点。

这里有两种主要技术,都不需要使用 JavaScript 来执行实际的下载。总体思路是将浏览器重定向到提供所需文档的资源;如果您将重定向到下载,则不会离开页面,而是显示保存对话框。类似主题讨论在 this question 中。

GET 请求

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

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

POST请求

服务器创建资源并响应一个URL,该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响应。

0
我是按照以下方式实现的,对我来说效果很好:
$(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();
                    }
                });
            });
        });

My html:

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

-1
将文档发送到指定的目标:浏览器、文件或字符串。在浏览器的情况下,可以使用插件(如果存在)或强制下载(“另存为”对话框)。
如有必要终止文档,该方法首先调用Close()。 参数 name: 文件名。如果未指定,则将文档发送到浏览器(目标I),名称为doc.pdf。 dest: 要发送文档的目标位置。它可以取以下值之一:
  • I:通过插件(如果可用)向浏览器发送文件。当选择生成PDF链接时的“另存为”选项时,使用名称指定的名称。
  • D:发送到浏览器并强制使用名称给出的名称进行文件下载。
  • F:保存到本地文件中,文件名由名称给出(可能包括路径)。
  • S:将文档作为字符串返回。忽略名称。

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