最近项目在搞重构,一个很重要的图片上传功能之前是上个前端从网上找的严重不支持ie8,ie9;而且产品经理
还对上传插件提了好多新的要求,然后,插件被改的面目全非,而且改的很痛苦;现在终于有了时间重构;
自己花了三天不到时间写了一个图片上传插件(也不能算是插件,因为可能只是符合我们现有的要求,而且通用性不太好)。
这里分享给大家,不是让大家拿过去用;而是梳理上传的思路,分享自己写代码过程中的一些心得体会等等。
整体来说插件要支持以下几个功能:
1.指定格式,指定数量上传;
2.支持本地预览已选择图片
3.支持终止上传,继续上传;
4.支持度图片的删除,重新选择等等。
5.还有进度条,上传完成的数量,状态信息等等。。
还有一个一些诸如,当前文件夹满了给出提示,并上传到新的文件夹等等。。其实自己一开始想的比较简单;因为产品的流程图
页想下面一样简单;各种异常情况;规格都没有。
第一步当然是画页面,搭架子,留接口。界面大概如下:
为了方便下面说明我先直接贴源码和调用示例
define(['min_win','uploadPreview'], function (min_win, uploadPreview) {function Upload() { if(this instanceof Upload){ this.config = { maxCount:20,//最大批量上传数量 dragable:false,//是否支持拖拽 formats:["jpg", "jpeg", "png"],//支持的图片格式 onChange:null,//选中的回调 onSubmit:null,//确认上传回调 onDelete:null,//删除回调 onSuccess:null,//单个文件成功的回调 onComplete:null,//完成本次上传的回调,(全部成功不会触发) onFailure:null,//单个失败回调 onAllSuccess:null,//全部成功的回调 OnUpload:null,//单个文件准备上传前的回调, onClose:null,//关闭弹窗回调 url:"",//上传的url htmlStr:""//自定义html片段 }; this.handlers = {};//自定义事件集合 this.filesArr = []; this.fileSuccessCount = 0;//上传成功个数 this.fileFailureCount = 0;//上传失败个数 this.filesFailureArr = []; this.uploadFlag = false;//是否继续上传 this.isComplete = false; this.isAllSuccess = false; this.uploadStatus = 0;//0,表示为开始,1表示进行时,-1表示完成 } else{ return new Upload(); }}/*Upload.prototype = new window.Widget();*/Upload.prototype.run = function (config) { var CFG = $.extend(this.config, config);//合并对象 this.initData(); this.renderUI(CFG); this.syncUI(CFG); this.bindUI(CFG); return this;}Upload.prototype.initData = function(){ this.handlers = {}; this.filesArr = []; this.clearFilesCount(); this.filesFailureArr = []; this.uploadFlag = false;//是否继续上传 this.uploadStatus = 0;}Upload.prototype.renderUI = function (CFG) { new min_win.min_window({ type:{ mack:true, position:'center' }, html:CFG.htmlStr, callback:null }).run();}Upload.prototype.bindUI = function(CFG){ var that = this; $("#ul-fileInput").on("change", changeHandler); $(".uploadSubmit").bind("click", uploadHandler); $(".continueSelect").bind("click", continueHandler); $(".reSelect").bind("click", reSelectHandler); $("body").on("click", ".ul-waitItem .ul-curIcon", deleteHandler); $(".u-uploadDailog .ul-closeBut").bind("click", closeHandler); //关闭事件 function closeHandler(event){ CFG.onClose && CFG.onClose(); } //选中change事件 function changeHandler(event){ console.log("changeHandler"); //如果支持html5的this.files if(this.files){ var itemListHtml =""; var len = this.files.length; for(var i = 0;i < len; i++){ var tempIndex = 0; var tempfile = this.files[i]; if(that.checkFormat(tempfile.name, that.config.formats)){ if(that.filesArr.length){ tempIndex = that.filesArr.length; } //文件数量>maxCount,只取前maxCount个 if(that.filesArr.length < that.config.maxCount){ itemListHtml += ' '; tempfile.index = tempIndex;//每个file对象打上唯一标示,与ul-item一一对应 tempfile.sendtype = "h5"; that.filesArr.push(tempfile); } } } $(".ul-imgCont").append(itemListHtml); that.setButHideOrShow($(".continueSelect"), true);//展示继续上传按钮 } else { var fileVal = $("#ul-fileInput").val(); if(!that.checkFormat(fileVal, that.config.formats)){ return; } var itemHtmlStr = ' '; $(".ul-imgCont").append(itemHtmlStr); var $img = $(".ul-item img"); try { $img.attr('src', getImgPath(this.files[0])); } catch (e) { var src = ""; var obj = $img; var div = obj.parents(".ul-waitItem"); var curDom = document.getElementById("ul-fileInput"); curDom.select(); curDom.blur(); src = document.selection.createRange().text; document.selection.empty(); div.css({ 'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale)' }); div[0].filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = src; var fileObj = {}; fileObj.sendtype = "ie"; fileObj.src = src; fileObj.index = 0; that.filesArr.push(fileObj); } } //选择文件后的回调 CFG.onChange && CFG.onChange(); } //继续选择 function continueHandler(event){ //如果当前按钮处于禁用状态,直接返回 if($(this).hasClass("ul-input-disable")){ return; } $("#ul-fileInput").click(); } //上传图片 function uploadHandler(event) { //如果当前按钮处于禁用状态,直接返回 if($(this).hasClass("ul-input-disable")){ return; } //点击终止上传 if($(this).hasClass("ul-input-stopUpload")){ that.stopUpload(); //终止后,为上传图片全部标记为失败 $(".ul-waitItem").addClass("ul-failureItem"); $(".ul-waitItem").removeClass("ul-waitItem"); that.setButText($(".uploadSubmit"), '继续上传'); that.setButHideOrShow($(".continueSelect"), false);//隐藏继续选择 that.setButHideOrShow($(".reSelect"), true);//显示重新选择 $(".uploadSubmit").addClass("ul-input-goOnUpload"); $(this).removeClass("ul-input-stopUpload"); return; } //点击继续上传 if($(this).hasClass("ul-input-goOnUpload")){ //that.clearFilesCount(); that.goOnUpload(); } //点击上传的回调 CFG.onSubmit && CFG.onSubmit(); that.uploadSingle(); } //预览时,删除指定图片 function deleteHandler(event){ if(1=== that.uploadStatus){ return; } var parentItem = $(this).parents(".ul-waitItem"); var index = parseInt(parentItem.attr("index"), 10); delArrItemByIndex("index", index, that.filesArr); that.setSelectMsg(); parentItem.remove(); //删除文件后的回调 CFG.onDelete && CFG.onDelete(); } //重新选择 function reSelectHandler(event){ //如果当前按钮处于禁用状态,直接返回 if($(this).hasClass("ul-input-disable")){ return; } that.clearDailog(); that.clearBar(); that.clearUploadMsg(); that.clearSelectMsg(); that.initData(); }}//图片格式校验Upload.prototype.checkFormat = function(urlStr, array){ if(!urlStr || "" === urlStr){ return false; } var fromats = urlStr.substring(urlStr.lastIndexOf(".")+1,urlStr.length) if($.inArray(fromats.toLowerCase(), array) > -1){ return true; } return false;}//修改当前上传的状态:0,表示为开始,1表示进行时,-1表示完成Upload.prototype.setUploadStatus = function(status){ this.uploadStatus = status;}Upload.prototype.setFilesFailure = function(){ }//销毁插件Upload.prototype.destroy = function(){ this.initData(); $(".mack_cont").remove(); $(".mack").remove();}//修改上传urlUpload.prototype.updateUrl = function(newUrl){ this.config.url = newUrl;}//获取文件的个数Upload.prototype.getFileArr = function(){ return this.filesArr;}//还原到初始布局Upload.prototype.clearDailog = function(){ $(".ul-item").remove(); $(".ul-imgCont").css({ "text-align":"center" }); $(".ul-filepickerWrap").show(); $(".ul-uploadMsg p").hide(); this.setButHideOrShow($(".ul-input-enable"), false); this.setButHideOrShow($(".uploadSubmit"), true); $(".uploadSubmit").removeClass("ul-input-goOnUpload"); this.setButText($(".uploadSubmit"), '确认上传');}//清空进度条Upload.prototype.clearBar = function(){ $(".ul-bar-cur").animate({width:20},250); $(".ul-bar-tips").text("0%");}//设置进度条100%Upload.prototype.fullBar = function(){ $(".ul-bar-cur").animate({width:450},250); $(".ul-bar-tips").text("100%");}//清空上传成功失败提示信息Upload.prototype.clearUploadMsg = function(){ $(".ul-successCount").text("0"); $(".ul-errorCount").text("0");}//清空上传已选文件信息Upload.prototype.clearSelectMsg = function(){ $(".ul-selectcount").text("0"); $(".ul-selectsize").text("0" + "KB"); $(".ul-selectleftCount").text("30");}//修改状态为:停止上传Upload.prototype.stopUpload = function(){ this.uploadFlag = true;}//修改状态为:继续上传Upload.prototype.goOnUpload = function(){ this.uploadFlag = false;}//设置上传成功失败,提示信息Upload.prototype.setUploadMsg = function(){ $(".ul-successCount").text(this.filesArr.length); $(".ul-errorCount").text(this.fileFailureCount);}//设置已选文件个数,及容量Upload.prototype.setSelectMsg = function (){ var len = this.filesArr.length; var size = 0; for(var i = 0;i < len; i++){ var temp = this.filesArr[i]; if("ie" === temp.sendtype){ //var filePath = $("#ul-fileInput").val(); //var fileSystem = new ActiveXObject("Scripting.FileSystemObject"); //var file = fileSystem.GetFile (filePath); //size += file.Size; } size += temp.size; } var sizeVal = size/1000; var unit = "KB"; if(sizeVal >= 1000 ){ sizeVal = sizeVal/1000; unit ="M"; } $(".ul-selectcount").text(len); $(".ul-selectleftCount").text(this.config.maxCount - len); $(".ul-selectsize").text(sizeVal.toFixed(2) + unit);}//清除上传失败和成功个数Upload.prototype.clearFilesCount = function(){ this.fileFailureCount = 0; this.fileSuccessCount = 0;}//刷新进度条Upload.prototype.resetBarByOnce = function (that){ var curlen = this.fileFailureCount + this.fileSuccessCount; var totalLen = this.getFilesLen(); var newPrecent = curlen/totalLen; $(".ul-bar-tips").text(Math.floor(newPrecent*100) + "%"); $(".ul-bar-cur").animate({ width:Math.floor(newPrecent*450) },250);}//根据返回结果,调整图片的区域和状态Upload.prototype.LayoutDailog = function(file, data){ var index = file.index; var status= data.status; var curItem = $(".ul-imgCont [index='" + index + "']"); if(status){ curItem.removeClass("ul-waitItem"); curItem.removeClass("ul-failureItem"); curItem.addClass("ul-scItem"); var htmlStr = $(".ul-imgCont [index='" + index + "']")[0].outerHTML; $(".ul-list").append(htmlStr); if("ie" === file.sendtype){ $(".ul-list [index='" + index + "']")[0].filters.item("DXImageTransform.Microsoft.AlphaImageLoader").src = file.src; } curItem.remove(); return; } curItem.removeClass("ul-waitItem"); curItem.addClass("ul-failureItem");}//获取本次上传文件的数量Upload.prototype.getFilesLen = function(){ return $(".ul-item").length;}//获取本次上传失败的文件数量Upload.prototype.getfailureFilesLen = function(){ return $(".ul-failu").length;}//递归调用,逐个上传文件Upload.prototype.uploadSingle = function (uploadCallBack){ var that = this; //文件列表为空返回 if( 0 === this.filesArr.length || !this.filesArr[0]){ this.isComplete = true; this.uploadStatus = -1; this.resetErrorCount(); //全部上传成功 if(this.getFilesLen() === this.fileSuccessCount){ this.isAllSuccess = true; this.config.onAllSuccess && this.config.onAllSuccess(); } else {//部分成功 this.filesArr = this.filesFailureArr;//一次上传完成后,将fileArr重置为当前失败的fileArr this.config.onComplete && this.config.onComplete(); } return; } if(this.uploadFlag){//上传被终止,直接返回 return; } var file = this.filesArr[0]; var sendtype = file.sendtype; var CFG = this.config; this.uploadStatus = 1; uploadCallBack && uploadCallBack();//上传前的回调 if("h5" === sendtype){ var formdata = new FormData(); formdata.append("fileList", file); $.ajax({ url: CFG.url, type: 'POST', data: formdata, async: true, cache: false, contentType: false, processData: false, success: function (data) { that.monitorResponse(file, data); that.uploadSingle(); }, error: function (data) { //that.filesArr.shift(); that.filesFailureArr.push(file); that.uploadSingle(); } }); return; } else { //ie处理文件上传 window.frameCount = 0; var frameName = 'upload_frame_' + (frameCount++); var iframe = $('
调用示例:
//新增文件 $("body").on("click",".add_file", addFileHandler);//确认上传到其他文件夹 function uploadToOtherSubmit(event){ var floderId = $('.checkboxitem:checked').val(); var floderType = $(".moveFileDailog .check").attr("tab"); var newUrl = '/CloudUpload/upLoad/folder_id/' + floderId + '/type/' + tab_type + '.html'; uploader.updateUrl(newUrl); uploader.goOnUpload(); uploader.clearBar(); uploader.clearFilesCount(); uploader.uploadSingle(uploadCallback); } function uploadCallback() { $(".moveFileDailog .j_win_close").click(); } var uploader = null; function addFileHandler(event){ uploader = new upload.Upload().run({ url:'/CloudUpload/upLoad/folder_id/' + folder_id + '/type/' + folder_type + '.html', htmlStr:uploadDailogHtml, maxCount:30, formats:["jpg", "jpeg", "png"], onChange:function(){ if(uploader.filesArr.length){ $(".ul-filepickerWrap").hide(); $(".ul-imgCont").css({ "text-align":"left" }); uploader.setSelectMsg(); uploader.setButEnable($(".continueSelect")); uploader.setButHideOrShow($(".continueSelect"), true); } }, onSubmit:function(){ var len = uploader.filesArr.length; if(!len){ cloudUtil.cloudMsg("请选择文件.", null); } uploader.clearUploadMsg(); uploader.clearBar(); uploader.setUploadMsg(); uploader.setButDisable($(".continueSelect"));//禁用继续选择按钮 uploader.setButText($(".uploadSubmit"), '终止上传'); $(".uploadSubmit").addClass("ul-input-stopUpload"); }, onSuccess:function(file, data){ uploader.LayoutDailog(file, data); }, onComplete:function(){ uploader.clearFilesCount(); uploader.setButHideOrShow($(".continueSelect"), false);//隐藏继续选择 uploader.setButHideOrShow($(".reSelect"), true);//显示重新选择 uploader.setButText($(".uploadSubmit"), '继续上传'); }, onAllSuccess:function(){ uploader.setButHideOrShow($(".continueSelect"), false);//隐藏继续选择 uploader.setButHideOrShow($(".reSelect"), false);//隐藏重新选择 uploader.setButText($(".uploadSubmit"), '上传完成'); $(".ul-waitItem").remove(); //清空预览区域 uploader.setButDisable($(".uploadSubmit"));//禁用上传 setTimeout(function(){ uploader.destroy(); window.location.reload(); }, 2000); }, onClose:function(){ if(1 === uploader.uploadStatus){ uploader.stopUpload();//文件夹已满时,停止 var length = 0; min_win.comm_remind({ html:"文件未上传完,是否关闭?", yes_callback:function(){ uploader.destroy(); $(".mack_cont").eq(length-1).find(".j-win_no").click(); window.location.reload(); }, callback:function(){ length = $(".mack_cont").length; $(".mack_cont").eq(length-1).find(".j-win_no").addClass("j_win_close"); }, no_callback:function(){ uploader.goOnUpload(); uploader.uploadSingle(null); } }).run(); }else{ uploader.destroy(); } }, onFailure:function(index, data){ switch(data.errorCode){ case 401: cloudUtil.cloudMsg("个人云盘空间不足"); uploader.stopUpload();//空间不足时,停止上传 uploader.fullBar(); uploader.setFilesFailure(); ploader.setUploadStatus(-1); break; case 402: uploader.stopUpload();//文件夹已满时,停止 uploader.fullBar(); uploader.setFilesFailure(); uploader.setUploadStatus(-1); var length = 0; fullRemind = min_win.comm_remind({ html:' 该文件夹已满,是否新建主题 文件夹来存储剩余的图片? ', yes_callback:function(){ $(".mack_cont").eq(length-1).find(".j_win_close").click(); uploadFileToOthers(); }, callback:function(){ var length = $(".mack_cont").length; $(".mack_cont").eq(length-1).find(".j-win_no").addClass("j_win_close"); } }).run(); break; case 403: uploader.LayoutDailog(file, data); //console.log("上传的文件过大"); break; case 405: uploader.LayoutDailog(file, data); //console.log("文件格式不允许"); break; case 406: uploader.LayoutDailog(file, data); //console.log("生成缩略图失败"); break; case 407: uploader.LayoutDailog(file, data); //console.log("文件上传失败407"); break; } } }); }
参与含义就不一一说明,注释很清楚的。
然后首先实现的是,图片的预览功能(支持IE8)
IE8,9由于所谓的安全机制(网友都这么说,人云亦云)所以要使用滤镜实现预览;
通过捕获IE8,9的异常然后使用滤镜实现预览
需要主要的是,这行代码:
var curDom = document.getElementById("ul-fileInput");
curDom.select();
如果不选中,src始终为空;也就实现不了滤镜预览图片
1.由于插件按钮众多,状态也比较多所以提供了几个接口来操作按钮的显示隐藏,文件,状态(是否禁用)等。
setButDisable,setButEnable,setButText,setButHideOrShow
2.同样由于支持终止,继续上传;需要提供两个接口来操作上传的状态
stopUpload,goOnUpload
3.还有就是支持文件夹满时,上传到其他文件夹,所以需要提供一个更新url的接口
当然还有一些其他接口就不一一说明。
需要重点说明的是,IE8,9是采用表单提交方式;为了保证为了上传图片成功后,不刷新页面;使用了一个隐藏的iframe获取上传结果;
并监听iframede load事件获取上传的响应信息
//ie处理文件上传window.frameCount = 0;var frameName = 'upload_frame_' + (frameCount++);var iframe = $('
效果图如下:
上传:
终止上传:
继续上传,上传完成
文件满了:
正在上传时,关闭弹窗
大概效果就是这样,总结总结。