文件上传是一个非常基础的问题了,日常项目中经常会遇到,最简单的方式就是前端设置一个上传按钮,后端设置方法进行接收,对于较小的文件和较高的带宽来说文件很快就能上传,用户体验良好,但是对于较大的文件和较低的带宽时,前端会一直阻塞在页面,容易给用户造成界面卡住的误会,所以想到用进度条提示给用户当前上传的进度,本文将介绍Layui框架在文件上传进度显示中的应用,其余框架大家可以借照思路进行参考,作者在这里进行抛砖引玉,希望能对大家有帮助。
对于该问题,想到两种解决方式,方法一,前端读取文件,通过计算当前一共向服务器传输多少字节,再除以文件总大小得出传输进度作为文件上传进度。方法二,前端不读取文件,而是通过短轮询不断向后台请求服务器接收了多少百分比,后端通过接收字节,再除以文件总大小得出接收进度作为文件上传进度。
方法比较,方法一通过客户端进行计算,减少了服务器压力,在多用户同时间段上传时效果较好,方法二,服务器需要将接收百分比放到session中,前端再通过不断查询获取服务器接收进度,此方法比较浪费资源,短轮询时间不容易控制,时间太短容易给服务器造成太大压力,时间太长用户感知接收进度不及时。所以我们在实战项目中主要应用第一种方法,第二种方法作为参考。
主要流程:
由于原生框架2.5.5以前不支持文件上传进度显示,所以我们要在组件中添加新的功能,layui/modules/upload.js文件中,该代码块为upload组件配置代码,在该处添加如下代码
注:Layui2.5.5及以后的版本加入了上传进度条回调函数,使用较新的版本可以跳过前3步
/* 支持进度回调 */
xhr: l.xhr(function (e) {//此处为新添加功能
var percent = Math.floor((e.loaded / e.total) * 100);//计算百分比
l.progress(percent);//回调将数值返回
}),
为了防止不配置xhr而报错,还需在layui/modules/upload.js文件中配置默认选项,该代码块为upload组件配置代码,在该处添加如下代码
xhr:function(){}//xhr空函数的定义,防止不设置报错
在页面js代码中添加如下代码
/* 设置进度条 */
var xhrOnProgress = function (fun) {
xhrOnProgress.onprogress = fun; //绑定监听
console.log(xhrOnProgress.onprogress);
//使用闭包实现监听绑
return function () {
//通过$.ajaxSettings.xhr();获得XMLHttpRequest对象
var xhr = $.ajaxSettings.xhr();
//判断监听函数是否为函数
if (typeof xhrOnProgress.onprogress !== 'function')
return xhr;
//如果有监听函数并且xhr对象支持绑定时就把监听函数绑定上去
if (xhrOnProgress.onprogress && xhr.upload) {
xhr.upload.onprogress = xhrOnProgress.onprogress;
}
return xhr;
}
}
在渲染upload组件时添加如下配置
demoFlag作为全局变量控制进度条在文件预处理后再开始变化
demoCount控制progress组件id
/* 添加进度条回调 */
xhr: xhrOnProgress,
progress: function (value) { //上传进度回调 value进度值,由于是进度条同时又是多文件上传不只一个进度条,所以要保证每次进度条的类名不一致,demoCount控制类名,demoFlag保证上下一致,所以该方法一直在被调用
console.log(value);
if (demoFlag) {
layui.element.progress(`progress${demoCount}`, value + '%') //设置页面进度条
}
},
由于该方法是前端计算文件上传进度,所以在开始上传前需要前端对文件进行预处理获取文件的信息对用户显示,这与后端计算文件上传进度不同,在完全上传前,前端只能进行上传操作,不能通过后端获取文件信息,所以可能出现信息不匹配的问题,例如,该文件名称在后端重名了,可能后端要对文件名称进行修改,又例如前端上传时间为发送时间,后端上传时间为接收时间可能存在时间不匹配问题,所以解决办法,可以设置文件原始名称originName和服务器文件名称fileName,上传时间uploadTime和接收时间receiveTime进行区别显示。
layui/table展示区别,由于layui/table是通过url获取后端数据进行渲染的,但是方法一需要在原始数据上添加一条新的数据,该数据仅仅是由前端对文件预处理获取的,所以这里介绍一个坑,就是纯前端的数据crud,在table.reload时需要将url设置为空,否则重加载时会加载后端数据而丢失前端对文件预处理的数据。
before: function (obj) {
layer.load(2);
/* 前端预览文件对象 */
obj.preview(function (index, file, result) {
var size = file.size;
var fileName = file.name;
demoCount = index;
console.log(fileName, size, demoCount);
demoFlag = true;
/* 向动态表格添加自定义行 */
var array = table.cache['fileTable'];
console.log(array);
var data = {"name": fileName, "size": size, "progress": 100, "filter": demoCount};
array.unshift(data);
console.log(array);
table.reload('fileTable', {
url: "",
data: array
});
})
},
field: 'progress', title: '上传进度', align: 'center', sort: true, templet: function (d) {
console.log(d)
d.filter == undefined ? d.filter = d.LAY_INDEX : d.filter;
d.progress == undefined ? d.progress = 100 : d.progress;
var color = 'layui-bg-green';
if (d.progress > 50 && d.progress <= 100) {
color = 'layui-bg-green'
} else if (d.progress > 20 && d <= 50) {
color = 'layui-bg-orange'
}
return '+ d.filter + '">' +
''
}
该方法不需要表格中添加纯静态数据,可以直接请求后端获取文件数据,在借助setInterval函数进行短轮询,不断获取后端接收进度更新页面进度条
layui.use('element', function () {
var element = layui.element;
//弹出进度条
var progressLayer = layer.open({
type: 0,
title: false,
closeBtn: 0,
btn: false,
content: ''
});
//ajax每隔一秒向后端请求一次进度异步渲染进度条
var scanTime = 1000;
var timer = setInterval(function (){
$.ajax({
url: "/distributor/exportStatus",
success: function (data) {
var arr = data.split(",");
if(arr.length == 2){
var percent = arr[1];
element.progress('progress', percent +'%')
if(percent == 100){
//进度到100%,注意关闭定时器
clearInterval(timer);
//关闭弹出层
layer.close(progressLayer);
layer.closeAll();
layer.msg('保存成功')
}
}
},
error: function (e) {
//关闭定时器
clearInterval(timer);
//关闭弹出层
layer.close(progressLayer);
layer.msg("保存失败");
layer.closeAll();
}
});
}, scanTime);
})
后端代码逻辑:获取for循环每次循环的索引,然后比上总数获得当前进度,然后存到session里面,供前端不停调用,代码如下:
// 接收 HttpServletRequest request
HttpSession session = request.getSession();
//定义double类型,用于保存当前百分比
double currentPercent;
//int类型,进度条显示的百分比
int percent = 0;
for (int i = 20; i < celldataArray.size(); i += 20)) {
currentPercent=((double)i/celldataArray.size())*100;
//因为百分比定义的为整数型,所以每次当前百分比和进度条显示的百分比之间差值大于1才更新
if((int) Math.floor(currentPercent) - percent >= 1) {
percent = (int)Math.floor(currentPercent);
if (percent>=99){
percent=100;
}
//"write,数值形式"方便显示
session.setAttribute("exportStatus", "write," + percent);
}
}
/**
* 查询当前导出的进度(用于进度条显示)
* @return
*/
@RequestMapping("/exportStatus")
@ResponseBody
public String exportStatus(HttpServletRequest httpServletRequest) {
return (String) httpServletRequest.getSession().getAttribute("exportStatus");
}
方法一纯静态数据添加问题,新添加的数据会重新加载表格,上传完成又会重新加载表格,过度动画太多,直接插入新数据会更好
方法一进度条没有随着百分比变化颜色,由红色变绿色会更加符合用户直觉
方法一没有记录文件原始名称、文件上传时间和上传进度百分比