Обработка загрузки файла с поста ajax

У меня есть приложение javascript, которое отправляет запросы POST ajax на определенный URL. Ответ может быть строкой JSON или файлом (в виде вложения). Я легко могу определить Content-Type и Content-Disposition в своем вызове ajax, но как только я обнаружу, что ответ содержит файл, как я могу предложить клиенту загрузить его? Я прочитал несколько похожих тем здесь, но ни одна из них не дает ответ, который я ищу.

Пожалуйста, пожалуйста, пожалуйста, не публикуйте ответы, предлагающие, чтобы я не использовал ajax для этого или что я должен перенаправить браузер, потому что ничего из этого не вариант. Использование простой формы HTML также не вариант. Мне нужно показать клиенту диалог загрузки. Можно ли это сделать и как?

РЕДАКТИРОВАТЬ:

По-видимому, это не может быть сделано, но есть простой обходной путь, как предполагает принятый ответ. Для тех, кто сталкивается с этой проблемой в будущем, вот как я решил ее:

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, request) {
        var disp = request.getResponseHeader('Content-Disposition');
        if (disp && disp.search('attachment') != -1) {
            var form = $('<form method="POST" action="' + url + '">');
            $.each(params, function(k, v) {
                form.append($('<input type="hidden" name="' + k +
                        '" value="' + v + '">'));
            });
            $('body').append(form);
            form.submit();
        }
    }
});

В общем, просто сгенерируйте HTML-форму с теми же параметрами, которые были использованы в AJAX-запросе, и отправьте ее.

22 ответа

Решение

Создайте форму, используйте метод POST, отправьте форму - нет необходимости в iframe. Когда страница сервера отвечает на запрос, напишите заголовок ответа для mime-типа файла, и он откроет диалоговое окно загрузки - я делал это несколько раз.

Вы хотите контент-приложение / загрузку - просто найдите, как обеспечить загрузку для любого языка, который вы используете.

Не сдавайтесь так быстро, потому что это можно сделать (в современных браузерах), используя части FileAPI:

Изменить 2017-09-28: Обновлен для использования конструктора файлов, когда он доступен, чтобы он работал в Safari> = 10.1.

Edit 2015-10-16: jQuery ajax не может правильно обрабатывать двоичные ответы (не может установить responseType), поэтому лучше использовать простой вызов XMLHttpRequest.

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        var filename = "";
        var disposition = xhr.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
        }
        var type = xhr.getResponseHeader('Content-Type');

        var blob = typeof File === 'function'
            ? new File([this.response], filename, { type: type })
            : new Blob([this.response], { type: type });
        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

Вот старая версия с использованием jQuery.ajax. Он может искажать двоичные данные, когда ответ преобразуется в строку некоторой кодировки.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});

Я столкнулся с той же проблемой и успешно решил ее. Мой сценарий использования это.

"Отправьте данные JSON на сервер и получите файл Excel. Этот файл Excel создается сервером и возвращается как ответ клиенту. Загрузите этот ответ как файл с пользовательским именем в браузере"

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

Выше приведенный фрагмент просто делает следующее

  • Отправка массива в виде JSON на сервер с использованием XMLHttpRequest.
  • После извлечения контента в виде двоичного объекта (двоичного файла) мы создаем загружаемый URL-адрес и прикрепляем его к невидимой ссылке "а", а затем нажимаем на нее.

Здесь нам нужно тщательно настроить несколько вещей на стороне сервера. Я установил несколько заголовков в Python Django HttpResponse. Вам необходимо установить их соответствующим образом, если вы используете другие языки программирования.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Так как я загружаю xls (excel) здесь, я настроил contentType выше одного. Вы должны установить его в соответствии с вашим типом файла. Вы можете использовать эту технику для загрузки любых файлов.

Какой серверный язык вы используете? В моем приложении я могу легко загрузить файл из вызова AJAX, установив правильные заголовки в ответе PHP:

Установка заголовков на стороне сервера

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

Фактически это "перенаправит" браузер на эту страницу загрузки, но, как сказал @ahren alread в своем комментарии, он не уйдет с текущей страницы.

Все дело в настройке правильных заголовков, поэтому я уверен, что вы найдете подходящее решение для используемого на стороне сервера языка, если это не PHP.

Обработка ответной стороны клиента

Предполагая, что вы уже знаете, как сделать вызов AJAX, на стороне клиента вы выполняете запрос AJAX к серверу. Затем сервер генерирует ссылку, откуда этот файл может быть загружен, например, "прямой" URL, на который вы хотите указать. Например, сервер отвечает:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

При обработке ответа вы вводите iframe в вашем теле и установить iframeВот SRC для URL, который вы только что получили, вот так (используя jQuery для простоты этого примера):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

Если вы установили правильные заголовки, как показано выше, iframe вызовет диалог загрузки без перехода браузера от текущей страницы.

Заметка

Дополнительное дополнение по вашему вопросу; Я думаю, что лучше всегда возвращать JSON при запросе материала с технологией AJAX. После получения ответа JSON вы можете решить, что с ним делать на стороне клиента. Может быть, например, позже вы захотите, чтобы пользователь щелкнул ссылку для загрузки на URL-адрес, вместо того, чтобы принудительно загружать его, в вашей текущей настройке вам придется обновить как клиентскую, так и серверную часть, чтобы сделать это.

Вот как у меня это работает /questions/7935890/download-and-open-pdf-file-using-ajax/7935914#7935914

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

Обновленный ответ с использованием download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});

Для тех, кто ищет решение с угловой точки зрения, это сработало для меня:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});

Для тех, кто ищет более современный подход, вы можете использовать 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, я должен добавитьContent-Type заголовок и установите для него application/json; charset=utf-8, чтобы сообщить серверу, какой тип запроса он получит.

Я вижу, вы уже нашли решение, однако я просто хотел добавить некоторую информацию, которая может помочь кому-то, пытающемуся достичь того же самого с большими запросами POST.

У меня была такая же проблема пару недель назад, действительно, невозможно выполнить "чистую" загрузку через AJAX, Filament Group создала плагин jQuery, который работает именно так, как вы уже узнали, он называется jQuery File Скачать, однако, есть и обратная сторона этой техники.

Если вы отправляете большие запросы через AJAX (скажем, файлы +1 МБ), это отрицательно скажется на скорости отклика. При медленном интернет-соединении вам придется много ждать, пока запрос не будет отправлен, а также дождаться загрузки файла. Это не похоже на мгновенный щелчок "=" "всплывающее окно" => "начало загрузки". Это больше похоже на "щелчок" => "ожидание отправки данных" => "ожидание ответа" => "запуск загрузки", при котором файл выглядит в два раза больше, потому что вам придется ждать отправки запроса через AJAX и получите его обратно в виде загружаемого файла.

Если вы работаете с файлами небольшого размера<1 МБ, вы этого не заметите. Но, как я обнаружил в моем собственном приложении, для файлов большего размера это практически невыносимо.

Мое приложение позволяет пользователям экспортировать изображения, сгенерированные динамически, эти изображения отправляются через POST-запросы в формате base64 на сервер (это единственный возможный способ), затем обрабатываются и отправляются обратно пользователям в виде файлов.png, .jpg, base64. строки для изображений +1 МБ огромны, что заставляет пользователей ждать больше, чем необходимо, чтобы файл начал загружаться. В медленных интернет-соединениях это может быть действительно раздражающим.

Мое решение для этого состояло в том, чтобы временно записать файл на сервер, как только он будет готов, динамически генерировать ссылку на файл в виде кнопки, которая изменяется между состояниями "Пожалуйста, подождите..." и "Загрузить" и одновременно распечатайте изображение base64 во всплывающем окне предварительного просмотра, чтобы пользователи могли "щелкнуть правой кнопкой мыши" и сохранить его. Это делает все время ожидания более терпимым для пользователей, а также ускоряет процесс.

Обновление 30 сентября 2014 г.:

С тех пор, как я это опубликовал, прошли месяцы, и, наконец, я нашел лучший подход для ускорения работы с большими строками base64. Теперь я сохраняю строки base64 в базе данных (используя поля longtext или longblog), затем я передаю его идентификатор записи через jQuery File Download, наконец, в файле сценария загрузки, я запрашиваю базу данных, используя этот идентификатор, чтобы извлечь строку 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;
?>

Я знаю, что это далеко за пределы того, о чем просил ФП, однако я чувствовал, что было бы неплохо обновить мой ответ своими выводами.Когда я искал решения для своей проблемы, я прочитал множество веток "Загрузка из данных AJAX POST", которые не дали мне ответа, который я искал, я надеюсь, что эта информация поможет кому-то, стремящемуся достичь чего-то подобного.

Вот мое решение с использованием временной скрытой формы.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

Обратите внимание, что я широко использую JQuery, но вы можете сделать то же самое с нативным JS.

Я хочу указать на некоторые трудности, которые возникают при использовании техники в принятом ответе, то есть при использовании формы сообщения:

  1. Вы не можете установить заголовки по запросу. Если ваша схема аутентификации включает заголовки, то есть Json-Web-Token, переданный в заголовке Authorization, вам нужно будет найти другой способ отправить его, например, в качестве параметра запроса.

  2. Вы не можете точно сказать, когда запрос закончился. Что ж, вы можете использовать cookie, который устанавливается в ответ, как это делает jquery.fileDownload, но это FAR от идеального. Он не будет работать для одновременных запросов и сломается, если ответ так и не поступит.

  3. Если сервер отвечает с ошибкой, пользователь будет перенаправлен на страницу ошибки.

  4. Вы можете использовать только те типы контента, которые поддерживаются формой. Это означает, что вы не можете использовать JSON.

В итоге я использовал метод сохранения файла на S3 и отправки предварительно подписанного URL-адреса, чтобы получить файл.

Как уже говорили другие, вы можете создать и отправить форму для загрузки через запрос POST. Однако вам не нужно делать это вручную.

Одна действительно простая библиотека для выполнения именно этого - jquery.redirect. Предоставляет API, похожий на стандартный jQuery.post метод:

$.redirect(url, [values, [method, [target]]])

Это вопрос на 3 года, но у меня была такая же проблема сегодня. Я посмотрел ваше отредактированное решение, но думаю, что оно может принести в жертву производительность, потому что оно должно сделать двойной запрос. Так что если кому-то нужно другое решение, которое не подразумевает повторного вызова службы, то я так и сделал:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

Эта форма просто используется для вызова службы и избегает использования window.location(). После этого вам просто нужно отправить форму из jquery, чтобы вызвать сервис и получить файл. Это довольно просто, но таким образом вы можете сделать загрузку с помощью POST. Теперь я понимаю, что это может быть проще, если услуга, которую вы вызываете, является GET, но это не мой случай.

Вот мое решение, собранное из разных источников: Реализация на стороне сервера:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

Реализация на стороне клиента (с использованием jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   

См.: http://www.henryalgus.com/reading-binary-files-using-jquery-ajax/ он вернет в качестве ответа большой двоичный объект, который затем может быть помещен в файловую заставку

Я использовал этот FileSaver.js. В моем случае с CSV-файлами я сделал это (в coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Я думаю, что для самого сложного случая данные должны быть обработаны должным образом. Под капотом FileSaver.js реализован тот же подход, что и в ответе Jonathan Amend.

Ниже приведено мое решение для загрузки нескольких файлов в зависимости от некоторого списка, который состоит из некоторых идентификаторов и поиска в базе данных, файлы будут определены и готовы к загрузке - если они существуют. Я вызываю действие C# MVC для каждого файла с помощью Ajax.

И да, как говорили другие, это можно сделать в jQuery Ajax. Я сделал это с успехом Ajax, и я всегда отправляю ответ 200.

Итак, это ключ:

  success: function (data, textStatus, xhr) {

И это мой код:

var i = 0;
var max = 0;
function DownloadMultipleFiles() {
            if ($(".dataTables_scrollBody>tr.selected").length > 0) {
                var list = [];
                showPreloader();
                $(".dataTables_scrollBody>tr.selected").each(function (e) {
                    var element = $(this);
                    var orderid = element.data("orderid");
                    var iscustom = element.data("iscustom");
                    var orderlineid = element.data("orderlineid");
                    var folderPath = "";
                    var fileName = "";

                    list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
                });
                i = 0;
                max = list.length;
                DownloadFile(list);
            }
        }

Затем звоню:

function DownloadFile(list) {
        $.ajax({
            url: '@Url.Action("OpenFile","OrderLines")',
            type: "post",
            data: list[i],
            xhrFields: {
                responseType: 'blob'
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("RequestVerificationToken",
                    $('input:hidden[name="__RequestVerificationToken"]').val());

            },
            success: function (data, textStatus, xhr) {
                // check for a filename
                var filename = "";
                var disposition = xhr.getResponseHeader('Content-Disposition');
                if (disposition && disposition.indexOf('attachment') !== -1) {
                    var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
                    var matches = filenameRegex.exec(disposition);
                    if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
                    var a = document.createElement('a');
                    var url = window.URL.createObjectURL(data);
                    a.href = url;
                    a.download = filename;
                    document.body.append(a);
                    a.click();
                    a.remove();
                    window.URL.revokeObjectURL(url);
                }
                else {
                    getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
                }
                i = i + 1;
                if (i < max) {
                    DownloadFile(list);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {

            },
            complete: function () {
                if(i===max)
                hidePreloader();
            }
        });
    }

С # MVC:

 [HttpPost]
 [ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
        {
            byte[] file = null;

            try
            {
                if (model != null)
                {
                    //code for getting file from api - part is missing here as not important for this example
                    file = apiHandler.Get<byte[]>(downloadApiUrl, token);

                    var contentDispositionHeader = new System.Net.Mime.ContentDisposition
                    {
                        Inline = true,
                        FileName = fileName
                    };
                    //    Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
                    Response.Headers.Add("Content-Type", "application/pdf");
                    Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
                    Response.Headers.Add("Content-Transfer-Encoding", "binary");
                    Response.Headers.Add("Content-Length", file.Length.ToString());

                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error getting pdf", null);
                return Ok();
            }

            return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
        }

Пока вы возвращаете ответ 200, успех в Ajax может работать с ним, вы можете проверить, существует ли файл на самом деле или нет, поскольку строка ниже в этом случае будет ложной, и вы можете сообщить об этом пользователю:

 if (disposition && disposition.indexOf('attachment') !== -1) {

Чтобы получить ответ Jonathan Amend на работу в Edge, я внес следующие изменения:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

к этому

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Я бы предпочел опубликовать это как комментарий, но у меня недостаточно репутации для этого

Я использовал решение Naren Yellavula и заставил его работать с небольшими изменениями в сценарии после того, как попробовал несколько других решений с использованием jquery. Но jquery не будет правильно загружать zip-файл. Я не могу разархивировать файл после загрузки. В моем случае использования мне нужно загрузить zip-файл, который распаковывается в сервлете, файлы обрабатываются и снова сжимаются, прежде чем zip-файл загружается клиенту. Это то, что вам нужно сделать на стороне клиента.

      This worked for me.

<script>
$(document).ready(function(){    
$(".download-button").click(function(){
var val1 = $(this).data("column1");
var val2 = $(this).data("column2");
        var val3 = $("#SearchString").val();
var val4 = $(this).closest("tr").find(".textbox").val();

$.ajax({
            url: '/TestDownload/DownloadFile',
            type: 'POST',
            data:{
                val1 : val1,
                val2: val2,
                val3 : val3,
                val4: val4

            },
            success: function (data, status, xhr) {
                var contentDisposition = xhr.getResponseHeader('content-disposition');
                var fileName = '';
                if (contentDisposition && contentDisposition.indexOf('attachment') != -1) {
                    var startIndex = contentDisposition.indexOf('filename=') + 9;
                    var endIndex = contentDisposition.indexOf(';', startIndex);
                    if (endIndex === -1) {
                        endIndex = contentDisposition.length;
                    }
                    fileName = contentDisposition.substring(startIndex, endIndex);
                }

                var blob = new Blob([data], { type: 'application/octet-stream' });
                var url = window.URL.createObjectURL(blob);
                var a = document.createElement('a');
                a.href = url;
                a.download = fileName;
                a.click();
                window.URL.revokeObjectURL(url);
                $("#errorMessage").text("File downloaded successfully.");


            },
            error: function(xhr, status, error){                    
                $("#errorMessage").text(xhr.responseText);
            }
            });
            });
            });
</script>

Мне нужно было решение, аналогичное решению @alain-cruz, но в nuxt/vue с несколькими загрузками. Я знаю, что браузеры блокируют загрузку нескольких файлов, и у меня также есть API, который возвращает набор данных в формате csv. Сначала я собирался использовать JSZip, но мне нужна была поддержка IE, так что вот мое решение. Если бы кто-нибудь мог помочь мне улучшить это, это было бы здорово, но пока это работает для меня.

API возвращает:

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();
   },
}

Если ответ является буфером массива, попробуйте это в событии onsuccess в Ajax:

 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;
      }
  • где event.data - ответ, полученный в функции успеха события xhr.

Есть другое решение для загрузки веб-страницы в ajax. Но я имею в виду страницу, которая должна быть сначала обработана, а затем загружена.

Для начала нужно отделить обработку страницы от загрузки результатов.

1) В вызове ajax выполняются только расчеты страницы.

$.post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"}, функция (данные, статус) 
       {
            if (status == "success") 
            {
                /* 2) В ответ загружается страница, которая использует предыдущие расчеты. Например, это может быть страница, которая печатает результаты таблицы, вычисленной в вызове ajax. */
                window.location.href = DownloadPage.php+"?ID="+29;
            }               
       });

// Например: в CalculusPage.php

    if (! Empty($_POST["calculusFunction"])) 
    {
        $ID = $_POST["ID"];

        $query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
        ...
    }

// Например: в DownloadPage.php

    $ID = $_GET["ID"];

    $sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
    ...

    $filename="Export_Data.xls"; заголовок ("Content-Type: application/vnd.ms-excel"); заголовок ("Content-Disposition: inline; filename=$filename");

    ...

Я надеюсь, что это решение может быть полезным для многих, как и для меня.

Другие вопросы по тегам