我有一个允许用户下载动态生成文件的页面。生成它需要很长时间,所以我想显示一个“等待”指示器。问题是,我不知道如何检测浏览器何时接收到文件,以便我可以隐藏指示器。

我请求一个隐藏的表单,它post到服务器,并以一个隐藏的iframe作为其结果的目标。这样我就不会用结果替换整个浏览器窗口。我监听iframe上的“load”事件,希望它在下载完成时触发。

我将文件返回一个“Content-Disposition: attachment”头,这将导致浏览器显示“Save”对话框。但是浏览器不会在iframe中触发“load”事件。

我尝试过的一种方法是采用多部分回答。所以它会发送一个空的HTML文件,以及附加的可下载文件。

例如:

Content-type: multipart/x-mixed-replace;boundary="abcde"

--abcde
Content-type: text/html

--abcde
Content-type: application/vnd.fdf
Content-Disposition: attachment; filename=foo.fdf

file-content
--abcde

这在Firefox中是有效的;它接收空HTML文件,触发“load”事件,然后显示可下载文件的“Save”对话框。但它在ie和Safari浏览器上无法运行;Internet Explorer触发“load”事件,但它不下载文件,而Safari下载文件(带有错误的名称和内容类型),但不触发“load”事件。

另一种方法可能是调用启动文件创建,轮询服务器直到准备就绪,然后下载已经创建的文件。但我宁愿避免在服务器上创建临时文件。

我该怎么办?


当前回答

您可以依赖浏览器的缓存,并在文件加载到缓存时触发同一文件的第二次下载。

$('#link').click(function(e) {
    e.preventDefault();

    var url = $(this).attr('href');
    var request = new XMLHttpRequest();
    request.responseType = "blob";
    request.open("GET", url);

    var self = this;
    request.onreadystatechange = function () {
        if (request.readyState === 4) {
            var file = $(self).data('file');
            var anchor = document.createElement('a');
            anchor.download = file;
            console.log(file);
            console.log(request);
            anchor.href = window.URL.createObjectURL(request.response);
            anchor.click();
            console.log('Completed. Download window popped up.');
        }
    };
    request.send();
});

其他回答

如果您下载了一个文件,该文件是保存的,而不是在文档中,则无法确定下载何时完成,因为它不在当前文档的范围内,而是浏览器中的一个单独进程。

当用户触发文件的生成时,您可以简单地为“下载”分配一个唯一的ID,并将用户发送到每隔几秒钟刷新(或使用AJAX检查)的页面。一旦文件完成,将其保存在相同的唯一ID和…

如果文件已经准备好,请进行下载。 如果文件尚未准备好,请显示进度。

然后你可以跳过整个iframe/waiting/browserwindow的混乱,而有一个真正优雅的解决方案。

我使用以下代码下载blobs并在下载后撤销对象URL。它在Chrome和Firefox中工作!

function download(blob){
    var url = URL.createObjectURL(blob);
    console.log('create ' + url);

    window.addEventListener('focus', window_focus, false);
    function window_focus(){
        window.removeEventListener('focus', window_focus, false);
        URL.revokeObjectURL(url);
        console.log('revoke ' + url);
    }
    location.href = url;
}

关闭文件下载对话框后,窗口将获得其焦点,因此将触发焦点事件。

来自其他地方的有效解决方案:

/**
 *  download file, show modal
 *
 * @param uri link
 * @param name file name
 */
function downloadURI(uri, name) {
// <------------------------------------------       Do something (show loading)
    fetch(uri)
        .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 = name;
            document.body.appendChild(a);
            a.click();
            window.URL.revokeObjectURL(url);
            // <----------------------------------------  Detect here (hide loading)
            alert('File detected');
        })
        .catch(() => alert('An error sorry'));
}

你可以使用它:

downloadURI("www.linkToFile.com", "file.name");

如果你不想在服务器上生成和存储文件,你是否愿意存储状态,例如文件进行中,文件完成?您的“等待”页面可以轮询服务器以了解文件生成何时完成。您不能确定是浏览器启动了下载,但您有一定的信心。