Xử lý tải tập tin từ bài ajax


393

Tôi có một ứng dụng javascript gửi các yêu cầu POST ajax đến một URL nhất định. Phản hồi có thể là một chuỗi JSON hoặc nó có thể là một tệp (dưới dạng tệp đính kèm). Tôi có thể dễ dàng phát hiện Loại nội dung và Xử lý nội dung trong cuộc gọi ajax của mình, nhưng một khi tôi phát hiện ra rằng phản hồi có chứa một tệp, làm cách nào để cung cấp cho khách hàng tải xuống? Tôi đã đọc một số chủ đề tương tự ở đây nhưng không ai trong số họ cung cấp câu trả lời tôi đang tìm kiếm.

Xin vui lòng, xin vui lòng, không đăng câu trả lời cho thấy rằng tôi không nên sử dụng ajax cho việc này hoặc tôi nên chuyển hướng trình duyệt, vì không ai trong số này là một tùy chọn. Sử dụng một hình thức HTML đơn giản cũng không phải là một lựa chọn. Những gì tôi cần là hiển thị một hộp thoại tải xuống cho khách hàng. Điều này có thể được thực hiện và làm thế nào?


Đối với những người đọc bài viết này, hãy đọc bài đăng này: stackoverflow.com/questions/20830309/ trên
sobhan

Tôi đã xóa giải pháp của bạn khỏi câu hỏi. Bạn được chào đón để đăng nó dưới dạng một bài trả lời dưới đây, nhưng nó không thuộc về bài đăng câu hỏi.
Martijn Pieters

Câu trả lời:


111

Tạo biểu mẫu, sử dụng phương thức POST, gửi biểu mẫu - không cần iframe. Khi trang máy chủ phản hồi yêu cầu, hãy viết tiêu đề phản hồi cho loại mime của tệp và nó sẽ hiển thị hộp thoại tải xuống - Tôi đã thực hiện việc này nhiều lần.

Bạn muốn loại nội dung của ứng dụng / tải xuống - chỉ cần tìm kiếm cách cung cấp tải xuống cho bất kỳ ngôn ngữ nào bạn đang sử dụng.


35
Như đã nêu trong câu hỏi: "Sử dụng một hình thức HTML đơn giản cũng không phải là một tùy chọn."
Pavle Dự đoán

13
Không, bởi vì sử dụng POST thông thường sẽ điều hướng trình duyệt đến URL POST. Tôi không muốn điều hướng khỏi trang. Tôi muốn thực hiện yêu cầu trong nền, xử lý phản hồi và trình bày nó cho khách hàng.
Pavle Dự đoán

6
Nếu máy chủ gửi lại các tiêu đề như câu trả lời khác, nó sẽ mở trong một cửa sổ mới - tôi đã làm điều đó trước đây. Nó sẽ chỉ điều hướng đi nếu tập lệnh phía máy chủ của bạn trả về mã HTML

1
@PavlePredic cuối cùng bạn đã tìm ra cách quản lý cả hai kịch bản phản hồi, tức là phản hồi văn bản JSON hoặc phản hồi tệp tải xuống?
Người dùng web

9
Câu trả lời không rõ ràng và giải pháp đề xuất không hoạt động.
stack247

531

Đừng từ bỏ quá nhanh, vì điều này có thể được thực hiện (trong các trình duyệt hiện đại) bằng cách sử dụng các phần của FileAPI:

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;
        if (typeof File === 'function') {
            try {
                blob = new File([this.response], filename, { type: type });
            } catch (e) { /* Edge */ }
        }
        if (typeof blob === 'undefined') {
            blob = 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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

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

Đây là phiên bản cũ sử dụng jQuery.ajax. Nó có thể mang dữ liệu nhị phân khi phản hồi được chuyển đổi thành một chuỗi ký tự.

$.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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

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

1
Cảm ơn rất nhiều ! Mặc dù vậy, tôi đã phải thêm 'Bố trí nội dung' cho cả 'Tiêu đề kiểm soát truy cập-tiếp cận-tiêu đề' và 'Kiểm soát truy cập-cho phép-tiêu đề' để phản hồi HTTP của tôi hoạt động.
JulienD

1
Nó không hoạt động khi tệp lớn hơn 500 mb, có lẽ chúng ta nên sử dụng một api khác?
hirra

Còn việc xóa phần tử khỏi DOM trong phần dọn dẹp (và không chỉ URL) thì sao? document.body.removeChild(a);
Scoregraphic

@hirra sử dụng responceType "blob" thay vì "Arraybuffer" và viết lại hàm gọi lại onload theo cách đó, var blob là this.response (var blob = this.response;) vì vậyvar blob =this.responce; /** if (typeof File === 'function') { try { blob = new File([this.response], filename, { type: type }); } catch (e) { /* Edge */ } } if (typeof blob === 'undefined') { blob = new Blob([this.response], { type: type }); } */
Chris Tobba

1
Đây là giải pháp hoàn hảo. Chỉ cần một thay đổi nhỏ. Trong Bản đánh máy tôi cần window.location.href = downloadUrlthay vìwindow.location = downloadUrl
michal.jakubeczy

39

Tôi đã đối mặt với cùng một vấn đề và giải quyết nó thành công. Trường hợp sử dụng của tôi là thế này.

" Đăng dữ liệu JSON lên máy chủ và nhận tệp excel. Tệp excel đó được tạo bởi máy chủ và trả về dưới dạng phản hồi cho máy khách. Tải xuống phản hồi đó dưới dạng tệp có tên tùy chỉnh trong trình duyệt "

$("#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));
});

Đoạn mã trên chỉ đang làm theo

  • Đăng một mảng dưới dạng JSON lên máy chủ bằng XMLHttpRequest.
  • Sau khi tìm nạp nội dung dưới dạng blob (nhị phân), chúng tôi đang tạo một URL có thể tải xuống và đính kèm nó vào liên kết "a" vô hình sau đó nhấp vào nó.

Ở đây chúng ta cần cẩn thận đặt vài thứ ở phía máy chủ. Tôi đặt vài tiêu đề trong Python Django HttpResponse. Bạn cần đặt chúng cho phù hợp nếu bạn sử dụng các ngôn ngữ lập trình khác.

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

Vì tôi tải xuống xls (excel) tại đây, tôi đã điều chỉnh contentType thành trên. Bạn cần đặt nó theo loại tập tin của bạn. Bạn có thể sử dụng kỹ thuật này để tải xuống bất kỳ loại tập tin.


33

Bạn đang sử dụng ngôn ngữ phía máy chủ nào? Trong ứng dụng của mình, tôi có thể dễ dàng tải xuống một tệp từ một cuộc gọi AJAX bằng cách đặt các tiêu đề chính xác trong phản hồi của PHP:

Đặt tiêu đề phía máy chủ

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;

Trên thực tế, điều này sẽ 'chuyển hướng' trình duyệt đến trang tải xuống này, nhưng như @ahren alread đã nói trong bình luận của mình, nó sẽ không điều hướng khỏi trang hiện tại.

Đó là tất cả về việc đặt tiêu đề chính xác, vì vậy tôi chắc chắn bạn sẽ tìm thấy một giải pháp phù hợp cho ngôn ngữ phía máy chủ mà bạn đang sử dụng nếu không phải là PHP.

Xử lý phía khách hàng phản hồi

Giả sử bạn đã biết cách thực hiện cuộc gọi AJAX, về phía máy khách, bạn thực hiện yêu cầu AJAX đến máy chủ. Sau đó, máy chủ sẽ tạo một liên kết từ nơi tệp này có thể được tải xuống, ví dụ: URL 'chuyển tiếp' nơi bạn muốn trỏ đến. Ví dụ: máy chủ trả lời:

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

Khi xử lý phản hồi, bạn tiêm một phần iframetrong cơ thể và đặt iframeSRC của URL thành URL bạn vừa nhận được như thế này (sử dụng jQuery để dễ dàng cho ví dụ này):

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

Nếu bạn đã đặt các tiêu đề chính xác như được hiển thị ở trên, iframe sẽ buộc hộp thoại tải xuống mà không điều hướng trình duyệt ra khỏi trang hiện tại.

Ghi chú

Ngoài ra liên quan đến câu hỏi của bạn; Tôi nghĩ tốt nhất là luôn trả lại JSON khi yêu cầu công cụ với công nghệ AJAX. Sau khi bạn nhận được phản hồi JSON, bạn có thể quyết định phía khách hàng phải làm gì với nó. Ví dụ, có thể sau này bạn muốn người dùng nhấp vào liên kết tải xuống URL thay vì buộc tải trực tiếp, trong thiết lập hiện tại của bạn, bạn sẽ phải cập nhật cả phía máy khách và phía máy chủ để làm như vậy.


24

Đối với những người tìm kiếm một giải pháp từ góc độ Angular, điều này làm việc cho tôi:

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

Điều này đã giúp, nhưng tôi cần giữ nguyên tên tệp gốc. Tôi thấy tên tệp trong các tiêu đề phản hồi trong phần "Xử lý nội dung", nhưng tôi không thể tìm thấy giá trị đó trong đối tượng phản hồi trong mã. Đặt link.download = ""kết quả trong tên tệp hướng dẫn ngẫu nhiên và link.download = nullkết quả là tệp có tên "null".
Marie

@Marie, bạn có thể ghi lại tên tệp tại thời điểm tải lên bằng cách sử dụng thuộc tính của INPUTphần tử HTMLInputElement.files. Xem Tài liệu MDN trên đầu vào tệp để biết thêm chi tiết.
Tim Hettler

Kích thước blob bị giới hạn: stackoverflow.com/questions/28307789/ từ
user0800

22

Đây là cách tôi làm việc này https://stackoverflow.com/a/27563953/2845977

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

Cập nhật câu trả lời bằng download.js

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


Cảm ơn bạn vì điều này, tôi chỉ sử dụng ngày hôm nay. Tuyệt vời
Ryan Wilson

Xin chào, tôi có cần phải có jQuery 3.0> để nó hoạt động không?
gbade_

Tôi cũng nhận được một bản pdf trống với cả hai ví dụ bạn đã đưa ra. Tôi đang cố gắng để có được nó để tải về một tập tin pdf.
gbade_

@gbade_ Không, bạn không cần bất kỳ phiên bản jQuery cụ thể nào. Nên hoạt động tốt với tất cả các phiên bản. Bạn đã kiểm tra xem bản PDF bạn đang tải xuống có đúng tiêu đề CORS không? Bất kỳ lỗi nào trên bảng điều khiển có thể giúp gỡ lỗi
Mayur Padshala

Điều này làm việc cho tôi bằng cách sử dụng download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga

12

Tôi thấy bạn đã tìm ra một giải pháp, tuy nhiên tôi chỉ muốn thêm một số thông tin có thể giúp ai đó cố gắng đạt được điều tương tự với các yêu cầu POST lớn.

Tôi đã gặp vấn đề tương tự vài tuần trước, thực sự không thể đạt được tải xuống "sạch" thông qua AJAX, Nhóm Filament đã tạo một plugin jQuery hoạt động chính xác như cách bạn đã tìm ra, nó được gọi là Tệp jQuery Tải về tuy nhiên có một nhược điểm của kỹ thuật này.

Nếu bạn đang gửi các yêu cầu lớn thông qua AJAX (giả sử tệp + 1MB), nó sẽ tác động tiêu cực đến khả năng đáp ứng. Trong các kết nối Internet chậm, bạn sẽ phải chờ rất nhiều cho đến khi yêu cầu được gửi và cũng chờ tệp tải xuống. Nó không giống như một "nhấp chuột" => "cửa sổ bật lên" => "bắt đầu tải xuống". Giống như "nhấp chuột" => "đợi cho đến khi dữ liệu được gửi" => "chờ phản hồi" => "bắt đầu tải xuống" khiến tệp xuất hiện gấp đôi kích thước của nó vì bạn sẽ phải đợi yêu cầu được gửi thông qua AJAX và lấy lại dưới dạng tệp có thể tải xuống.

Nếu bạn đang làm việc với kích thước tệp nhỏ <1MB, bạn sẽ không nhận thấy điều này. Nhưng như tôi đã khám phá trong ứng dụng của riêng mình, đối với kích thước tệp lớn hơn, nó gần như không thể chịu đựng được.

Ứng dụng của tôi cho phép người dùng xuất hình ảnh được tạo động, những hình ảnh này được gửi qua các yêu cầu POST ở định dạng base64 đến máy chủ (đó là cách duy nhất có thể), sau đó xử lý và gửi lại cho người dùng dưới dạng tệp .png, .jpg, base64 chuỗi cho hình ảnh + 1MB là rất lớn, điều này buộc người dùng phải chờ nhiều hơn mức cần thiết để tệp bắt đầu tải xuống. Trong các kết nối Internet chậm, nó có thể thực sự gây phiền nhiễu.

Giải pháp của tôi cho việc này là tạm thời ghi tệp vào máy chủ, khi nó sẵn sàng, tự động tạo một liên kết đến tệp dưới dạng nút thay đổi giữa trạng thái "Vui lòng đợi ..." và "Tải xuống" và cùng một lúc thời gian, in hình ảnh base64 trong cửa sổ bật lên xem trước để người dùng có thể "nhấp chuột phải" và lưu nó. Điều này làm cho tất cả thời gian chờ đợi trở nên dễ chịu hơn đối với người dùng và cũng tăng tốc mọi thứ.

Cập nhật ngày 30 tháng 9 năm 2014:

Đã nhiều tháng trôi qua kể từ khi tôi đăng bài này, cuối cùng tôi đã tìm thấy một cách tiếp cận tốt hơn để tăng tốc mọi thứ khi làm việc với các chuỗi base64 lớn. Bây giờ tôi lưu trữ các chuỗi base64 vào cơ sở dữ liệu (sử dụng các trường longtext hoặc longblog), sau đó tôi chuyển ID bản ghi của nó thông qua Tải xuống tệp jQuery, cuối cùng trên tệp tập lệnh tải xuống, tôi truy vấn cơ sở dữ liệu bằng cách sử dụng ID này để kéo chuỗi base64 và chuyển qua chức năng tải xuống.

Tải xuống tập lệnh Ví dụ:

<?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;
?>

Tôi biết điều này vượt xa những gì OP yêu cầu, tuy nhiên tôi cảm thấy thật tốt khi cập nhật câu trả lời của mình với những phát hiện của mình. Khi tôi đang tìm kiếm giải pháp cho vấn đề của mình, tôi đã đọc rất nhiều chủ đề "Tải xuống từ dữ liệu AJAX POST" không cho tôi câu trả lời mà tôi đang tìm kiếm, tôi hy vọng thông tin này sẽ giúp ai đó tìm kiếm thứ gì đó như thế này.


Việc jQuery File Downloadduy nhất chuyển hướng tôi đến url. Tôi gọi nó như thế này : jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper

5

Tôi muốn chỉ ra một số khó khăn phát sinh khi sử dụng kỹ thuật trong câu trả lời được chấp nhận, tức là sử dụng một bài mẫu:

  1. Bạn không thể đặt tiêu đề theo yêu cầu. Nếu lược đồ xác thực của bạn liên quan đến các tiêu đề, Json-Web-Token được chuyển trong tiêu đề Ủy quyền, bạn sẽ phải tìm cách khác để gửi nó, ví dụ như một tham số truy vấn.

  2. Bạn thực sự không thể biết khi nào yêu cầu kết thúc. Chà, bạn có thể sử dụng một cookie được đặt theo phản hồi, như được thực hiện bởi jquery.fileD Download , nhưng đó là FAR hoàn hảo. Nó sẽ không hoạt động cho các yêu cầu đồng thời và nó sẽ bị hỏng nếu phản hồi không bao giờ đến.

  3. Nếu máy chủ phản hồi với lỗi, người dùng sẽ được chuyển hướng đến trang lỗi.

  4. Bạn chỉ có thể sử dụng các loại nội dung được hỗ trợ bởi một biểu mẫu . Điều đó có nghĩa là bạn không thể sử dụng JSON.

Tôi đã kết thúc bằng cách sử dụng phương pháp lưu tệp trên S3 và gửi URL đã ký trước để lấy tệp.


5

Đối với những người tìm kiếm một cách tiếp cận hiện đại hơn, bạn có thể sử dụng fetch API. Ví dụ sau đây cho thấy cách tải xuống tệp bảng tính. Nó dễ dàng được thực hiện với mã sau đây.

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

Tôi tin rằng phương pháp này dễ hiểu hơn nhiều so với các XMLHttpRequestgiải pháp khác . Ngoài ra, nó có một cú pháp tương tự nhưjQuery cách tiếp cận, mà không cần thêm bất kỳ thư viện bổ sung nào.

Tất nhiên, tôi sẽ khuyên bạn nên kiểm tra trình duyệt nào bạn đang phát triển, vì cách tiếp cận mới này sẽ không hoạt động trên IE. Bạn có thể tìm thấy danh sách tương thích trình duyệt đầy đủ trên [link] [1] sau đây.

Quan trọng : Trong ví dụ này, tôi đang gửi một yêu cầu JSON đến một máy chủ lắng nghe url. Điều này urlphải được đặt ra, trên ví dụ của tôi, tôi giả sử bạn biết phần này. Ngoài ra, hãy xem xét các tiêu đề cần thiết cho yêu cầu của bạn để làm việc. Vì tôi đang gửi JSON, tôi phải thêm Content-Typetiêu đề và đặt nó thành application/json; charset=utf-8, để cho máy chủ biết loại yêu cầu mà nó sẽ nhận được.


1
Tuyệt vời! Để mở cái này trong một tab mới thay vì cửa sổ bật lên tải xuống: hãy sử dụng `` `const window = open (downloadUrl," _blank "); if (window! == null) window.f Focus (); `` `
andy

Có cách nào để tôi làm điều này cho nhiều bộ dữ liệu không? Ví dụ, lấy lại {fileOne: data, fileTwo: data, fileThree: data} từ cuộc gọi api và tạo ba tệp được tải xuống cùng một lúc? Cảm ơn!
il0v3d0g

Hmmm, tôi đã không thử điều đó. Nhưng bạn luôn có thể nén hình ảnh trong một tệp zip và tải xuống. Tôi sẽ kiểm tra nếu có thể.
Alain Cruz

4

Đây là giải pháp của tôi bằng cách sử dụng một hình thức ẩn tạm thời.

//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();

Lưu ý rằng tôi sử dụng ồ ạt JQuery nhưng bạn có thể làm tương tự với JS gốc.


3

Như những người khác đã nêu, bạn có thể tạo và gửi biểu mẫu để tải xuống thông qua yêu cầu POST. Tuy nhiên, bạn không phải làm điều này bằng tay.

Một thư viện thực sự đơn giản để làm chính xác điều này là jquery.redirect . Nó cung cấp một API tương tự như jQuery.postphương thức tiêu chuẩn :

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

3

Đây là một câu hỏi 3 năm tuổi nhưng tôi đã có cùng một vấn đề ngày hôm nay. Tôi đã xem giải pháp chỉnh sửa của bạn nhưng tôi nghĩ rằng nó có thể hy sinh hiệu suất vì nó phải thực hiện một yêu cầu kép. Vì vậy, nếu bất cứ ai cần một giải pháp khác mà không ngụ ý gọi dịch vụ hai lần thì đây là cách tôi đã làm:

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

Biểu mẫu này chỉ được sử dụng để gọi dịch vụ và tránh sử dụng window.location (). Sau đó, bạn chỉ cần tạo một biểu mẫu gửi từ jquery để gọi dịch vụ và nhận tệp. Điều này khá đơn giản nhưng theo cách này bạn có thể thực hiện tải xuống bằng POST . Bây giờ tôi có thể dễ dàng hơn nếu dịch vụ bạn gọi là GET , nhưng đó không phải là trường hợp của tôi.


1
Đây không phải là một bài ajax vì câu hỏi đang sử dụng ajax
Nidhin David

nó chỉ là một giải pháp cho vấn đề trên, tuy nhiên không phải cho các cuộc gọi ajax.
Nomi Ali

1

Tôi đã sử dụng FileSaver.js này . Trong trường hợp của tôi với các tệp csv, tôi đã làm điều này (trong coffescript):

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

Tôi nghĩ đối với hầu hết các trường hợp phức tạp, dữ liệu phải được xử lý đúng. FileSaver.js thực hiện theo cách tiếp cận tương tự câu trả lời của Jonathan Amend .


1
..nhưng bạn có thể tải xuống các tệp thường trong iOS không?
Alex Marshall


1

Để có được câu trả lời của Jonathan Amends trong Edge tôi đã thực hiện các thay đổi sau:

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

đến đây

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

Tôi thà đăng bài này như một bình luận nhưng tôi không đủ danh tiếng cho điều đó


0

Đây là giải pháp của tôi, được thu thập từ các nguồn khác nhau: Triển khai phía máy chủ:

    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;
    }

Triển khai phía máy khách (sử dụng 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();
    }
});   

0

có một giải pháp khác để tải xuống một trang web trong ajax. Nhưng tôi đang đề cập đến một trang đầu tiên phải được xử lý và sau đó tải xuống.

Trước tiên, bạn cần tách việc xử lý trang khỏi tải xuống kết quả.

1) Chỉ các tính toán trang được thực hiện trong cuộc gọi ajax.

$ .post ("CompusPage.php", {tính toán: đúng, ID: 29, data1: "a", data2: "b"},

       chức năng (dữ liệu, trạng thái) 
       {
            if (status == "thành công") 
            {
                / * 2) Trong câu trả lời, trang sử dụng các tính toán trước đó được tải xuống. Ví dụ: đây có thể là một trang in kết quả của một bảng được tính trong lệnh gọi ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Ví dụ: trong CompusPage.php

    if (! trống ($ _ POST ["tính toán"])) 
    {
        $ ID = $ _POST ["ID"];

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

// Ví dụ: trong DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "CHỌN * TỪ Ví dụPage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    tiêu đề ("Loại nội dung: application / vnd.ms-excel");
    tiêu đề ("Xử lý nội dung: nội tuyến; tên tệp = $ tên tệp");

    ...

Tôi hy vọng giải pháp này có thể hữu ích cho nhiều người, vì nó là cho tôi.


0

Nếu phản hồi là Bộ đệm Mảng , hãy thử điều này trong sự kiện onsuccess trong 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;
      }
  • trong đó event.data là phản hồi nhận được trong chức năng thành công của sự kiện xhr.

0

Dưới đây là giải pháp của tôi để tải xuống nhiều tệp tùy thuộc vào một số danh sách bao gồm một số id và tìm kiếm trong cơ sở dữ liệu, các tệp sẽ được xác định và sẵn sàng để tải xuống - nếu chúng tồn tại. Tôi đang gọi hành động C # MVC cho mỗi tệp bằng Ajax.

Và có, như những người khác đã nói, có thể làm điều đó trong jQuery Ajax. Tôi đã làm điều đó với thành công của Ajax và tôi luôn gửi phản hồi 200.

Vì vậy, đây là chìa khóa:

  success: function (data, textStatus, xhr) {

Và đây là mã của tôi:

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

Sau đó gọi:

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

C # 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);
        }

Miễn là bạn trả về phản hồi 200, thành công trong Ajax có thể hoạt động với nó, bạn có thể kiểm tra xem tệp có thực sự tồn tại hay không vì dòng dưới đây trong trường hợp này là sai và bạn có thể thông báo cho người dùng về điều đó:

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

0

Tôi cần một giải pháp tương tự như của @ alain-cruz, nhưng trong nuxt / vue với nhiều lượt tải xuống. Tôi biết các trình duyệt chặn tải xuống nhiều tệp và tôi cũng có API trả về một tập hợp dữ liệu được định dạng csv. Lúc đầu tôi sẽ sử dụng JSZip nhưng tôi cần hỗ trợ IE vì vậy đây là giải pháp của tôi. Nếu bất cứ ai có thể giúp tôi cải thiện điều này sẽ rất tuyệt, nhưng nó vẫn hoạt động với tôi cho đến nay.

API trả về:

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

trang.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();
   },
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.