当用户点击按钮时,我该如何让他们下载多个文件?

62

我有一个运行着的httpd服务器,其中包含许多文件的链接。假设用户从文件列表中选择了三个要下载的文件,它们位于:

mysite.com/file1 
mysite.com/file2
mysite.com/file3

当他们点击下载按钮时,我希望他们可以从上面的链接中下载这三个文件。

我的下载按钮大致长这样:

var downloadButton = new Ext.Button({
  text: "Download",
  handler: function(){
    //download the three files here
  }
});

1
如果你在手机上,zip 压缩文件就很糟糕。你可以触发三个下载,Chrome 甚至会识别并提示用户是否允许你的网站“下载多个文件”。如果你想使用 zip,你可以通过 ajax 获取文件,使用 JSZip 进行压缩,然后从生成的 JS 树对象中下载 zip 文件。 - dandavis
如何最好地触发下载?window.open("mysite.com/file1")是最佳方式吗? - Grammin
我喜欢在可用的情况下使用 JavaScript 方法(例如 danml.com/js/download.js),或者<a download=file1.ext href=file1.ext>save</a>,它可以处理 dataURLs。虽然我通常不下载文件,而是使用 JavaScript 生成字符串,所以请将我的建议视作参考。 - dandavis
一个仅限于Chrome浏览器的API,文件系统访问 API,最近新增了多文件(流式)下载功能。 - tsh
这回答解决了您的问题吗?使用单个操作下载多个文件 - Flimm
13个回答

41

最好的方法是将您的文件压缩并链接到该文件:

另一种解决方案可以在这里找到:如何在单击时使链接打开多个页面

其内容如下:

HTML:

<a href="#" class="yourlink">Download</a>

JS:

$('a.yourlink').click(function(e) {
    e.preventDefault();
    window.open('mysite.com/file1');
    window.open('mysite.com/file2');
    window.open('mysite.com/file3');
});

尽管如此,我仍会选择压缩文件,因为这种实现需要JavaScript并且有时也可能被阻止弹出。


所以我没有访问preventDefault()函数的权限,有没有办法在打开第二个文件之前等待用户对第一个文件执行操作?发生的情况是直接跳转到第三个文件,我无法下载前两个文件。 - Grammin
你在谈论什么样的操作?因为我不确定你能否追踪它。 - T J
我的调用是 window.open('mysite.com/file1', '_parent');,它会覆盖所有其他窗口(这不起作用,因为我需要在每个窗口上等待用户交互)。我使用 _self 的原因是因为我不想切换窗口,我想留在父窗口。 - Grammin
最好您创建两个页面:mysite.com/file1.html 和 mysite.com/file2.html。每当这些页面加载时,您可以开始下载该页面的文件,并要求用户交互。一旦交互得到确认,然后载入下一个页面。(我不确定我是否解释得清楚) - T J

31

这是我用过的最好的方法,它不会打开新标签页,而是直接下载我需要的文件/图片:

var filesForDownload = [];
filesForDownload( { path: "/path/file1.txt", name: "file1.txt" } );
filesForDownload( { path: "/path/file2.jpg", name: "file2.jpg" } );
filesForDownload( { path: "/path/file3.png", name: "file3.png" } );
filesForDownload( { path: "/path/file4.txt", name: "file4.txt" } );

$jq('input.downloadAll').click( function( e )
{
    e.preventDefault();

    var temporaryDownloadLink = document.createElement("a");
    temporaryDownloadLink.style.display = 'none';

    document.body.appendChild( temporaryDownloadLink );

    for( var n = 0; n < filesForDownload.length; n++ )
    {
        var download = filesForDownload[n];
        temporaryDownloadLink.setAttribute( 'href', download.path );
        temporaryDownloadLink.setAttribute( 'download', download.name );

        temporaryDownloadLink.click();
    }

    document.body.removeChild( temporaryDownloadLink );
} );

4
我刚刚在使用丹的代码时遇到了问题,Chrome浏览器忽略了快速点击,因此我不得不设置一个延迟。 - Iain M Norman
@IainMNorman 有趣的问题,超时似乎是一个足够好的解决方案,不确定还有什么其他方法可以解决。 - Dan
9
提醒其他读者:下载属性不适用于跨域请求(至少在最新版的Chrome浏览器中是如此)。我认为它曾经可以使用,但已被修补掉。 - georaldc
3
temporaryDownloadLink.setAttribute('href', 'data:application/octet-stream,' + encodeURIComponent( download.path)); 这段代码用于强制浏览器下载图像而不是在新标签页中打开它。 - JasperJ
1
你调用了一个数组 O_o。是不是在数组初始化行中缺少了 .push 方法? - Klesun
这是我正在使用的代码,但如果文件超过10个,它会跳过它们并仅下载前10个文件。 - Sharad

28

我发现在 for循环 中执行 a 元素上的 click() 事件进行多文件下载只适用于有限数量的文件(在我的情况下是10个文件)。唯一能解释这种行为的原因,对我来说有意义的,是由于 click() 事件所执行的下载速度/间隔造成的。

我想到了一个解决方法,如果我减缓 click() 事件的执行,那么我就能够下载所有文件。

这是对我有效的解决方案。

var urls = [
  'http://example.com/file1',
  'http://example.com/file2',
  'http://example.com/file3'
]

var interval = setInterval(download, 300, urls);

function download(urls) {
  var url = urls.pop();

  var a = document.createElement("a");
  a.setAttribute('href', url);
  a.setAttribute('download', '');
  a.setAttribute('target', '_blank');
  a.click();

  if (urls.length == 0) {
    clearInterval(interval);
  }
}

我每隔300毫秒执行一次下载事件click()。当没有更多的文件需要下载时,即urls.length == 0,我会执行clearInterval函数停止下载。


4
我知道定时执行代码应该是可以避免的,但有时你必须去做。 - Lukasz Dynowski
我认为您的浏览器配置了最多10个连接。 - CodeWriter23
@CodeWriter23或许知道,你能提供一下在Chrome和Firefox中设置“最大连接数”的链接吗? - Lukasz Dynowski
Lukasz:不受程序控制。我最好的答案是Chrome已经硬编码了限制,而在Firefox中它在about:config下的network.http.max-persistent-connections-per-server中。 - CodeWriter23

5
您可以选择以下任一操作:
  1. 将所选文件压缩并返回一个压缩文件。
  2. 打开多个弹窗,每个弹窗都会提示下载。

注意:第一个选项更具客观优势。

还有第三个选项:使用单个动作下载多个文件


选项3! - Alex Weitz
如何“压缩选定的文件并返回一个压缩文件”? - 2540625

3

我通过使用window.location以不同的方式解决了这个问题。它在Chrome中有效,而Chrome是我唯一需要支持的浏览器。这可能对某些人有用。我最初使用了Dan的答案,但也需要我在这里使用的超时,否则只会下载一个文件。

var linkArray = [];
linkArray.push("http://example.com/downloadablefile1");
linkArray.push("http://example.com/downloadablefile2");
linkArray.push("http://example.com/downloadablefile3");    

function (linkArray) {
  for (var i = 0; i < linkArray.length; i++) { 
    setTimeout(function (path) { window.location = path; }, 200 + i * 200, linkArray[i]);
  }        
};

这在Firefox上也可以工作,并且据报道在Edge上也可以,但我对自己测试没有兴趣(Edge正在自行迁移到Blink - 好事)。 - aross

1

使用:

<!DOCTYPE html>
<html ng-app='app'>
    <head>
        <title>
        </title>
        <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
        <link rel="stylesheet" href="style.css">
    </head>
    <body ng-cloack>
        <div class="container" ng-controller='FirstCtrl'>
          <table class="table table-bordered table-downloads">
            <thead>
              <tr>
                <th>Select</th>
                <th>File name</th>
                <th>Downloads</th>
              </tr>
            </thead>
            <tbody>
              <tr ng-repeat = 'tableData in tableDatas'>
                <td>
                    <div class="checkbox">
                      <input type="checkbox" name="{{tableData.name}}" id="{{tableData.name}}" value="{{tableData.name}}" ng-model= 'tableData.checked' ng-change="selected()">
                    </div>
                </td>
                <td>{{tableData.fileName}}</td>
                <td>
                    <a target="_self" id="download-{{tableData.name}}" ng-href="{{tableData.filePath}}" class="btn btn-success pull-right downloadable" download>download</a>
                </td>
              </tr>
            </tbody>
          </table>
            <a class="btn btn-success pull-right" ng-click='downloadAll()'>download selected</a>

            <p>{{selectedone}}</p>
        </div>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
        <script src="script.js"></script>
    </body>
</html>

文件 app.js

var app = angular.module('app', []);
app.controller('FirstCtrl', ['$scope','$http', '$filter', function($scope, $http, $filter){

$scope.tableDatas = [
    {name: 'value1', fileName:'file1', filePath: 'data/file1.txt', selected: true},
    {name: 'value2', fileName:'file2', filePath: 'data/file2.txt', selected: true},
    {name: 'value3', fileName:'file3', filePath: 'data/file3.txt', selected: false},
    {name: 'value4', fileName:'file4', filePath: 'data/file4.txt', selected: true},
    {name: 'value5', fileName:'file5', filePath: 'data/file5.txt', selected: true},
    {name: 'value6', fileName:'file6', filePath: 'data/file6.txt', selected: false},
  ];
$scope.application = [];

$scope.selected = function() {
    $scope.application = $filter('filter')($scope.tableDatas, {
      checked: true
    });
}

$scope.downloadAll = function(){
    $scope.selectedone = [];
    angular.forEach($scope.application,function(val){
       $scope.selectedone.push(val.name);
       $scope.id = val.name;
       angular.element('#'+val.name).closest('tr').find('.downloadable')[0].click();
    });
}

}]);

Plunker示例:https://plnkr.co/edit/XynXRS7c742JPfCA3IpE?p=preview

1
这是我找到的最简单的方法来下载多个文件。
$('body').on('click','.download_btn',function(){
    downloadFiles([
        ['File1.pdf', 'File1-link-here'],
        ['File2.pdf', 'File2-link-here'],
        ['File3.pdf', 'File3-link-here'],
        ['File4.pdf', 'File4-link-here']
    ]);
})
function downloadFiles(files){
    if(files.length == 0){
        return;
    }
    file = files.pop();
    var Link = $('body').append('<a href="'+file[1]+'" download="file[0]"></a>');
    Link[0].click();
    Link.remove();
    downloadFiles(files);
}

这对你应该有效。

1
这适用于所有浏览器(IE11、Firefox、Microsoft Edge、Chrome和Chrome Mobile)。我的文档在多个选择元素中。当您尝试过快进行操作时,浏览器似乎存在问题... 因此我使用了一个超时。
<select class="document">
    <option val="word.docx">some word document</option>
</select>

//user clicks a download button to download all selected documents
    $('#downloadDocumentsButton').click(function () {
        var interval = 1000;
        //select elements have class name of "document"
        $('.document').each(function (index, element) {
            var doc = $(element).val();
            if (doc) {
                setTimeout(function () {
                    window.location = doc;
                }, interval * (index + 1));
            }
        });
    });

这个解决方案使用了 Promise:
function downloadDocs(docs) {
    docs[0].then(function (result) {
        if (result.web) {
            window.open(result.doc);
        }
        else {
            window.location = result.doc;
        }
        if (docs.length > 1) {
            setTimeout(function () { return downloadDocs(docs.slice(1)); }, 2000);
        }
    });
}

 $('#downloadDocumentsButton').click(function () {
    var files = [];
    $('.document').each(function (index, element) {
        var doc = $(element).val();
        var ext = doc.split('.')[doc.split('.').length - 1];

        if (doc && $.inArray(ext, docTypes) > -1) {
            files.unshift(Promise.resolve({ doc: doc, web: false }));
        }
        else if (doc && ($.inArray(ext, webTypes) > -1 || ext.includes('?'))) {
            files.push(Promise.resolve({ doc: doc, web: true }));
        }
    });

    downloadDocs(files);
});

1
你可以选择使用iframe
注意:虽然这是一种在不创建弹出窗口的情况下同时下载多个文件的少数方法之一,但对于可以在浏览器中渲染的文件,此方法将无效。请查看JavaScript代码注释。
注意2:某些浏览器可能会要求允许从同一页下载多个文件。

function download(){
  const links = ["mysite.com/file1", "mysite.com/file2", "mysite.com/file3"]
  // It only works with files that don't render in the browser
  // I.e., not video, not text, not and photo
  for(let i = 0; i < links.length; i++) {
    var frame = document.createElement("iframe");
    frame.src = links[i];
    frame["download"] = 1
    document.body.appendChild(frame);
  }
}
iframe{
  display: none;
}
<body>
  <div onclick="download()">Download</div>
</body>


0
这个解决方案对我来说完全没问题。
var downloadButton = new Ext.Button({
    text: "Download",
    handler: function () {

        /** @type {Array<string>} URLS */
        const URLS = [
            "mysite.com/file1 ",
            "mysite.com/file2",
            "mysite.com/file3",
        ];

        for (let x = 0; x < URLS.length; x++) {

            /** @type {string} URL */
            const URL = URLS[x];

            /** @type {HTMLLinkElement} LINK */
            const LINK = document.createElement('a');

            LINK.href = URL;
            LINK.setAttribute('download', 'download');

            document.body.appendChild(LINK);

            LINK.click();
            LINK.parentNode.removeChild(LINK);

            window.URL.revokeObjectURL(URL);
        }
    }
});

即使它能工作,它仍然是一堆代码。需要解释一下。例如,这个想法/要点是什么?来自帮助中心的说明:“...始终解释为什么你提出的解决方案是合适的以及它是如何工作的”。请通过编辑(更改)您的答案进行回应,而不是在评论中回复(但*** *** *** *** *** *** *** *** *** *** *** *** 不要 *** *** ***使用“编辑:”,“更新:”或类似的词语 - 答案应该看起来像是今天写的)。 - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接