一次操作即可下载多个文件
Posted
技术标签:
【中文标题】一次操作即可下载多个文件【英文标题】:Download multiple files with a single action 【发布时间】:2011-01-21 07:39:12 【问题描述】:我不确定这是否可以使用标准网络技术。
我希望用户能够在一个操作中下载多个文件。即点击文件旁边的复选框,然后获取所有被选中的文件。
有可能吗?如果有,您推荐什么基本策略。我知道我可以使用彗星技术来创建触发 HttpResponse 的服务器端事件,但我希望有一种更简单的方法。
【问题讨论】:
【参考方案1】:var links = [
'https://s3.amazonaws.com/Minecraft.Download/launcher/Minecraft.exe',
'https://s3.amazonaws.com/Minecraft.Download/launcher/Minecraft.dmg',
'https://s3.amazonaws.com/Minecraft.Download/launcher/Minecraft.jar'
];
function downloadAll(urls)
var link = document.createElement('a');
link.setAttribute('download', null);
link.style.display = 'none';
document.body.appendChild(link);
for (var i = 0; i < urls.length; i++)
link.setAttribute('href', urls[i]);
link.click();
document.body.removeChild(link);
<button onclick="downloadAll(window.links)">Test me!</button>
【讨论】:
我正在处理许多文件类型,包括图片,这对我来说效果最好。但是,link.setAttribute('download', null);
将我所有的文件重命名为 null。
它在 IE 11 中不起作用,它只下载 .jar(列表中的最后一项)这是完美的解决方案:(
@AngeloMoreira 是的,至少它在 Edge 中有效。如果您尝试在 MS 站点上的 IE 中下载多个文件,for example,它们会产生多个弹出窗口,所以我认为 IE 的最佳解决方案仍然来自Dmitry Nogin above。
@tehlivi - 我发现了同样的事情。在循环内添加link.setAttribute('download',filename)
。这使您可以随意命名文件。另外,我认为它只需要是文件名,不包括 URL。我最终发送了两个数组:一个带有完整的 URL,另一个带有文件名。
在 Chrome v75、Windows 10 中无法正常工作:下载的唯一文件是 Minecraft.jar
。【参考方案2】:
HTTP 不支持一次下载多个文件。
有两种解决方案:
打开 x 个窗口以启动文件下载(这将使用 javascript 完成) 首选解决方案创建一个脚本来压缩文件【讨论】:
为什么 zip 文件是首选解决方案?它为用户创建了一个额外的步骤(解压缩)。 此页面包含创建 ZIP 文件的 javascript。看看它有一个很好的例子的页面。 stuk.github.io/jszip 第三种方法是将文件封装成 SVG 文件。如果文件在浏览器中显示,SVG 似乎是最好的方式。 HTTP 本身支持多部分消息格式。但是浏览器不能便携地解析来自服务器端的多部分响应,但从技术上讲,这样做并没有什么困难。 这可以是使用 javascript github.com/sindresorhus/multi-download的绝佳解决方案【参考方案3】:您可以创建一组临时隐藏的 iframe,通过 GET 或 POST 在其中启动下载,等待下载开始并删除 iframe:
<!DOCTYPE html>
<html>
<body>
<button id="download">Download</button>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript">
$('#download').click(function()
download('http://nogin.info/cv.doc','http://nogin.info/cv.doc');
);
var download = function()
for(var i=0; i<arguments.length; i++)
var iframe = $('<iframe style="visibility: collapse;"></iframe>');
$('body').append(iframe);
var content = iframe[0].contentDocument;
var form = '<form action="' + arguments[i] + '" method="GET"></form>';
content.write(form);
$('form', content).submit();
setTimeout((function(iframe)
return function()
iframe.remove();
)(iframe), 2000);
</script>
</body>
</html>
或者,没有 jQuery:
function download(...urls)
urls.forEach(url =>
let iframe = document.createElement('iframe');
iframe.style.visibility = 'collapse';
document.body.append(iframe);
iframe.contentDocument.write(
`<form action="$url.replace(/\"/g, '"')" method="GET"></form>`
);
iframe.contentDocument.forms[0].submit();
setTimeout(() => iframe.remove(), 2000);
);
【讨论】:
太棒了,但由于某些原因,文件没有被下载。对我来说,原因似乎是脚本执行后页面重新加载,似乎是文件没有被下载的原因。关于我做错了什么的任何线索? 我在使用这个解决方案时遇到了多个问题。在 IE 中,由于我的父窗口更改了 document.domain,我的访问被拒绝。有很多关于解决这个问题的帖子,但都觉得很hacky。在 Chrome 中,用户会收到一条警告消息,告诉网站尝试下载多个文件(但至少它可以工作)。在 Firefox 中,我得到不同的下载框,但是当我点击保存时,我没有得到保存文件对话框... 这对我不起作用,因为文件对话框“阻止”了其他保存对话框的出现。我所做的事情有点棘手——mousemove 操作仅在文件对话框消失后注册,所以我使用了它——但它没有经过测试。我将其添加为另一个答案。 这在 IE10 中有效吗?我得到:对象不支持属性或方法'write' 为什么在setTimeout()
上返回函数(闭包?)?【参考方案4】:
此解决方案适用于跨浏览器,并且不会触发警告。这里我们为每个文件创建一个链接,而不是创建一个iframe
。这样可以防止弹出警告消息。
为了处理循环部分,我们使用setTimeout
,这是它在IE中工作所必需的。
2021 年更新:我知道“运行代码 sn-p”不再有效,但这是由于跨站点 cookie 问题。如果将代码部署在您自己的站点上,则该代码可以正常工作。
/**
* Download a list of files.
* @author speedplane
*/
function download_files(files)
function download_next(i)
if (i >= files.length)
return;
var a = document.createElement('a');
a.href = files[i].download;
a.target = '_parent';
// Use a.download if available, it prevents plugins from opening.
if ('download' in a)
a.download = files[i].filename;
// Add a to the doc for click to work.
(document.body || document.documentElement).appendChild(a);
if (a.click)
a.click(); // The click method is supported by most browsers.
else
$(a).click(); // Backup using jquery
// Delete the temporary link.
a.parentNode.removeChild(a);
// Download the next file with a small timeout. The timeout is necessary
// for IE, which will otherwise only download the first file.
setTimeout(function()
download_next(i + 1);
, 500);
// Initiate the first download.
download_next(0);
<script>
// Here's a live example that downloads three test text files:
function do_dl()
download_files([
download: "https://***.com/robots.txt", filename: "robots.txt" ,
download: "https://www.w3.org/TR/PNG/iso_8859-1.txt", filename: "standards.txt" ,
download: "http://qiime.org/_static/Examples/File_Formats/Example_Mapping_File.txt", filename: "example.txt" ,
]);
;
</script>
<button onclick="do_dl();">Test downloading 3 text files.</button>
【讨论】:
这是这里唯一对我有用的,因为我必须支持 IE。谢谢。 这个答案是金。只有一种适用于所有浏览器而不会出现警告消息。特别是 IE。很棒的东西 在 Chrome OSX 中不起作用,它要求我允许多次下载,但即使我允许,也只下载第一个文件,并且我听到与要下载的文件数量相对应的“哔”声左 按钮什么都不做Google Chrome Version 76.0.3809.100 (Official Build) (64-bit)
.
按钮在堆栈溢出中不起作用运行代码 sn-p。浏览器 Crome @speedplane【参考方案5】:
最简单的方法是将多个文件捆绑到一个 ZIP 文件中。
我想您可以使用一堆 iframe 或弹出窗口来启动多个文件下载,但从可用性的角度来看,ZIP 文件仍然更好。谁愿意点击浏览器会弹出的十个“另存为”对话框?
【讨论】:
我知道你的回答是从 2010 年开始的,但是现在很多用户都在用智能手机浏览,其中一些默认情况下无法打开 zips(一个朋友告诉我他的三星 S4 Active 可以'不打开拉链)。【参考方案6】:iframe 答案的 jQuery 版本:
function download(files)
$.each(files, function(key, value)
$('<iframe></iframe>')
.hide()
.attr('src', value)
.appendTo($('body'))
.load(function()
var that = this;
setTimeout(function()
$(that).remove();
, 100);
);
);
【讨论】:
每个都在寻找一个数组。这将起作用:download(['http://nogin.info/cv.doc','http://nogin.info/cv.doc']);
但是,这不适用于下载图像文件。【参考方案7】:
以下脚本优雅地完成了这项工作。
var urls = [
'https://images.pexels.com/photos/432360/pexels-photo-432360.jpeg',
'https://images.pexels.com/photos/39899/rose-red-tea-rose-regatta-39899.jpeg'
];
function downloadAll(urls)
for (var i = 0; i < urls.length; i++)
forceDownload(urls[i], urls[i].substring(urls[i].lastIndexOf('/')+1,urls[i].length))
function forceDownload(url, fileName)
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "blob";
xhr.onload = function()
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL(this.response);
var tag = document.createElement('a');
tag.href = imageUrl;
tag.download = fileName;
document.body.appendChild(tag);
tag.click();
document.body.removeChild(tag);
xhr.send();
【讨论】:
缺点:没有像常规下载那样的“下载中断-继续”功能(浏览器通过范围请求自动与服务器协商) 这行得通,但是当我运行它时,它似乎正在下载文件两次。有什么想法吗? @cbartell,可能是您在 urls 数组中添加了两次 url,如果这也下载文件两次,请测试上面给出的示例。【参考方案8】:我同意 zip 文件是一种更简洁的解决方案……但如果您必须推送多个文件,这就是我想出的解决方案。它适用于 IE 9 及更高版本(也可能是较低版本 - 我尚未测试过)、Firefox、Safari 和 Chrome。 Chrome 会在您的网站首次使用时向用户显示一条消息,以征得他同意下载多个文件。
function deleteIframe (iframe)
iframe.remove();
function createIFrame (fileURL)
var iframe = $('<iframe style="display:none"></iframe>');
iframe[0].src= fileURL;
$('body').append(iframe);
timeout(deleteIframe, 60000, iframe);
// This function allows to pass parameters to the function in a timeout that are
// frozen and that works in IE9
function timeout(func, time)
var args = [];
if (arguments.length >2)
args = Array.prototype.slice.call(arguments, 2);
return setTimeout(function() return func.apply(null, args); , time);
// IE will process only the first one if we put no delay
var wait = (isIE ? 1000 : 0);
for (var i = 0; i < files.length; i++)
timeout(createIFrame, wait*i, files[i]);
这种技术的唯一副作用是用户将看到提交和下载对话框显示之间的延迟。为了尽量减少这种影响,我建议您使用描述here 和Detect when browser receives file download 这个问题的技术,该技术包括为您的文件设置一个cookie,以知道它已经开始下载。您必须在客户端检查此 cookie 并在服务器端发送它。不要忘记为您的 cookie 设置正确的路径,否则您可能看不到它。您还必须针对多文件下载调整解决方案。
【讨论】:
【参考方案9】:角度解决方案:
HTML
<!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();
);
]);
工作示例:https://plnkr.co/edit/XynXRS7c742JPfCA3IpE?p=preview
【讨论】:
【参考方案10】:这适用于所有浏览器(IE11、firefox、Edge、Chrome 和 Chrome Mobile)我的文档位于多个选择元素中。当您尝试太快时,浏览器似乎有问题......所以我使用了超时。
//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);
);
【讨论】:
【参考方案11】:到目前为止最简单的解决方案(至少在 ubuntu/linux 中):
使用要下载的文件的 url 制作一个文本文件(即 file.txt) 将“file.txt”放在要下载文件的目录中 在上一个林的下载目录中打开终端 使用命令“wget -i file.txt”下载文件像魅力一样工作。
【讨论】:
我不明白为什么这会被否决。这很好用,非常感谢。 不是很友好。【参考方案12】:为了解决这个问题,我创建了一个 JS 库来将多个文件直接流式传输到客户端的 zip 中。主要的独特功能是它没有内存大小限制(所有内容都是流式传输的),也没有 zip 格式(如果内容超过 4GB,它使用 zip64)。
由于它不做压缩,所以它的性能也非常好。
在npm 或github 上找到“压缩”它!
【讨论】:
【参考方案13】:改进@Dmitry Nogin 的回答:这在我的情况下有效。
但是,它没有经过测试,因为我不确定文件对话如何在各种操作系统/浏览器组合上工作。 (因此是社区维基。)
<script>
$('#download').click(function ()
download(['http://www.arcelormittal.com/ostrava/doc/cv.doc',
'http://www.arcelormittal.com/ostrava/doc/cv.doc']);
);
var download = function (ar)
var prevfun=function();
ar.forEach(function(address)
var pp=prevfun;
var fun=function()
var iframe = $('<iframe style="visibility: collapse;"></iframe>');
$('body').append(iframe);
var content = iframe[0].contentDocument;
var form = '<form action="' + address + '" method="POST"></form>';
content.write(form);
$(form).submit();
setTimeout(function()
$(document).one('mousemove', function() //<--slightly hacky!
iframe.remove();
pp();
);
,2000);
prevfun=fun;
);
prevfun();
</script>
【讨论】:
【参考方案14】:我正在寻找一种解决方案来执行此操作,但是在 javascript 中解压缩文件并不像我喜欢的那样干净。我决定将这些文件封装成一个 SVG 文件。
如果您将文件存储在服务器上(我没有),您可以简单地在 SVG 中设置 href。
就我而言,我会将文件转换为 base64 并将它们嵌入到 SVG 中。
编辑:SVG 工作得非常好。如果您只想下载文件,ZIP 可能会更好。如果您要显示文件,那么 SVG 似乎更胜一筹。
【讨论】:
【参考方案15】:使用 Ajax 组件时,可以启动多个下载。因此你必须使用https://cwiki.apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow
将 AJAXDownload 的实例添加到您的页面或其他任何内容。创建一个 AjaxButton 并覆盖 onSubmit。创建一个 AbstractAjaxTimerBehavior 并开始下载。
button = new AjaxButton("button2")
private static final long serialVersionUID = 1L;
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form)
MultiSitePage.this.info(this);
target.add(form);
form.add(new AbstractAjaxTimerBehavior(Duration.milliseconds(1))
@Override
protected void onTimer(AjaxRequestTarget target)
download.initiate(target);
);
下载愉快!
【讨论】:
javascrpt?!?!?!?!?!【参考方案16】:下面的代码 100% 工作。
第 1 步:将以下代码粘贴到 index.html 文件中
<!DOCTYPE html>
<html ng-app="ang">
<head>
<title>Angular Test</title>
<meta charset="utf-8" />
</head>
<body>
<div ng-controller="myController">
<button ng-click="files()">Download All</button>
</div>
<script src="angular.min.js"></script>
<script src="index.js"></script>
</body>
</html>
第 2 步:将以下代码粘贴到 index.js 文件中
"use strict";
var x = angular.module('ang', []);
x.controller('myController', function ($scope, $http)
var arr = [
file:"http://localhost/angularProject/w3logo.jpg", fileName: "imageone",
file:"http://localhost/angularProject/cv.doc", fileName: "imagetwo",
file:"http://localhost/angularProject/91.png", fileName: "imagethree"
];
$scope.files = function()
angular.forEach(arr, function(val, key)
$http.get(val.file)
.then(function onSuccess(response)
console.log('res', response);
var link = document.createElement('a');
link.setAttribute('download', val.fileName);
link.setAttribute('href', val.file);
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
)
.catch(function onError(error)
console.log('error', error);
)
)
;
);
注意:确保将要下载的所有三个文件与 angularProject/index.html 或 angularProject/index 一起放在同一文件夹中.js 文件。
【讨论】:
你为 thuis 揉捏 ayng[the last airbender]ular for thuis/???【参考方案17】:通过ajax调用获取url列表,然后使用jquery plugin并行下载多个文件。
$.ajax(
type: "POST",
url: URL,
contentType: "application/json; charset=utf-8",
dataType: "json",
data: data,
async: true,
cache: false,
beforeSend: function ()
blockUI("body");
,
complete: function () unblockUI("body"); ,
success: function (data)
//here data --> contains list of urls with comma seperated
var listUrls= data.DownloadFilePaths.split(',');
listUrls.forEach(function (url)
$.fileDownload(url);
);
return false;
,
error: function (result)
$('#mdlNoDataExist').modal('show');
);
【讨论】:
【参考方案18】:这就是我这样做的方式。我打开了多个 ZIP,但也打开了其他类型的数据(我以 PDF 格式导出项目,同时打开了许多带有文档的 ZIP)。
我只是复制过去的部分代码。 来自列表中按钮的调用:
$url_pdf = "pdf.php?id=7";
$url_zip1 = "zip.php?id=8";
$url_zip2 = "zip.php?id=9";
$btn_pdf = "<a href=\"javascript:;\" onClick=\"return open_multiple('','".$url_pdf.",".$url_zip1.",".$url_zip2."');\">\n";
$btn_pdf .= "<img src=\"../../../images/icones/pdf.png\" alt=\"Ver\">\n";
$btn_pdf .= "</a>\n"
因此是对 JS 例程的基本调用(Vanilla 规则!)。 这是JS例程:
function open_multiple(base,url_publication)
// URL of pages to open are coma separated
tab_url = url_publication.split(",");
var nb = tab_url.length;
// Loop against URL
for (var x = 0; x < nb; x++)
window.open(tab_url[x]);
// Base is the dest of the caller page as
// sometimes I need it to refresh
if (base != "")
window.location.href = base;
诀窍是不提供 ZIP 文件的直接链接,而是将其发送到浏览器。像这样:
$type_mime = "application/zip, application/x-compressed-zip";
$the_mime = "Content-type: ".$type_mime;
$tdoc_size = filesize ($the_zip_path);
$the_length = "Content-Length: " . $tdoc_size;
$tdoc_nom = "Pesquisa.zip";
$the_content_disposition = "Content-Disposition: attachment; filename=\"".$tdoc_nom."\"";
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header($the_mime);
header($the_length);
header($the_content_disposition);
// Clear the cache or some "sh..." will be added
ob_clean();
flush();
readfile($the_zip_path);
exit();
【讨论】:
【参考方案19】: <p class="style1">
<a onclick="downloadAll(window.links)">Balance Sheet Year 2014-2015</a>
</p>
<script>
var links = [
'pdfs/IMG.pdf',
'pdfs/IMG_0001.pdf',
'pdfs/IMG_0002.pdf',
'pdfs/IMG_0003.pdf',
'pdfs/IMG_0004.pdf',
'pdfs/IMG_0005.pdf',
'pdfs/IMG_0006.pdf'
];
function downloadAll(urls)
var link = document.createElement('a');
link.setAttribute('download','Balance Sheet Year 2014-2015');
link.style.display = 'none';
document.body.appendChild(link);
for (var i = 0; i < urls.length; i++)
link.setAttribute('href', urls[i]);
link.click();
document.body.removeChild(link);
</script>
【讨论】:
以上是关于一次操作即可下载多个文件的主要内容,如果未能解决你的问题,请参考以下文章
前台JS可以一次性批量下载多个Excel表格吗xlsx.full. min