Tải xuống một tệp bởi jQuery.Ajax


420

Tôi có một hành động Struts2 ở phía máy chủ để tải xuống tệp.

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

Tuy nhiên khi tôi gọi hành động bằng jQuery:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

trong Fireorms tôi thấy dữ liệu được truy xuất với luồng nhị phân . Tôi tự hỏi làm thế nào để mở cửa sổ tải xuống tệp mà người dùng có thể lưu tệp cục bộ?



1
Tôi đã đánh dấu nó là một bản sao mặc dù có sự khác biệt về nền tảng, bởi vì theo như tôi có thể thấy giải pháp là như nhau (Bạn không thể và không cần phải làm điều này thông qua Ajax).
Pekka

1
vì vậy, không có ajax, chỉ cần sử dụng window.location = "download.action? para1 = value1 ...."?
hguser

Câu trả lời:


676

Cập nhật trình duyệt hiện đại 2019

Đây là cách tiếp cận mà tôi muốn giới thiệu với một vài cảnh báo:

  • Cần có một trình duyệt tương đối hiện đại.
  • Nếu tệp được dự kiến ​​là rất lớn, bạn có thể sẽ làm một cái gì đó tương tự như cách tiếp cận ban đầu (iframe và cookie) vì một số thao tác dưới đây có thể tiêu thụ bộ nhớ hệ thống ít nhất bằng với tệp được tải xuống và / hoặc CPU thú vị khác phản ứng phụ.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

Cách tiếp cận dựa trên jQuery / iframe / Cookie gốc 2012

Bluish hoàn toàn đúng về điều này, bạn không thể thực hiện điều đó thông qua Ajax vì JavaScript không thể lưu tệp trực tiếp vào máy tính của người dùng (vì lo ngại về bảo mật). Thật không may, việc trỏ URL của cửa sổ chính vào tệp tải xuống của bạn có nghĩa là bạn có ít quyền kiểm soát đối với trải nghiệm người dùng khi tải xuống tệp xảy ra.

Tôi đã tạo Tải xuống tệp jQuery cho phép trải nghiệm "giống như Ajax" với việc tải xuống tệp hoàn tất với các cuộc gọi lại OnSuccess và OnFailure để cung cấp trải nghiệm người dùng tốt hơn. Hãy xem bài đăng trên blog của tôi về vấn đề phổ biến mà plugin giải quyết và một số cách để sử dụng nó và cũng là bản demo của Tải xuống tệp jQuery trong thực tế . Đây là nguồn

Dưới đây là một bản demo trường hợp sử dụng đơn giản bằng cách sử dụng nguồn plugin với lời hứa. Các trang demo bao gồm nhiều người khác, 'tốt hơn UX' ví dụ là tốt.

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

Tùy thuộc vào trình duyệt nào bạn cần hỗ trợ, bạn có thể sử dụng https://github.com/eligrey/FileSaver.js/ cho phép kiểm soát rõ ràng hơn phương thức Tải xuống tệp jQuery của phương pháp IFRAME.


69
Tôi yêu những gì bạn đã xây dựng nhưng tôi nghi ngờ rằng để có thêm StackOverFlow, câu trả lời của bạn ở đây nên chứa nhiều chi tiết hơn. Cụ thể về cách bạn giải quyết vấn đề.
AnthonyVO

14
Sẽ thật tuyệt nếu bạn đề cập chính xác cách "plugin" này vượt qua giới hạn, thay vì buộc chúng tôi phải truy cập nguồn blog / plugin của bạn để xem nó. ví dụ, nó thay vì đăng lên iframe? thay vào đó, nó yêu cầu tập lệnh từ xa để lưu tệp và trả lại một url cho nó?
Kevin B

2
@asgerhallas Chắc chắn, nhưng điều đó hoàn toàn vô dụng nếu liên kết nói sẽ biến mất.
Kevin B

26
Tôi đồng ý, một blog là một nơi tốt hơn nhiều để đặt một mô tả dài về cách sử dụng plugin của bạn và cách nó hoạt động. nhưng ít nhất bạn có thể đưa ra một cái nhìn tổng quan ngắn về cách plugin này giải quyết vấn đề. Ví dụ: điều này giải quyết vấn đề bằng cách máy chủ đặt cookie và yêu cầu javascript của bạn liên tục tìm cookie cho đến khi nó tồn tại. Khi nó tồn tại, chúng ta có thể giả định rằng việc tải xuống đã hoàn tất. Với loại thông tin đó, người ta có thể dễ dàng tung ra giải pháp của riêng mình rất nhanh và câu trả lời không còn phụ thuộc 100% vào blog / plugin / jquery của bạn và có thể được áp dụng cho các thư viện khác.
Kevin B

1
Royi, theo tôi hiểu, AJAX không bao giờ có thể hỗ trợ tải xuống tệp dẫn đến cửa sổ bật lên tải xuống tệp để lưu vào đĩa. Bạn đã tìm thấy một cách mà tôi không biết?
John Culviner

227

Không ai đăng giải pháp này của @ Pekka ... vì vậy tôi sẽ đăng nó. Nó có thể giúp ai đó.

Bạn không cần phải làm điều này thông qua Ajax. Chỉ dùng

window.location="download.action?para1=value1...."

4
Rất vui ... vì tôi đang vật lộn với việc xử lý lời nhắc tải xuống tệp và sử dụng jquery ajax..và giải pháp này hoạt động hoàn hảo với tôi .. + 1
hoán đổi

45
Lưu ý rằng điều này yêu cầu máy chủ phải đặt giá trị tiêu đề Xử lý nội dung của 'tệp đính kèm', nếu không trình duyệt sẽ chuyển hướng đến (và hiển thị) nội dung phản hồi
brichins

21
Hoặc sử dụng thay thế window.open(<url>, '_blank');để đảm bảo rằng bản tải xuống sẽ không thay thế nội dung trình duyệt hiện tại của bạn (bất kể tiêu đề Bố trí nội dung).
Christopher King

4
Vấn đề với giải pháp này là nếu thao tác thất bại / máy chủ trả về lỗi, trang của bạn sẽ được chuyển hướng đến trang lỗi. Để giải quyết vấn đề đó, hãy sử dụng giải pháp iFrame
kofifus

4
Vấn đề thực sự với giải pháp này - câu hỏi là về POSTyêu cầu.
Atomosk

35

Bạn có thể với HTML5

Lưu ý: Dữ liệu tệp được trả về PHẢI được mã hóa base64 vì bạn không thể mã hóa dữ liệu nhị phân JSON

trong tôi AJAX phản hồi của tôi, tôi có một cấu trúc dữ liệu trông như thế này:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

Điều đó có nghĩa là tôi có thể thực hiện các thao tác sau để lưu tệp qua AJAX

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

Hàm base64ToBlob được lấy từ đây và phải được sử dụng để tuân thủ chức năng này

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

Điều này là tốt nếu máy chủ của bạn đang bán phá giá được lưu. Tuy nhiên, tôi đã không hoàn toàn tìm ra cách người ta sẽ thực hiện dự phòng HTML4


1
Cái a.click()này dường như không hoạt động trong firefox ... Có ý kiến ​​gì không?
bigpony

Trong một số trình duyệt, bạn có thể cần thêm adom vào để mã này hoạt động và / hoặc xóa revokeObjectURLphần:document.body.appendChild(a)
bigpony

đã cứu ngày của tôi (và có thể là một công việc nữa :)) Không phải là một chuyên gia javascript bằng bất kỳ biện pháp nào ... thêm anh chàng java. Tuy nhiên, tôi không biết tại sao một "createdObjectURL (Blob mới ([atob (base64)])") không hoạt động! Nó chỉ đơn giản là không, trong khi tất cả bản năng nói rằng nó phải.
grrr

tại dòng var bytechars = atob(base64)nó ném một lỗi JavaScript runtime error: InvalidCharacterError. Tôi đang sử dụng Chrome Phiên bản 75.0.3770.142 nhưng tôi không biết, có gì sai ở đây.
Muflix

27

1. Khung bất khả tri: Tải xuống tệp Servlet dưới dạng tệp đính kèm

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: Tệp tải xuống hành động dưới dạng tệp đính kèm

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

Sẽ tốt hơn nếu sử dụng <s:a>thẻ trỏ bằng OGNL đến một URL được tạo bằng <s:url>thẻ:

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

Trong các trường hợp trên, bạn cần viết tiêu đề Xử lý nội dung cho phản hồi , chỉ định rằng tệp cần được tải xuống ( attachment) và không được mở bởi trình duyệt ( inline). Bạn cũng cần chỉ định Loại Nội dung và bạn có thể muốn thêm tên và độ dài của tệp (để giúp trình duyệt vẽ một thanh tiến trình thực tế).

Ví dụ: khi tải xuống ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

Với Struts2 (trừ khi bạn đang sử dụng Action as a Servlet, ví dụ như hack để phát trực tiếp ), bạn không cần phải viết trực tiếp bất cứ điều gì vào phản hồi; chỉ cần sử dụng loại kết quả Luồng và định cấu hình nó trong struts.xml sẽ hoạt động: VÍ DỤ

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Khung bất khả tri (khung Struts2): Mở tệp Servlet (/ Action) bên trong trình duyệt

Nếu bạn muốn mở tệp bên trong trình duyệt, thay vì tải xuống tệp, bố trí Nội dung phải được đặt thành nội tuyến , nhưng mục tiêu không thể là vị trí cửa sổ hiện tại; bạn phải nhắm mục tiêu một cửa sổ mới được tạo bởi javascript, một <iframe>trong trang hoặc một cửa sổ mới được tạo khi đang di chuyển với mục tiêu "đã thảo luận" = "_ blank":

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>

2
Thưa ngài, đầu vào của bạn: "Bố trí nội dung", "nội tuyến; .... đã cứu ngày của người lập trình viên nghèo :)
Vedran Maricevic.

1
Đây là câu trả lời duy nhất đề cập đến "window.open" (một trong những ý kiến ​​đề cập đến nó).
Andrew Koster

Nó không hoạt động nếu bạn có nhiều tham số, bởi vì bạn sẽ gặp too long urllỗi.
Muflix

25

Cách đơn giản để làm cho trình duyệt tải xuống một tệp là thực hiện yêu cầu như thế:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }

Điều này sẽ mở trình duyệt tải lên.


3
Cảm ơn bạn, tôi đã sử dụng giải pháp này. Làm việc như người ở. Ngoài ra, nếu bạn không nhận được một blob từ phản hồi, chỉ cần tạo một Blob mới.
fabio.sang

6
Một phiên bản tốt hơn với liên kết
startedWith_R

Liên kết từ @startsWith_R thực sự hữu ích nếu bạn đang làm việc với IE11
alexventuraio

Cảm ơn nó đã làm việc cho tôi!
Zaki Mohammed

23

Tôi đã tạo ra ít chức năng như giải pháp khắc phục (lấy cảm hứng từ plugin @JohnCulviner):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Demo với sự kiện click:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});

Điều đó sẽ gửi dữ liệu theo một cách rất lạ đến máy chủ. Tôi tự hỏi nếu nó có thể được thay đổi để tạo POST tuân thủ?
Shayne

16

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ó. Tôi đã làm một yêu cầu POST ở đây. Thay vào đó, bạn có thể đi cho một GET đơn giản quá. Chúng tôi không thể tải xuống tệp thông qua Ajax, phải sử dụng XMLHttpRequest.

Ở đây chúng ta cần đặt cẩn thận mộ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.


"Chúng tôi không thể tải xuống tệp thông qua Ajax, phải sử dụng XMLHttpRequest". Theo định nghĩa, XMLHttpRequest là AJAX. Nếu không thì giải pháp tuyệt vời cho các trình duyệt web hiện đại. Đối với IE, không hỗ trợ HTMLAnchorElement.download, tôi nghĩ đến việc kết hợp nó với phương thức msSaveOrOpenBlob độc quyền .
Tsahi Asher

15

Ok, dựa trên mã của ndpu, đây là phiên bản cải tiến (tôi nghĩ) của ajax_doad; -

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Sử dụng như thế này; -

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

Các thông số được gửi dưới dạng thông số bài thích hợp như thể đến từ đầu vào chứ không phải là chuỗi được mã hóa json theo ví dụ trước.

CAVEAT: Hãy cảnh giác về khả năng tiêm biến trên các hình thức đó. Có thể có một cách an toàn hơn để mã hóa các biến đó. Hoặc suy ngẫm thoát khỏi chúng.


Đây là ví dụ làm việc. Cảm ơn. Có thể làm điều đó mà không có iframe nhưng không có window.location không?
Marek Bar

Tôi cho rằng bạn chỉ có thể nối biểu mẫu ẩn vào dưới cùng của DOM. Cũng có thể đáng để khám phá là sử dụng Shadow dom, mặc dù điều đó không nhất thiết được hỗ trợ tốt trên các trình duyệt cũ hơn.
Shayne

Trong mã này tôi nhận được lỗi này. Uncaught SecurityError: Blocked a frame with origin "http://foo.bar.com" from accessing a frame with origin "null". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
khoảng trống

Làm thế nào tôi có thể ánh xạ biểu mẫu này đến một số lớp mô hình? Tôi có: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) nhưng nó không hoạt động ..
bartex9

void: Đó có thể là một số vấn đề bảo mật nguồn gốc chéo. Đó có lẽ là một câu hỏi tràn toàn bộ trong và tự nó. @ bartex9: Điều đó sẽ phụ thuộc rất nhiều vào loại khung sử dụng của bạn. Nhưng nguyên tắc sẽ là lấy tên và đường dẫn và lưu trữ nó, trong khi đẩy chính tệp vào vùng truy cập web của hệ thống tệp hoặc một cái gì đó như amazon S3 để có tính sẵn sàng cao
Shayne

8

Đây là những gì tôi đã làm, thuần javascript và html. Không kiểm tra nó nhưng điều này sẽ hoạt động trong tất cả các trình duyệt.

Chức năng Javascript

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Chỉ sử dụng các thành phần được hỗ trợ trong tất cả các trình duyệt không có thư viện bổ sung.

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Đây là mã máy chủ JAVA Spring của máy chủ của tôi.

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }

có vẻ như sự kiện tải của bạn không được gọi cho nội dung đính kèm xử lý nội dung (vì không có gì được tải vào iframe), nếu nó hoạt động cho bạn (bạn nhận được console.log) vui lòng gửi mẫu
kofifus

Dưới đây là một fiddle nhanh jsfiddle.net/y2xezyoj điều này kích hoạt ass ass sự kiện tải ngay khi tệp pdf được tải vào iframe .. fiddle này không tải xuống vì khóa để tải xuống nằm ở phía máy chủ "answer.setHeader (" Nội dung -disposeition "," tập tin đính kèm ; filename = \ "" + fileName + ".xlsx \" ");"
manukyanv07

1
vâng, nó sẽ hoạt động trong trường hợp đó, nhưng nếu tệp được tải xuống, đó là máy chủ gửi Nội dung xử lý: tệp đính kèm, thì sự kiện tải sẽ không kích hoạt, đó là quan điểm của tôi
kofifus

Sự kiện tải hoàn toàn đúng của bạn được kích hoạt ngay sau khi máy chủ xử lý xong bắt đầu gửi tệp. Đây là những gì tôi đang tìm kiếm, 1- chặn nút và hiển thị xử lý để người dùng có thể có phản hồi rằng mọi thứ đang xảy ra. 2 - Sau đó, khi máy chủ xử lý xong và sắp gửi tệp 3- (sự kiện tải được kích hoạt) trong đó tôi mở khóa nút và xóa trình xử lý spinner 4 - người dùng hiện bật lên với tệp lưu hoặc trình duyệt bắt đầu tải xuống vị trí tải xuống được xác định. Xin lỗi tiếng Anh của tôi.
manukyanv07

5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

Bạn có thể giải thích câu trả lời của bạn? Điều đó sẽ giúp người khác hiểu những gì bạn đã làm để họ có thể áp dụng các kỹ thuật của bạn vào tình huống của họ.
Wai Ha Lee

2
Chỉ là một cảnh báo: Safari và IE không hỗ trợ downloadthuộc tính, vì vậy tệp của bạn sẽ có tên "Không xác định"
Yangshun Tay

4

Trong Rails, tôi làm theo cách này:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

Bí quyết là phần window.location . Phương thức của bộ điều khiển trông giống như:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end

2
Câu hỏi nhanh, điều này sẽ không tạo ra các tập tin hai lần? Một khi bạn gửi yêu cầu ajax. Sau đó, bạn thực hiện chuyển hướng trang đến cùng một URL. Làm thế nào chúng ta có thể loại bỏ điều đó?
coderhs

Không phải trong trường hợp của tôi. Tôi chỉ thử nghiệm nó trên Chrome.
aarkerio

Vì coderh đã nêu chính xác, hành động được gọi hai lần.
Sven

Nó cũng được gọi hai lần cho tôi.
CSquared

4

Sử dụng window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open

Ví dụ: bạn có thể đặt dòng mã này trong trình xử lý nhấp chuột:

window.open('/file.txt', '_blank');

Nó sẽ mở một tab mới (vì tên cửa sổ '_blank') và tab đó sẽ mở URL.

Mã phía máy chủ của bạn cũng nên có một cái gì đó như thế này:

res.set('Content-Disposition', 'attachment; filename=file.txt');

Và theo cách đó, trình duyệt sẽ nhắc người dùng lưu tệp vào đĩa, thay vì chỉ hiển thị cho họ tệp. Nó cũng sẽ tự động đóng tab mà nó vừa mở.


4

Tôi cố tải xuống một tệp CSV và sau đó làm một cái gì đó sau khi tải xuống xong. Vì vậy, tôi cần phải thực hiện một callbackchức năng thích hợp .

Sử dụng window.location="..."không phải là một ý tưởng tốt vì tôi không thể vận hành chương trình sau khi tải xong. Một cái gì đó như thế này, thay đổi tiêu đề để nó không phải là một ý tưởng tốt.

fetchlà một thay thế tốt tuy nhiên nó không thể hỗ trợ IE 11 . Và window.URL.createObjectURLkhông thể hỗ trợ IE 11. Bạn có thể tham khảo điều này .

Đây là mã của tôi, nó tương tự như mã của Shahrukh Alam. Nhưng bạn nên lưu ý rằng window.URL.createObjectURLcó thể tạo ra rò rỉ bộ nhớ. Bạn có thể tham khảo điều này . Khi có phản hồi, dữ liệu sẽ được lưu vào bộ nhớ của trình duyệt. Vì vậy, trước khi bạn nhấp vào aliên kết, tập tin đã được tải xuống. Nó có nghĩa là bạn có thể làm bất cứ điều gì sau khi tải về.

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);

    //Do something after download 
    ...

    }
}).then(after_download)
}

4

Cách TẢI XUỐNG một tệp sau khi nhận được bởi AJAX

Thật tiện lợi khi tệp được tạo trong một thời gian dài và bạn cần hiển thị PRELOADER

Ví dụ khi gửi biểu mẫu web:

<script>
$(function () {
    $('form').submit(function () {
        $('#loader').show();
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serialize(),
            dataType: 'binary',
            xhrFields: {
                'responseType': 'blob'
            },
            success: function(data, status, xhr) {
                $('#loader').hide();
                // if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
                //     var reader = new FileReader();
                //     reader.readAsText(data);
                //     reader.onload = function() {alert(reader.result);};
                //     return;
                // }
                var link = document.createElement('a'),
                    filename = 'file.xlsx';
                // if(xhr.getResponseHeader('Content-Disposition')){//filename 
                //     filename = xhr.getResponseHeader('Content-Disposition');
                //     filename=filename.match(/filename="(.*?)"/)[1];
                //     filename=decodeURIComponent(escape(filename));
                // }
                link.href = URL.createObjectURL(data);
                link.download = filename;
                link.click();
            }
        });
        return false;
    });
});
</script>

Chức năng tùy chọn được nhận xét để đơn giản hóa ví dụ.

Không cần phải tạo tập tin tạm thời trên máy chủ.

Trên jQuery v2.2.4 OK. Sẽ có một lỗi trên phiên bản cũ:

Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').

Để có được tên tệp từ Xử lý nội dung, trận đấu này hoạt động với tôi: filename.match(/filename=(.*)/)[1](không có dấu ngoặc kép hoặc dấu hỏi) - regex101.com/r/2AsD4y/2 . Tuy nhiên, giải pháp của bạn là giải pháp duy nhất hoạt động sau khi tìm kiếm rất nhiều.
jstuardo

3

Thêm một số điều nữa vào câu trả lời ở trên để tải xuống một tệp

Dưới đây là một số mã mùa xuân java tạo ra byte Array

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Bây giờ trong mã javascript bằng FileSaver.js, có thể tải xuống một tệp có mã bên dưới

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

Ở trên sẽ tải tập tin


2

Ok, đây là mã làm việc khi sử dụng MVC và bạn sẽ nhận được tệp của mình từ bộ điều khiển

giả sử bạn có khai báo và điền vào mảng byte, điều duy nhất bạn cần làm là sử dụng chức năng Tệp (sử dụng System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

và sau đó, trong cùng một bộ điều khiển, thêm 2 hàm

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

và sau đó bạn sẽ có thể gọi bộ điều khiển của mình để tải xuống và nhận cuộc gọi lại "thành công" hoặc "thất bại"

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });

1

Tôi đã tìm thấy một sửa chữa rằng trong khi nó không thực sự sử dụng ajax, nó cho phép bạn sử dụng một cuộc gọi javascript để yêu cầu tải xuống và sau đó nhận được một cuộc gọi lại khi quá trình tải xuống thực sự bắt đầu. Tôi thấy điều này hữu ích nếu liên kết chạy tập lệnh phía máy chủ cần một chút thời gian để soạn tập tin trước khi gửi. để bạn có thể thông báo cho họ rằng nó đang xử lý, và sau đó khi cuối cùng nó sẽ gửi tệp xóa thông báo xử lý đó. đó là lý do tại sao tôi muốn thử tải tệp qua ajax để bắt đầu để tôi có thể có một sự kiện xảy ra khi tệp được yêu cầu và một sự kiện khác khi nó thực sự bắt đầu tải xuống.

js trên trang nhất

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

khung nội tuyến

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

sau đó tập tin khác:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

Tôi nghĩ rằng có một cách để đọc dữ liệu bằng cách sử dụng js để không cần php. nhưng tôi không biết điều đó và máy chủ tôi đang sử dụng hỗ trợ php nên điều này hoạt động với tôi. nghĩ rằng tôi sẽ chia sẻ nó trong trường hợp nó giúp được bất cứ ai.


0

Nếu bạn muốn sử dụng Tải xuống tệp jQuery, xin lưu ý điều này cho IE. Bạn cần đặt lại phản hồi nếu không nó sẽ không tải xuống

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Hành động của bạn có thể thực hiện ServletResponseAware để truy cậpgetServletResponse()


0

Điều chắc chắn là bạn không thể thực hiện được thông qua cuộc gọi Ajax.

Tuy nhiên, có một cách giải quyết.

Các bước:

Nếu bạn đang sử dụng form.submit () để tải xuống tệp, điều bạn có thể làm là:

  1. Tạo một cuộc gọi ajax từ máy khách đến máy chủ và lưu trữ luồng tệp bên trong phiên.
  2. Khi "thành công" được trả về từ máy chủ, hãy gọi form.submit () của bạn để chỉ truyền phát luồng tệp được lưu trữ trong phiên.

Điều này hữu ích trong trường hợp khi bạn muốn quyết định xem có cần tải xuống tệp hay không sau khi tạo form.submit (), ví dụ: có thể xảy ra trường hợp trên form.submit (), ngoại lệ xảy ra ở phía máy chủ và thay vào đó về sự cố, bạn có thể cần hiển thị một thông báo tùy chỉnh ở phía máy khách, trong trường hợp đó việc triển khai này có thể giúp ích.


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 xử lý trang khỏi tải về 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
The HTML Code:-

'<button type="button" id="GetFile">Get File!</button>'


The jQuery Code:-

'$('#GetFile').on('click', function () {
    $.ajax({
        url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            var a = document.createElement('a');
            var url = window.URL.createObjectURL(data);
            a.href = url;
            a.download = 'myfile.pdf';
            document.body.append(a);
            a.click();
            a.remove();
            window.URL.revokeObjectURL(url);
        }
    });
});'

Mã chỉ có câu trả lời nên có ít nhất một mô tả tối thiểu giải thích cách mã hoạt động và lý do tại sao nó trả lời cho câu hỏi.
Roberto Caboni

0

Điều đó hoạt động rất tốt trong mọi trình duyệt (Tôi đang sử dụng lõi asp.net)

            function onDownload() {

  const api = '@Url.Action("myaction", "mycontroller")'; 
  var form = new FormData(document.getElementById('form1'));

  fetch(api, { body: form, method: "POST"})
      .then(resp => resp.blob())
      .then(blob => {
          const url = window.URL.createObjectURL(blob);
        $('#linkdownload').attr('download', 'Attachement.zip');
          $('#linkdownload').attr("href", url);
          $('#linkdownload')
              .fadeIn(3000,
                  function() { });

      })
      .catch(() => alert('An error occurred'));



}
 
 <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
 
 
 
 <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
 
 
 <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
 
 

        function onDownload() {
            const api = '@Url.Action("myaction", "mycontroller")'; 
            //form1 is your id form, and to get data content of form
            var form = new FormData(document.getElementById('form1'));

            fetch(api, { body: form, method: "POST"})
                .then(resp => resp.blob())
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    $('#linkdownload').attr('download', 'Attachments.zip');
                    $('#linkdownload').attr("href", url);
                    $('#linkdownload')
                        .fadeIn(3000,
                            function() {

                            });
                })
                .catch(() => alert('An error occurred'));                 

        }

-1

Tôi đã vật lộn với vấn đề này trong một thời gian dài. Cuối cùng một thư viện bên ngoài thanh lịch được đề nghị ở đây đã giúp tôi ra ngoài.

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.