WebUploader 的 jQuery 封装

在开发 kayura-uasp 项目时,其中的附件库管理模块,需要使用到文件上传组件。

最后通过各种比较,发现 WebUploader 这个组件很不错,还支持 md5 校验,PC端移动端,都可以支持,初步使用比较满意。

于是乎就有了下面的代码,来支持文件上传。

uploader = WebUploader.create({
    swf: '${root}/res/webuploader/Uploader.swf',
    server: '${root}/file/upload.json',
    pick: '#upload',   // 上传按钮Id
    auto: true,
    formData:{
        folderId: selectNode.id   // 上传目录Id
    }
});

uploader.on('uploadFinished', function (file, response) {
    _findFiles(selectNode.id);     // 上传完成后刷新目录.
});

后来通过调整了  webuploader.css 样式文件,去制了添加的按钮颜色。这下就保持了绑定按钮的原汁原味了。

上面的代码功能一切正常,能正确上传文件,并且在上传完成后,刷新目录文件列表。但这并没有完,新的想法和需求随之而来,归纳为以下几个方面;

  1. 我没有办法看到文件的上传过程,只有静静的等待上传完成后的刷新,才知道已经上传完成。所以我需要一个显示文件上传进度条,并且只有在上传过种中才显示,上传完成后随之消失。不能破坏整体页面结构。
  2. 有时仅需要在一个页面中显示一个上传按钮,点击它就进行上传,完成后提供一个事件。
  3. 有时在表单页面中,需要多个上传按钮,并且每个按钮上传后均在按钮下面显示已上传的文件列表。显示的文件列表也可以通过参数控制来决定显示方式。
  4. 要是每个上传按钮,都要写 WebUploader.create 代码来处理,那么显示使用非常不友好,冗余代码会很多,封装成 jQuery 组件应该是个不错的方式。

根据上面的需求,创建了一个 juasp-uploader.js 文件,并且封装成了一个 jQuery 上传组件。那么现在要使用它来绑定上传按钮时,代码就变成了下面的样式:

// 上传按钮Id
$("#upload").uploader({
  formData : {
    folderId: selectNode.id  // 上传目录Id
  },
  onFinished : function (){
    _findFiles(selectNode.id); // 上传完成后刷新目录.
  }
});

现在是不是简洁了很多。

如果一个表单需要绑定多种类型的附件时,也可以使用以下代码来完成。

<script type="text/javascript">
$(function(){
  $("#fileUpload1").uploader({
    showlist : true,    // 显示列表
    showinfos : false,  // 不显示文件信息(默认显示)
    actions : {
      remove : true     // 提供移除功能(仅能移除自己上传的)
    },
    formData : {
      category: '合同',          // 表单附件分类
      bizId: $("#bizId").val()   // 表单主键Id
    }
  });
  $("#fileUpload2").uploader({
    showlist : true,     // 显示列表
    formData : {
      category: '归档',
      bizId: $("#bizId").val()
    }
  });
});
</script>

<!-- 业务表单主键 Id -->
<input type="hidden" id="bizId" name="bizId" value="8F6528BEEAB411E5AD4E10BF48BBBEC9" />
<k:panel id="p1" title="列表上传(合同)" style="width: 600px; padding: 10px">
    <k:linkbutton id="fileUpload1" iconCls="icon-upload" text="上传文件" />
</k:panel>
<div style="margin: 5px;"></div>
<k:panel id="p2" title="列表上传(归档)" style="width: 600px; padding: 10px;">
    <k:linkbutton id="fileUpload2" iconCls="icon-upload" text="上传文件" />
</k:panel>

现在来看看最终附件上传及显示效果。

上传页面示例

现在基本满足了开发与使用的需求。当然在这并不是最终,现在又有了新的想法与需求要去解决了。

  1. md5校验(秒传)还需要实现;
  2. 还有需要针对图片上传的预览以及浏览功能实现;

最后再看看目录已经完成的 juasp-uploader.js 文件里有些什么内容吧,它可是一个标准的 jQuery  组件。

/**
 * 使用 WebUploader 的封装文件/图片上传组件.
 * 
 * Copyright 2015-2016 the original author or authors.
 * HomePage: https://kayura.net
 */

// 文件上传组件.
(function($, win) {

  var $queue = null;
  var MB = 1024 * 1024, GB = MB * 1024;
  var icons = ['avi','bmp','bz2','db','doc','docx','exe','gif','jpg','jpge','mov',
               'mp3','mp4','mpg','mpp','msi','pdf','png','ppt','pptx','rar','rdp',
               'sql','tar','txt','vsd','wps','xls','xlsx','zip','htm','html','mht',
               'mhtml','tif','tiff','iso','jar','rp','oom','pdm','psd','xml','xsm',
               'wma','wmv','vtx','udl','reg','chm','ini'];
    
  // 计算 Web 项目路径.
  var hostPath = win.location.href.substring(0, win.location.href.indexOf(win.location.pathname));
  var projectName = win.location.pathname.substring(0, win.location.pathname.substr(1).indexOf('/') + 1);
  var appPath =  hostPath + projectName;
  
  function newid() {
    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  }
  
  juasp.getIconName = function(postfix){
    
    if(!juasp.isEmpty(postfix)){
      var i = icons.indexOf(postfix.toLowerCase());
      if(i>=0){
        return icons[i];
      } else {
        return "unknown";
      }
    } else {
      return "unknown";
    }
  }
  
  juasp.removefile = function (frid, fileName){
    
    juasp.post(appPath + '/file/remove.json', 
        { id : frid, isBiz : 1 },
        { success: function(r) {
          var t = $("#fc_" + frid);
          t.fadeOut('fast', function(){ t.remove(); });
          juasp.info("文件 [" + fileName + "] 已经被移除。");
        }
    });
  }
  
  // ...
  function init(target){
    
        if (!WebUploader.Uploader.support()) {
            var error = "上传控件不支持您的浏览器!请尝试升级flash版本或者使用Chrome引擎的浏览器。";
            $(target).text(error);
            return;
        }

    var opts = $.data(target, 'uploader').options;
    var targetId = target.id;
    var $target = $(target);

    if ($queue == null) {
      $queue = $('<div class="webuploader-filequeues-list"></div>');
      $queue.appendTo("body");
    }

    if(opts.showlist) {
      var $list = $("<div style='list-style: none;padding: 5px;'></div>");
      $target.after($list);
    }
    
    if(!opts.actions.upload){
      $target.remove();
    }
    
    // 下载文件列表.
    if(opts.showlist) {
      
      juasp.post(appPath + '/file/find.json', 
          {
            bizId : opts.formData.bizId,
            category : opts.formData.category,
            tags : opts.formData.tags
          },
          { success: function(r) {
            for(var i in r.data){
              appendFileList(r.data[i]);
            }
          }
      });
    }
    
    var uploader = WebUploader.create($.extend({ 
      pick : '#' + targetId,
      formData : opts.formData
    }, opts.innerOptions));

        $.data(target, 'uploader', { options: opts, uploader : uploader });
    
        uploader.on('uploadFinished', function () {

        	if(opts.onFinished()) {
        		opts.onFinished();
        	}
        });
        
        uploader.on('filesQueued', function (files) {

        	for(var i in files){
        		$queue.append("<li id='li_" + files[i].id + "' class='webuploader-filequeues-item'>" + files[i].name + "</li>"); 
        	}
        });
        
        uploader.on('uploadSuccess', function (file, response) {
        	
        	var t = $queue.find("#li_" + file.id);
        	t.html("<span style='color:#A9A9A9'>" + file.name + " 上传完成.</span>");
        	
            setTimeout(function(){
            	t.fadeOut('slow', function(){ t.remove(); });
            },3000);
        });
        
        uploader.on('uploadProgress', function (file, percentage) {
        	
        	var t = $queue.find("#li_" + file.id);
        	
        	if(percentage == 1){
            	t.html("<span style='color:#A9A9A9'>" + file.name + " 上传完成.</span>");
        	} else {
            	t.html("<span style='color:#0000FF'>" + file.name + " 上传中 " + Math.round(percentage * 100) + " %</span>");
        	}
        });
        
        uploader.on('error', function(type){
        	
            /**
             * type {String} 错误类型。
             * Q_EXCEED_NUM_LIMIT 在设置了fileNumLimit且尝试给uploader添加的文件数量超出这个值时派送。
             * Q_EXCEED_SIZE_LIMIT 在设置了Q_EXCEED_SIZE_LIMIT且尝试给uploader添加的文件总大小超出这个值时派送。
             * Q_TYPE_DENIED 当文件类型不满足时触发。
             */

        	if(type == "Q_EXCEED_NUM_LIMIT"){
    			juasp.errortips("添加文件时超出了本次限制的最大文件数。");
        	}
        	
        });
        
        uploader.on('uploadAccept', function(o, r){

    		if(opts.showlist) {
    			if(r.type == juasp.SUCCESS) {
        			r.data.isUploader = true;
        			appendFileList(r.data);
        		} else {
        			juasp.errortips(r.message);
            	}
        	}
        });
        
        function appendFileList(data){
        	
        	var html = '<li id="fc_' + data.frId + '" class="webuploader-fileitems">' ;
        	
        	if(opts.showicon){
        		html += '<img class="webuploader-fileitem-icon" src="'+ appPath + '/res/images/types/' + juasp.getIconName(data.postfix)+ '.png">';
        	}
        	
        	html += '<a href="' + appPath + '/file/get?id=' + data.frId + '" '
        	html += 'title="文件大小: ' + data.fileSize + '; 上传者: ' + data.uploaderName + ';">' + data.fileName + '</a>';
        	
        	if(opts.showinfos){
        		html += "<span class='webuploader-fileitem-info'>(大小: " + data.fileSize + ", " + data.uploadTime + ")</span>";
        	}
        	
        	if(opts.actions.remove && data.isUploader) {
        		html += '<a href="javascript:void(0)" onclick="juasp.removefile(\'' + data.frId + '\',\'' + data.fileName + '\')">'; 
        		html += '<img class="webuploader-fileitem-action-icon" src="'+ appPath + '/res/images/icons/clear.png"></a>';
        	}
        	
        	html += '</li>';
    		$list.append(html);
        }
  }

  var webuploader = function(options, param){
    
    if (typeof options == 'string'){
      return webuploader.methods[options](this, param);
    }
    
    options = options || {};
    return this.each(function(){
      var state = $.data(this, 'uploader');
      if (state){
        $.extend(true, state.options, options);
      } else {
        var opts = $.extend(true, {}, webuploader.defaults, options);
        state = $.data(this, 'uploader', {
          options: opts
        });
        init(this);
      }
    });
  };
  
  // 上传组件支持方法.
  webuploader.methods = {
    setFormData: function(jq, param){
      var state = $.data(jq[0], 'uploader');
      if (state) {
        var opts = state.options;
        opts.formData = $.extend(true, {}, opts.formData, param);
        state.uploader.options.formData = opts.formData;
      }
    }
  };
  
  // 上传组件默认属性.
  webuploader.defaults = {
    onFinished : function () { },
    onSuccess : function (file, res) { } ,
    showlist : false,
    showicon : true,
    showinfos : true,
    actions : {
      upload : true,
      remove : true
    },
        formData: {				// 附件上传可接收的参数范本示例.
        	bizId : '',			// ● 绑定业务表单Id.
        	category : '',		// ● 该附件在表单中的分类.
        	folderId : '',		// ● 可指定添加到的文件夹.在指定了bizId时,该值将被忽略.
        	serial : 0,			// ● 排序码.
        	allowChange : 0,	// ● 是否允许编辑.
        	isEncrypt : 0,		// ● 是否加密文件.
        	tags : ''			// ● 自定义标签,使用逗号间隔.
        },
    innerOptions : {
      swf: appPath + '/res/webuploader/Uploader.swf',
      server: appPath + '/file/upload.json',
      auto: true, 					// 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。
          fileNumLimit: 999,				// 验证文件总数量, 超出则不允许加入队列。
          fileSizeLimit: 100 * MB,		// 验证文件总大小是否超出限制, 超出则不允许加入队列。
          fileSingleSizeLimit: 20 * MB,	// 验证单个文件大小是否超出限制, 超出则不允许加入队列。
      fileVal : "file",				// 设置文件上传域的name。
      method: "POST",					// 文件上传方式,POST或者GET。
      duplicate: true
    }
  };
  
  $.fn.uploader = webuploader;
  
}(jQuery, window));

更多详细的代码可以下载 kayura-uasp 项目来查看,比较上传服务端是怎么开发的。

kayura-uasp 项目 Git 地址:https://github.com/KayuraTeam/kayura-uasp