Javascript 文件批量上传,显示进度条

扩展方法如下:

jQuery.extend({
    /*
    传入参数说明:param = {
        url:"", //服务器接收的url,必填
        btnId:"", //上传按钮的Id,必填
        multiple:false, //是否支持多选文件,批量上传,选填。默认是false
        accept:"", //file控件接受的文件类型,选填。如仅接受图片:image/*,或者指定类型 image/jpg,image/png。还可以直接指定后缀 .jpg,.jpeg,.doc,.docx,.xls,.xlsx,.pdf
        fileId:"", //file控件的Id,选填。如果没有提供此参数,则认为动态创建file控件,点击btn后就弹出文件选择框,选中文件后就开始上传。
        uploadTipId:"", //提示进度文本的Id,选填 
        progressId:"", //进度条容器的Id,选填。如果没提供此参数,则忽略progressBarStyle参数。
        progressBarStyle:{}, //进度条(不包含外框)的样式        
        formData : {}, //每次上传到服务器url时,除了指定的文件(name默认为file),还包含此指定的表单值合集,一般是上传文件时其他的附带参数。
        before:function(idx, file){}, //每个文件上传前的事件,选填。默认传入2个参数:idx, file 原生对象。
        after: function(idx, newFileObj){}, //每个文件上传结束后的事件,选填。默认传入2个参数:idx, 服务器返回的Json(一般包含源文件名称,新文件的url)
        done: function (arr) { }, //所有文件上传完成后的事件,选填。默认传入1个数组参数:[newFileObj1, newFileObj2, newFileObj3, ...]        
        error:function(errMsg){} //上传出错时的事件,选填。
    }    
    */

    upload: function (param) {

        if (!param["btnId"] || param["btnId"].length == 0) {            
            return;
        }

        if (param.progressId && param.progressId.length > 0) {
            var progress = $("#" + param.progressId);
            var bar = progress.children(".bar");
            if (bar.length == 0) {
                progress.append("<div class='bar' style='height:100%'></div>");
            }
            bar = progress.children(".bar");            
            bar.css({ "background-color": "#27eb00", "min-height": "1px", "width":"0%" }).css(param.progressBarStyle);
        }

        if (param.fileId && param.fileId.length > 0) {
            if (param.multiple) {
                document.getElementById(param.fileId).setAttribute("multiple", true);
            }
            if (param.accept) {
                document.getElementById(param.fileId).setAttribute("accept", param.accept);                
            }
            $("#" + param["btnId"]).on("click", param, function (event) {
                var files = document.getElementById(event.data.fileId).files;
                if (!files || files.length == 0) {
                    return;
                }                
                $._uploadFileEvt(0, event.data);
            });
        } else {           
            if (!param.fileId || param.fileId == "") {
                $._uploadFileId = "file_" + Math.ceil(Math.random() * 1000000);
                param.fileId = $._uploadFileId;
                var fileCtrl = document.createElement("input");
                fileCtrl.type = "file";
                fileCtrl.style.display = "none";
                fileCtrl.id = param.fileId;
                if (param.multiple) {
                    fileCtrl.setAttribute("multiple", true);
                }                
                document.body.appendChild(fileCtrl);                           
            }       

            if (param.accept) {
                $("#" + param.fileId).attr("accept", param.accept);
            }

            $("#" + param.fileId).on("change", function () {                
                if ($(this)[0].files.length>0)
                    $._uploadFileEvt(0, param);
            });
            $("#" + param["btnId"]).on("click", function () { $("#" + param.fileId).trigger("click") });
        }
        
    },
    _uploadFileId:"",
    _uploadFileResults: [],  
    _uploadFileEvt: function (idx, param) {        
        var files = document.getElementById(param.fileId).files;        
        if (param.uploadTipId && param.uploadTipId.length > 0) {
            var name = files[idx].name;
            $("#" + param.uploadTipId).html("开始上传(" + (idx + 1) + "/" + files.length + "): " + name);
            
        }   
        if (param.progressId && param.progressId.length > 0) {
            var progress = $("#" + param.progressId);
            progress.children(".bar").css("width", "0%");            
        }
        param.before && param.before(idx, files[idx]);                       
        if (idx >= files.length) {
            param.error && param.error("指定上传文件的下标超出实际文件组长度");
            return;
        }
        var xhr = new XMLHttpRequest();
        xhr.open("POST", param.url, true);
        var fd = new FormData();  

        if (param.formData) {
            for (var k in param.formData) {
                fd.append(k, param.formData[k]);
            }
        }

        var fileCtrlName = document.getElementById(param.fileId).getAttribute("name");
        if (fileCtrlName) {
            fd.append(fileCtrlName, files[idx]);            
        } else {
            fd.append("file", files[idx]);
        }        
        
        if (param.progressId && param.progressId.length>0) {
            xhr.upload.onprogress = function (evt) {
                var percent = evt.loaded / evt.total;
                if (percent < 0.1) percent = 0.1;
                var width = (percent * 100).toString() + "%";
                var progress = $("#" + param.progressId);
                progress.children(".bar").css("width", width);
            };
        }

        xhr.send(fd);            

        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    var result = JSON.parse(xhr.responseText);
                    param.after && param.after(idx, result);
                    $._uploadFileResults.push(result);
                    if (idx >= files.length - 1) {                            
                        if (param.uploadTipId && param.uploadTipId.length > 0) {
                            $("#" + param.uploadTipId).html("上传完成(" + files.length+")");
                        } 
                        param.done && param.done($._uploadFileResults);
                        if ($._uploadFileId) {
                            $("#" + $._uploadFileId).remove();
                            $._uploadFileId = "";
                            param.fileId = "";
                        }
                        return;
                    }
                    idx++;                        
                    $._uploadFileEvt(idx, param);
                }
            }
        }        
    }
});

调用方法:

$.upload({
    url: "/URL", //服务器接收的url,必填
    btnId: "uploadBtn", //上传按钮的Id,必填
    multiple: true,                
    uploadTipId: "uploadText", //提示进度文本的Id,选填 
    progressId: "progressBar", //进度条容器的Id,选填。如果没提供此参数,则忽略progressBarStyle参数。
    progressBarStyle: { "height": "6px", "border-radius": "3px" }, //进度条(不包含外框)的样式                                    
    done: function (arr) {
        var files = JSON.stringify(arr);
        //files是上传成功后的文件信息
    }
});

$.upload() 是页面初始化时调用的,指定的btnId元素(一般是按钮)将会获得事件。点击此元素,将会弹出文件选择对话框,一旦选中了文件,立即开始按顺序上传文件,同时显示进度条。


由于前端脚本是按顺序逐个文件独立上传,比如上传10个文件,就会按顺序发出10次http请求,每个文件传输完成收到服务端返回的Json后,才继续上传下一个文件。服务端接收文件的代码就是常见的接收附件的流程,按单个文件接收。全部文件上传完成后,这个扩展方法会汇总每个文件的结果Json,合成一个数组,传入回调 done 方法。服务端返回的Json结构没有任何要求,不同的业务环境可以自定义不同的Json结构,比如返回文件的大小,访问Url,缩略图Url等信息。

2024-02-19 Javascript

发布评论