使用js监听文件下载事件,解决导出excel文件名为.do的bug

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。。。。。。

关于前台往后台传值中文乱码的解决方式,可以参考这篇博客,使用两次编码:https://blog.csdn.net/acmman/article/details/47755723?utm_source=blogxgwz0

一个文件名折腾了半天,最后也没找到完美的解决办法,最后还是使用了前端硬编码,将文件名写在前端了。。。。。。。

这里只用了下载开始和完成的事件,更详细的使用可以参考下面的博客,里面有更详细的XMLHttpRequest的使用方法:https://blog.csdn.net/a576890883/article/details/101313227

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"));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值