我有一个javascript应用程序,发送ajax POST请求到某个URL。Response可以是JSON字符串,也可以是文件(作为附件)。我可以很容易地在ajax调用中检测到Content-Type和Content-Disposition,但是一旦我检测到响应包含一个文件,我如何让客户端下载它呢?我在这里读过一些类似的帖子,但没有一个能提供我想要的答案。

请,请,请不要发布建议我不应该使用ajax或我应该重定向浏览器的答案,因为这些都不是一个选项。使用普通的HTML表单也是不可行的。我所需要的是向客户端显示一个下载对话框。这能做到吗?如何做到?


当前回答

对于那些寻找更现代的方法的人,您可以使用fetch API。下面的代码展示了如何下载电子表格文件。

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

我相信这种方法比其他XMLHttpRequest解决方案更容易理解。此外,它具有与jQuery方法相似的语法,不需要添加任何额外的库。

当然,我建议检查一下你正在开发的浏览器,因为这种新方法在IE上行不通。您可以在以下[链接][1]上找到完整的浏览器兼容性列表。

重要提示:在本例中,我将发送一个JSON请求到侦听给定url的服务器。这个url必须设置,在我的例子中,我假设你知道这一部分。另外,考虑请求工作所需的头文件。因为我发送一个JSON,我必须添加内容类型头,并将其设置为application/ JSON;Charset =utf-8,以便让服务器知道它将接收的请求类型。

其他回答

对于那些寻找更现代的方法的人,您可以使用fetch API。下面的代码展示了如何下载电子表格文件。

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

我相信这种方法比其他XMLHttpRequest解决方案更容易理解。此外,它具有与jQuery方法相似的语法,不需要添加任何额外的库。

当然,我建议检查一下你正在开发的浏览器,因为这种新方法在IE上行不通。您可以在以下[链接][1]上找到完整的浏览器兼容性列表。

重要提示:在本例中,我将发送一个JSON请求到侦听给定url的服务器。这个url必须设置,在我的例子中,我假设你知道这一部分。另外,考虑请求工作所需的头文件。因为我发送一个JSON,我必须添加内容类型头,并将其设置为application/ JSON;Charset =utf-8,以便让服务器知道它将接收的请求类型。

我看到你已经找到了一个解决方案,但我只是想添加一些信息,这可能有助于有人试图实现与大POST请求相同的事情。

几周前我也遇到了同样的问题,确实不可能通过AJAX实现“干净”的下载,Filament Group创建了一个jQuery插件,它的工作方式与您已经发现的完全相同,它被称为jQuery文件下载,但这种技术有一个缺点。

If you're sending big requests through AJAX (say files +1MB) it will negatively impact responsiveness. In slow Internet connections you'll have to wait a lot until the request is sent and also wait for the file to download. It isn't like an instant "click" => "popup" => "download start". It's more like "click" => "wait until data is sent" => "wait for response" => "download start" which makes it appear the file double its size because you'll have to wait for the request to be sent through AJAX and get it back as a downloadable file.

如果您使用的是小于1MB的小文件,则不会注意到这一点。但正如我在自己的应用程序中发现的那样,对于更大的文件大小来说,这几乎是无法忍受的。

我的应用程序允许用户导出动态生成的图像,这些图像通过POST请求以base64格式发送到服务器(这是唯一可能的方式),然后处理并以.png, .jpg文件的形式发送回用户,图像的base64字符串+1MB是巨大的,这迫使用户等待超过必要的文件开始下载。在网速较慢的情况下,这真的很烦人。

我的解决方案是将文件临时写入服务器,一旦它准备好了,动态生成一个链接到文件的按钮形式,在“请等待…”和“下载”状态之间变化,同时,在预览弹出窗口中打印base64映像,以便用户可以“右键单击”并保存它。这使得所有的等待时间对用户来说更容易忍受,也加快了速度。

2014年9月30日更新:

自从我发布这篇文章以来,几个月过去了,最后我找到了一个更好的方法来加快使用大base64字符串的速度。我现在存储base64字符串到数据库(使用longtext或longblog字段),然后我通过jQuery文件下载它的记录ID,最后在下载脚本文件上,我查询数据库使用这个ID拉base64字符串并通过下载函数传递它。

下载脚本示例:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

我知道这远远超出了OP的要求,但是我觉得用我的发现更新我的答案会很好。当我在寻找我的问题的解决方案时,我读了很多“从AJAX POST数据下载”的帖子,这些帖子并没有给我我想要的答案,我希望这些信息能帮助那些想要实现这样的事情的人。

我需要一个类似的解决方案@alain-cruz的一个,但在nuxt/vue与多次下载。我知道浏览器阻止多个文件下载,我也有API返回一组csv格式的数据。我打算先使用JSZip,但我需要IE支持,所以这是我的解决方案。如果有人能帮助我改善这将是伟大的,但它为我工作到目前为止。

火了:

data : {
  body: {
    fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
    fileTwo: ""col1", "col2"..."
  }
}

page.vue:

<template>
  <b-link @click.prevent="handleFileExport">Export<b-link>
</template>

export default = {
   data() {
     return {
       fileNames: ['fileOne', 'fileTwo'],
     }
   },
  computed: {
    ...mapState({
       fileOne: (state) => state.exportFile.fileOne,
       fileTwo: (state) => state.exportFile.fileTwo,
    }),
  },
  method: {
    handleExport() {
      //exportFileAction in store/exportFile needs to return promise
      this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
        .then(async (response) => {
           const downloadPrep = this.fileNames.map(async (fileName) => {
           // using lodash to get computed data by the file name
           const currentData = await _.get(this, `${fileName}`);
           const currentFileName = fileName;
           return { currentData, currentFileName };
         });
         const response = await Promise.all(downloadPrep);
         return response;
       })
       .then(async (data) => {
         data.forEach(({ currentData, currentFileName }) => {
           this.forceFileDownload(currentData, currentFileName);
         });
       })
       .catch(console.error);
    },
    forceFileDownload(data, fileName) {
     const url = window.URL
         .createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
     const link = document.createElement('a');
     link.href = url;
     link.setAttribute('download', `${fileName}.csv`);
     document.body.appendChild(link);
     link.click();
   },
}

参见:http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ 它将返回一个blob作为响应,然后可以将其放入文件保存器

如果response是一个数组缓冲区,在Ajax的onsuccess事件下尝试这个:

 if (event.data instanceof ArrayBuffer) {
          var binary = '';
          var bytes = new Uint8Array(event.data);
          for (var i = 0; i < bytes.byteLength; i++) {
              binary += String.fromCharCode(bytes[i])
          }
          $("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
          return;
      }

事件的地方。数据是XHR事件成功函数接收到的响应。