目录:
- 1. 简单粗暴使用XHR,不考虑IE,带下载中的灰度弹窗
- 2. 苟一苟,直接使用`window.location`,不过除了保存文件外,没有别的点击提醒,不能防止重复点击。
- 3. 下载的文件名乱码或者不是后台设置的文件名
1. 简单粗暴使用XHR,不考虑IE,带下载中的灰度弹窗
在前端使用下载功能时,最简单的就是使用a标签
或者window.location.href = "";
,刚开始我也是用的是window.location
,但是当文件比较大的时候,速度就很慢了,并且对前端来说不太友好,可能会重复点击下载按钮。增加后台的压力。要是可以监听下载事件就好了,当用户点击下载之后,给与友好的提示。
下面的代码没有实现进度条的提示,只给了一个load提示。
function downloadExcel(filename, url){
console.log('downloadExcel........');
var page_url = url;
var req;
if (window.XMLHttpRequest){
console.log("IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码");
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
req = new XMLHttpRequest();
}else{
console.log("IE6, IE5 浏览器执行代码");
// IE6, IE5 浏览器执行代码
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.open("get", page_url, true);
//监听进度事件 IE不兼容
/* req.addEventListener("progress", function (evt) {
console.log("addEventListener.....");
console.log(evt);
console.log("evt.lengthComputable:" + evt.lengthComputable);
if (evt.lengthComputable) {
var percentComplete = evt.loaded / evt.total;
console.log("percentComplete:" + percentComplete);
$("#progressing").html((percentComplete * 100) + "%");
}
}, false); */
req.responseType = "blob";
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status === 200) {
if (typeof window.chrome !== 'undefined') {
// Chrome version
var link = document.createElement('a');
link.href = window.URL.createObjectURL(req.response);
link.download = filename;
link.click();
} else if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE version
var blob = new Blob([req.response], { type: 'application/force-download' });
window.navigator.msSaveBlob(blob, filename);
} else {
// Firefox version
var file = new File([req.response], filename, { type: 'application/force-download' });
window.open(URL.createObjectURL(file));
}
}
};
var loadIndex;
req.onloadstart = function(event) {
console.log("onloadstart()");
loadIndex = layer.load(1, {
shade: [0.1,'#fff'] //0.1透明度的白色背景
});
}
req.onloadend = function(event) {
console.log("onloadend().......");
//关闭加载层
layer.closeAll('loading');
}
req.onerror = function(event) {
console.log("onerror()");
layer.closeAll('loading');
layer.alert("连接服务器失败,请联系管理员!", {title:"系统提示",icon: 2, shade: 0.2});
}
req.send();
}
不过这里也指定了保存的文件名称,当使用window.location
的时候,我在后端代码里面已经指定了下载的文件名了,这里设置的文件名会覆盖后端设置的文件名。
后端主要是使用流的方式输出excel文件,核心代码:
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
//设置ConetentType CharacterEncoding Header,需要在excelWriter.write()之前设置
response.setContentType("mutipart/form-data");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition","attachment;filename=" + URLEncoder.encode(downloadFileName, "UTF-8"));
BaseExcelWriter<SjsbMxWriteRowModel> excelWriter = new BaseExcelWriter<SjsbMxWriteRowModel>(outputStream, ExcelTypeEnum.XLSX, true);
excelWriter.consumeWrite(list, new SjsbMxExcelWriter(outputStream, ExcelTypeEnum.XLSX, true));
// 记得 释放资源
excelWriter.finish();
outputStream.flush();
log.info("导出明细成功");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (outputStream != null) {
outputStream.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
上面的java代码中主要关注response
的处理就好,将流数据写到response
中去。BaseExcelWriter
为自定义封装的easyExcel的工具类。
如果在req.onreadystatechange
打印日志的话可以看到一次请求,req.onreadystatechange
会执行好几遍,可以优化下,使用onload函数代替,在函数内判断status为200时,处理保存文件的操作。
req.onload = function(event) {
if (req.status === 200) {
console.log("filename:" + filename);
console.log("fileName:" + req.getResponseHeader("fileName"));
filename = req.getResponseHeader("fileName");
if (typeof window.chrome !== 'undefined') {
// Chrome version
var link = document.createElement('a');
link.href = window.URL.createObjectURL(req.response);
link.download = filename;
link.click();
} else if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE version
var blob = new Blob([req.response], { type: 'application/force-download' });
window.navigator.msSaveBlob(blob, filename);
} else {
// Firefox version
var file = new File([req.response], filename, { type: 'application/force-download' });
window.open(URL.createObjectURL(file));
}
} else {
//其它操作
console.log('req.status !=== 200');
}
}
当直接下载文件时,使用:
URLEncoder.encode(downloadFileName, "UTF-8")
防止中文文件名称乱码,直接使用浏览器访问下载地址的话,下载文件名中的中文是正常的。
对于上面下载的文件名,前后端都可定义但是感觉前端硬编码不太好,我还是想在后端定义,这时就可以用到一个函数getResponseHeader
,这就是XHR的响应部分了,但是IE不支持
。。。。。
XHR响应
了解了XHR的请求、XHR的事件回调之后,就剩下处理XHR响应的工作了,比如解析数据等等,要处理响应,需要了解下面的方法和属性。
- getResponseHeader(ByteString name);参数name为HTTP响应头部的键值
- getAllResponseHeaders方法可以获取所有的HTTP响应头的数据,其定义如下:
- status和statusText属性status属性表示HTTP响应状态码,即200、404等;statusText属性表示HTTP响应状态的描述文本,即OK、Not Found等。
可以在后端响应的头部,将文件名带回来:
response.setHeader("Content-disposition","attachment;");
response.setHeader("fileName",URLEncoder.encode(downloadFileName, "UTF-8"));
然后在XHR的onload函数里面使用:
req.onload = function(event) {
if (req.status === 200) {
console.log("filename:" + filename);
console.log("fileName:" + req.getResponseHeader("fileName"));
filename = req.getResponseHeader("fileName");
}
}
但是这样会产生中文乱码,按理说不应该是吧,因为response里面已经设置了utf-8了,不应该乱码的。
可以看下乱码的文件名内容,是不是只有百分号和数字,这里我突然想到是不是因为浏览器默认的不是UTF-8编码??又或者是编码之后,浏览器没有解码??
我试着把乱码的文件名复制到js里面,然后解码发现,乱码的文件名其实就是utf-8之后的文件名。对于谷歌浏览器可以直接解码得到原来的文件名。解码使用函数:decodeURI()
var fileName = req.getResponseHeader("fileName");
console.log("fileName:" + fileName);
//防止中文乱码,判断是否需要解码utf-8的文件名
console.log(fileName.indexOf('%') > -1);
if(fileName && fileName.indexOf('%') > -1){
var tempFileName = fileName.split('.');
console.log(tempFileName);
if(tempFileName.length == 2){
fileName = decodeURI(tempFileName[0]) + '.' + tempFileName[1];
}
}else{
fileName = '123456';
}
在谷歌浏览器里面是正常的,解码得到原文件名,但是在IE里面就不好使了,直接连响应头里面的参数都拿不到。。。。。
req.getResponseHeader("fileName");
里面获取到的文件名是null
。。。。。。
关于前台往后台传值中文乱码的解决方式,可以参考这篇博客,使用两次编码
一个文件名折腾了半天,最后也没找到完美的解决办法,最后还是使用了前端硬编码,将文件名写在前端了。。。。。。。
这里只用了下载开始和完成的事件,更详细的使用可以参考下面的博客,里面有更详细的XMLHttpRequest的使用方法
2. 苟一苟,直接使用window.location,不过除了保存文件外,没有别的点击提醒,不能防止重复点击。
window.location = download_url;
这种IE是正常的,如果文件很小,下载速度秒开的话,是正常的,但是文件比较大时,可能点击下载按钮之后前端没有任何反应,这种情况用户可能会多次点击下载按钮。
3. 下载的文件名乱码或者不是后台设置的文件名
主要是看对response的处理:
OutputStream outputStream = null;
String downloadFileName = "我是下载文件名.xlsx";
try {
outputStream = response.getOutputStream();
//设置ConetentType CharacterEncoding Header,需要在excelWriter.write()之前设置
response.setCharacterEncoding("utf-8");
response.setContentType("application/x-download;charset=utf-8");
// response.setContentType("mutipart/form-data;charset=utf-8");
// response.setHeader("Content-Disposition","attachment;");
// response.setHeader("fileName",URLEncoder.encode(downloadFileName, "UTF-8"));
response.setHeader( "Content-Disposition ", "attachment;filename=" + URLEncoder.encode(downloadFileName, "UTF-8"));
//BaseExcelWriter为自定义工具类
BaseExcelWriter<SjsbMxWriteRowModel> excelWriter = new BaseExcelWriter<SjsbMxWriteRowModel>(outputStream, ExcelTypeEnum.XLSX, true);
excelWriter.consumeWrite(list, new SjsbMxExcelWriter(outputStream, ExcelTypeEnum.XLSX, true));
// 记得 释放资源
excelWriter.finish();
outputStream.flush();
log.info("导出枪支弹药明细成功");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (outputStream != null) {
outputStream.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
前面折腾了半天,最后还是改了response的头部参数,才使得下载的excel文件名正常了。
response.setContentType("application/x-download;charset=utf-8");
response.setHeader( "Content-Disposition ", "attachment;filename=" + URLEncoder.encode(downloadFileName, "UTF-8"));