我们在前一章已经实现了Bootstrap框架---Uploadify插件----多张图片上传交互方式一 。

Bootstrap框架---Uploadify插件----多张图片上传交互方式一



本章主要关注多张(动态张数)图片上传在Bootstrap框架中的布局和实现。



我们在之前的文章中已经在SpringMVC基础框架的基础上应用了BootStrap的后台框架,在此基础上记录 多张图片上传在Bootstrap框架中的布局方式二和实现。


应用bootstrap模板


基础项目源码下载地址为:

SpringMVC+Shiro+MongoDB+BootStrap基础框架


我们在基础项目中已经做好了首页index的访问。
现在就在index.jsp页面和index的路由Controller上做修改,实现  多张图片上传在Bootstrap框架中的布局和实现。




效果图


实现思路是动态的初始化上传组件,需要多少张图片则初始化多少个组件。后台用List接收图片。


Bootstrap框架---Uploadify插件----多张图片上传交互方式二_spring mvc












Uploadify插件介绍

Uploadify是jQuery的一个上传插件,主要功能是批量上传文件,实现的效果非常不错,带进度显示。而且是Ajax的,省去了自己写Ajax上传功能的麻烦。不过官方提供的实例是PHP版本的,本文将详细介绍Uploadify在J2EE中的使用。


Uploadify的官网链接:http://www.uploadify.com/ 

里面可以看到PHP的示例,属性说明,以及控件下载地址。

分flash版(免费)和HTML5版(收费)。
官网上可以看到效果演示。


需要注意的是flash版在Safari不支持。

会报错:

Uploadify Flash Safari wrong JSESSIONID



处理起来比较麻烦。


所以建议使用HTML5版的。




HTML5版是收费的,在网上找到一个直接可用html5版本的JS。




下载地址:





uploadifyHtml5




下载解压后放到项目路径中




Bootstrap框架---Uploadify插件----多张图片上传交互方式二_Uploadify_02





这里还需要一张样式图片,我们放在/res/assets/img/demoUpload.jpg路径。






Bootstrap框架---Uploadify插件----多张图片上传交互方式二_上传_03







JSP页面


<%@ include file="./include/header.jsp"%>
<%@ taglib uri="com.data.web.view.function" prefix="cf" %>
<style>
/*uploadfive上传插件背景按钮图样式*/
.upload-image {
    height: 200px;
    width: 200px;
    background-image: url(/res/assets/img/demoUpload.jpg);
    background-color: white;
    background-repeat: no-repeat;
    background-size: contain;
    background-origin: content-box;
    background-position: center;
    background-size: contain;
    background-origin: content-box;
}
</style>






        <div id="page-wrapper">
            <div id="page-inner">




                <div class="row">
                    <div class="col-md-12">
                        <h1 class="page-header">
                            多张(动态)图片上传 <small>Uploadify</small>
                        </h1>
                    </div>
                </div>
                <!-- /. ROW  -->


     <form class="form-horizontal" id="base">
                 <input type="text" value="${pic.id}" id="id" name="id" hidden/>
                <div class="form-group">
                    <label for="name" class="col-sm-2 control-label">名称:</label>
                    <div class="col-sm-10">
                        <input type="text" class="form-control" id="name" name="name" value="${pic.name}"
                               placeholder="路线名称,例如:A-F">
                    </div>
                </div>
                <div class="form-group">
                    <label class="col-sm-2 control-label">描述:</label>
                    <div class="col-sm-10">
                        <textarea id="description" name="description" class="form-control"
                                  rows="8">${pic.description}</textarea>
                    </div>
                </div>
              <div class="form-group">
                    <label class="col-sm-2 control-label"> 图片 </label>
                    <div class="col-sm-10">
                        <table id="imagePaths" class="table table-striped table-bordered" cellspacing="0"
                               style="margin-bottom: 0;">
                            <thead>
                            <tr>
                                <th width="25%" class="text-center">标题</th>
                                <th width="*" class="text-center">图片地址</th>
                                <th width="25%" class="text-center">描述</th>
                                <th width="15%" class="text-center">操作</th>
                                <th width="15%"></th>
                            </tr>
                            </thead>
                            <tbody>
                            </tbody>
                        </table>
                        <script type="text/template" id="tpl_imagePaths">
                            <tr>
                                <td><input type="text" class="form-control" value="{title}" name="title"></td>
                                <td><input type="text" class="form-control" value="{filePath}" name="filePath"
                                           id="video_{rowIndx}_filePath"></td>
                                <td><input type="text" class="form-control" value="{description}" name="description">
                                </td>
                                <td>
                                    <button type="button" class="btn btn-danger imgUploader"
                                            id="video_{rowIndx}_uploader" style="width:120px;height:30px;">图片上传
                                    </button>
                                </td>
                                <td class="text-center">
                                    <button type="button" class="btn btn-default btn-sm row-add" title="添加行"><i
                                            class="fa fa-plus fa-fw"></i></button>
                                    <button type="button" class="btn btn-default btn-sm row-del" title="删除行"><i
                                            class="fa fa-minus fa-fw"></i></button>
                                </td>
                            </tr>
                        </script>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-sm-6 col-sm-offset-2">
                        <button type="button" class="btn btn-default cancel"
                                data-dismiss="modal">取消
                        </button>
                        <button type="button" class="btn btn-primary save"
                                data-loading-text="Saving...">确认
                        </button>
                    </div>
                </div>
           </form>  
                <!-- /. ROW  -->
            </div>
            <!-- /. PAGE INNER  -->
        </div>
        <!-- /. PAGE WRAPPER  -->


    


        
        




 <%@ include file="./include/footer.jsp"%>
<script type="text/javascript" src="/plugins/uploadify/jquery.uploadifive.js"></script>
<script type="text/javascript">


/**
 * 格式化字符串 第一个参数为格式化模板 format('this is a {0} template!', 'format');
 * format('this is a {0.message} template!', { message: 'format'}); 等同于
 * format('this is a {message} template!', { message: 'format' });
 */
$.format = function() {
var template = arguments[0],
  templateArgs = arguments,
  stringify = function(obj) {
    if (obj == null) {
      return '';
    } else if (typeof obj === 'function') {
      return obj();
    } else if (typeof obj !== 'string') {
      return JSON.stringify ? JSON.stringify(obj) : obj;
    }
    return obj;
  };
return template.replace(/\{\w+(\.\w+)*\}/g, function(match) {
  var propChains = match.slice(1, -1).split('.');
  var index = isNaN(propChains[0]) ? 0 : +propChains.shift();
  var arg, prop;
  if (index + 1 < templateArgs.length) {
    arg = templateArgs[index + 1];
    while (prop = propChains.shift()) {
      arg = arg[prop] == null ? '' : arg[prop];
    }
    return stringify(arg);
  }
  return match;
});
};








/**
 * jQuery form 扩展绑定数据
 * 
 */
$.fn.formSet = function(data, formGroup) {
var els = formGroup ? this.find('[form-group="' + formGroup + '"]') : this.find('[name]');
if (!els || !els.length) {
  return this;
}


els.each(function() {
  var $this = $(this),
    type = $this.attr('type'),
    name = $this.attr('name'),
    tag = this.tagName.toLowerCase();


  var value = _fnObjectGetPropertyChainValue(data, name);
  if (tag == 'input') {
    if (type == 'checkbox') {
      var v = $(this).val();
      if (v == 'on' || !v) {
        this.checked = value ? 'checked' : '';
      } else {
        this.checked = $.isArray(value) && value.indexOf(v) > -1 ? 'checked' : ''
      }
    } else if (type == 'radio') {
      this.checked = $this.val() == String(value) ? 'checked' : '';
    } else {
      $this.val(value);
    }
  } else if ('|select|textarea|'.indexOf('|' + tag + '|') > -1) {
    $this.val(value);
  } else {
    $this.html(value);
  }
});
return this;
};












/**
 * jQuery form 扩展获取数据
 */
$.fn.formGet = function(opts) {
opts = $.extend({}, opts);
var data = {},
  els = opts.formGroup ? this.find('[form-group="' + opts.formGroup + '"]') : this.find('[name]');
if (!els || !els.length) {
  return data;
}


var fnSetValue = (function(emptyToNull) {
  return emptyToNull ? function(obj, propertyChain, value, allowMulti) {
    value !== '' && _fnObjectSetPropertyChainValue(obj, propertyChain, value, allowMulti)
  } : _fnObjectSetPropertyChainValue
})(opts.emptyToNull);


els.each(function() {
  var $this = $(this),
    type = $this.attr('type'),
    name = $this.attr('name'), // 可能为属性链
    tag = this.tagName.toLowerCase();
  if (tag == 'input') {
    if (type == 'checkbox') {
      var v = $(this).val();
      if (v == 'on' || !v) {
        fnSetValue(data, name, $(this).prop('checked'));
      } else {
        $(this).prop('checked') && fnSetValue(data, name, v, true);
      }
    } else if (type == 'radio') {
      this.checked && fnSetValue(data, name, $this.val());
    } else {
      fnSetValue(data, name, $this.val());
    }
  } else if ('|select|textarea|'.indexOf('|' + tag + '|') > -1) {
    fnSetValue(data, name, $this.val());
  } else {
    fnSetValue(data, name, $.trim($this.text()));
  }
});
return data;
};








/**
 * 内部私有方法
 */
var _fnObjectGetPropertyChainValue = function(obj, propertyChain) {
  /* 获取属性链的值 */
  if (!obj) return;
  if (!propertyChain) return obj;
  var property,
    chains = propertyChain.split('.'),
    i = 0,
    len = chains.length;
  for (;
    (property = chains[i]) && i < len - 1; i++) {
    if (!obj[property]) return;
    obj = obj[property];
  }
  return obj[property];
},
_fnObjectSetPropertyChainValue = function(obj, propertyChain, value, allowMulti) {
  /* 设置属性链的值 */
  if (!obj || !propertyChain) return;
  var property,
    chainObj = obj,
    chains = propertyChain.split('.'),
    i = 0,
    len = chains.length;
  for (;
    (property = chains[i]) && i < len - 1; i++) {
    if (!chainObj[property]) {
      chainObj[property] = {};
    }
    chainObj = chainObj[property];
  }
  // 改进版:checkbox的多选可以组合为数组
  if (!allowMulti || chainObj[property] === undefined) {
    chainObj[property] = value;
  } else {
    var pv = chainObj[property];
    if ($.isArray(pv)) {
      pv.push(value);
    } else {
      chainObj[property] = [pv, value];
    }
  }
  return obj;
};








  
    $(document).ready(function () {


    	 /*BEGIN-图片-BEGIN*/
        var $imagePathsBody = $('#imagePaths tbody'),
            _imagePathsTpl = $('#tpl_imagePaths').html();
        // 初始化图片列表
        (function (imges) {
            if (!imges) {
                imges = [{}];
            }
            for (var i = 0, item; item = imges[i]; i++) {
                $imagePathsBody.append($($.format(_imagePathsTpl, {rowIndx: i})).formSet(item));
            }
        })(${cf:toJSON(base.imagePaths)}
        );


        function _getImagePaths() {
            var imges = [];
            $imagePathsBody.find('tr').each(function (index, ele) {
                var row = $(ele).formGet();
                if (row.title || row.filePath || row.duration) {
                    imges.push({"title": row.title, "filePath": row.filePath, "description": row.description});
                }
            });
            return imges;
        };


        $imagePathsBody.on('click', '.row-del', function () {
            var $tr = $(this).closest('tr');
            if ($tr.parent().children().length == 1) return;
            $tr.remove();
        }).on('click', '.row-add', function () {
            var $tr = $(this).closest('tr');
            $imagePathsBody.append($($.format(_imagePathsTpl, {rowIndx: $tr.parent().children().length})).formSet({}));
            initUploader();
        });
        /*END-图片-END*/
        /* BEGIN-图片上传-BEGIN */
        var initUploader = function () {
            $('.imgUploader').each(function (index) {
            	
            	
                var selfId = this.id;
                var $input = $('#' + selfId.replace('_uploader', '_filePath'));
                console.log($input);
                
                var $uploader = $('#uploadifive-video_' +index+'_uploader' );
                if(!($uploader.length>0)){
                $(this).uploadifive({
                    'height': 30,
                    'width': 120,
                    'fileSizeLimit': '500KB',
                    'uploadScript': '/upload;_sid=${pageContext.session.id}',
                    'buttonClass': 'btn btn-danger',
                    'buttonText': '图片上传',
                    'multi': false,
                    'removeCompleted': true,
                    'onUploadComplete': function (file, data, response) {
                        data = JSON.parse(data);
                        if (data.code) {
                            var url = '${hostname}' + data.result;
                            $input.val(url).change();
                        }
                    }
                });
                }
                
                
                
                
            });
        };
        initUploader();


        //图片容器的显示图片切换
        var updateUploadButtonBackground = function (id, path) {
            path ? $('#uploadifive-' + id).css('background-image', 'url(' + path + ')') : $('#uploadifive-' + id).css('background-image', 'url(/res/assets/img/demoUpload.jpg)');
        };






        //删除图片    
        $('button.delete').on('click',
            function () {
                if (confirm('是否删除图片?')) {
                    var selfId = this.id;
                    var $input = $('#' + selfId.replace('_delete', '_filePath'));
                    var uploaderId = selfId.replace('_delete', '_uploader');
                    updateUploadButtonBackground(uploaderId, '/res/assets/img/demoUpload.jpg');
                    $input.val('');
                }
            });
        
        
        /*END-图片上传-END*/
        $('button.save').on('click', function () {
            debugger;
            var data = $('#base').formGet();
            if (_getImagePaths().length)
                data.imagePaths = _getImagePaths();
            $.ajax({
                type: "POST",
                url: "/pic/save",
                contentType: "application/json",
                data: JSON.stringify(data),
                success: function (result) {
                    console.log(result);
                    if (!result.code) {
                        $('#base').formSet(data);
                    } else {
                        alert(result.msg);
                    }
                },
                error: function (result) {
                    alert("出错了,请稍后重试");
                }
            });
        });



    });


</script>




</body>


</html>






返回信息辅助实体类

AjaxResult.java


package com.test.util;

import org.springframework.data.annotation.Transient;

/**
 * AjaxResult
 * 
 * 标准化的ajax响应, 取代之前直接返回结果的方式。
 * 
 */
public class AjaxResult {

	@Transient
	public static final int CODE_SUCCESS = 1;
	@Transient
	public static final int CODE_FAILURE = 0;
	@Transient
	public static final AjaxResult RESULT_ERROR = new AjaxResult(
			CODE_FAILURE, "执行出错了", null);
	@Transient
	public static final AjaxResult RESULT_SUCCESS = new AjaxResult(
			CODE_SUCCESS, null, null);
	@Transient
	public static final AjaxResult RESULT_INVAILD_PARAMETER = new AjaxResult(
			CODE_FAILURE, "参数格式不正确", null);

	private int code = CODE_FAILURE;
	private String error;
	private Object result;

	public static AjaxResult resultError(String error) {
		return new AjaxResult(CODE_FAILURE, error, null);
	}

	public static AjaxResult resultSuccess(Object result) {
		return new AjaxResult(CODE_SUCCESS, null, result);
	}

	/**
	 * @param code
	 * @param error
	 * @param result
	 */
	public AjaxResult(int code, String error, Object result) {
		super();
		this.code = code;
		this.error = error;
		this.result = result;
	}

	/**
	 * @return the code
	 */
	public int getCode() {
		return code;
	}

	/**
	 * @param code
	 *            the code to set
	 */
	public void setCode(int code) {
		this.code = code;
	}

	/**
	 * @return the error
	 */
	public String getError() {
		return error;
	}

	/**
	 * @param error
	 *            the error to set
	 */
	public void setError(String error) {
		this.error = error;
	}

	/**
	 * @return the result
	 */
	public Object getResult() {
		return result;
	}

	/**
	 * @param result
	 *            the result to set
	 */
	public void setResult(Object result) {
		this.result = result;
	}
}






JSONResult.java


package com.test.util;

/**
 * JSONResult
 * 标准化的JSON响应
 * 
 * <pre>
 * {@link JSONResult#success(Object)}
 * {@link JSONResult#error(String)}
 * </pre>
 * 
 * 
 */
public class JSONResult {

	/**
	 * 成功的代码
	 */
	public static final int			CODE_SUCCESS			= 0;

	/**
	 * 错误的代码,可根据错误类型进行详细分类
	 */
	public static final int			CODE_ERROR				= -1;

	/**
	 * 空白的成功响应
	 */
	public static final JSONResult	RESULT_SUCCESS_NO_DATA	= new JSONResult(CODE_SUCCESS, null, null);

	private int						code;
	private String					msg;
	private Object					data;

	/**
	 * 创建一个成功的响应
	 * 
	 * @param data
	 * @return
	 */
	public static JSONResult success(Object data) {
		return new JSONResult(CODE_SUCCESS, null, data);
	}

	/**
	 * 创建一个错误的响应
	 * 
	 * @param msg
	 * @return
	 */
	public static JSONResult error(String msg) {
		return new JSONResult(CODE_ERROR, msg, null);
	}

	/**
	 * @param code
	 * @param msg
	 * @param data
	 */
	public JSONResult(int code, String msg, Object data) {
		this.code = code;
		this.setMsg(msg);
		this.data = data;
	}

	public int getCode() {
		return code;
	}

	public JSONResult setCode(int code) {
		this.code = code;
		return this;
	}

	public Object getData() {
		return data;
	}

	public JSONResult setData(Object data) {
		this.data = data;
		return this;
	}

	public String getMsg() {
		return msg;
	}

	public JSONResult setMsg(String msg) {
		this.msg = msg;
		return this;
	}

}





Pic.java

package com.test.domain.entity;


import java.util.List;


public class Pic {
private String id;
private String name;
private String description;
private List<Image> imagePaths; // 图片
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<Image> getImagePaths() {
return imagePaths;
}
public void setImagePaths(List<Image> imagePaths) {
this.imagePaths = imagePaths;
}



}






Image.java


package com.test.domain.entity;

/**
 * 图片 Image
 */
public class Image {

    private String title; //名称
    private String description; //描述
    private String filePath; //文件地址

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public Image() {
    }

    /**
     * @param title
     * @param filePath
     */
    public Image(String title, String filePath) {
        this.title = title;
        this.filePath = filePath;
    }
}













页面路由控制器

IndexController.java


package com.test.web.controller;

import java.io.IOException;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.test.domain.entity.Pic;
import com.test.util.JSONResult;

/**
 * IndexController
 * 
 * 
 */
@Controller
public class IndexController {

	@RequestMapping("/")
	public String index(Model model) throws IOException {
          model.addAttribute("hostname", "http://127.0.0.1:8080/");
		return "/index";
	}

	
	@RequestMapping("/pic/save")
	@ResponseBody
	public JSONResult saveMigrateLine(@RequestBody Pic pic) {
		//保存pic记录
		//int result = save(pic);
		int result =1;
		return result > 0 ? JSONResult.success(pic)
				:JSONResult.error("保存失败!");
	}
}









文件上传接收控制器

UploadController.java


package com.test.web.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.test.util.AjaxResult;

/**
 * 通用的上传(保存到本地服务器)
 * 
 */
@Controller
public class UploadController {

    private final Logger logger = LoggerFactory.getLogger(getClass().getName());

    /**
     * 上传到服务器,文件名称随机生成(基本可以保证不重复)
     * 
     * @param request
     * @param response
     * @return AjaxResult 存储的文件的相对路径
     * @throws IOException 
     */
    @RequestMapping("/upload")
    @ResponseBody
    public AjaxResult upload(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
        	String filePath="/uploadfile";
        	//获取文件存储路径  (虚拟目录映射为本机服务器的实际目录)
            String path = request.getSession().getServletContext().getRealPath(filePath); 
        	//如果保存在ROOT里,重新发包后静态资源会丢失,所以保存在ROOT包的上级路径webapp中
            path=path.replace("ROOT\\", "");
            String fileNameResult ="";
            // 判断enctype属性是否为multipart/form-data  
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);  
            if (!isMultipart)  
                throw new IllegalArgumentException(  
                        "上传内容不是有效的multipart/form-data类型.");  
      
            // Create a factory for disk-based file items  
            DiskFileItemFactory factory = new DiskFileItemFactory();  
      
            // Create a new file upload handler  
            ServletFileUpload upload = new ServletFileUpload(factory);  
            // Parse the request  
            List<?> items = upload.parseRequest(request);  
      
            Iterator iter = items.iterator();  
            while (iter.hasNext()) {  
                FileItem item = (FileItem) iter.next();  
      
                if (item.isFormField()) {  
                    // 如果是普通表单字段  
                    String name = item.getFieldName();  
                    String value = item.getString();  
                    // ...  
                } else {  
                    // 如果是文件字段  
                    String fieldName = item.getFieldName();  
                    String fileName = item.getName();  
                    String contentType = item.getContentType();  
                    boolean isInMemory = item.isInMemory();  
                    long sizeInBytes = item.getSize();  
                    String fileExt = fileName.substring(fileName.lastIndexOf('.'));
                    String fileNameNew =getFileNameNew()+fileExt;
                    fileNameResult=fileNameNew;
                    //保存到本地
                        InputStream uploadedStream = item.getInputStream();  
                        savePic(path,uploadedStream,fileNameNew);
                        uploadedStream.close();  
                }  
            }  
            
            return AjaxResult.resultSuccess(filePath+"/"+fileNameResult);
        } catch (FileUploadException e) {
            logger.warn(e.getMessage(), e);
            return AjaxResult.resultError(e.getMessage());
        }
    }
    
    private void savePic(String path,InputStream inputStream, String fileName) {
    	 
        OutputStream os = null;
        try {
          // 2、保存到临时文件
          // 1K的数据缓冲
          byte[] bs = new byte[1024];
          // 读取到的数据长度
          int len;
          // 输出的文件流保存到本地文件
     
          File tempFile = new File(path);
          if (!tempFile.exists()) {
            tempFile.mkdirs();
            //如果图片是保存在ROOT项目外,首次创建目录,tomcat加载需要时间,所以需要延迟10秒
            Thread.sleep(10000);
          }
          os = new FileOutputStream(tempFile.getPath() + File.separator + fileName);
          // 开始读取
          while ((len = inputStream.read(bs)) != -1) {
            os.write(bs, 0, len);
          }
     
        } catch (IOException e) {
          e.printStackTrace();
        } catch (Exception e) {
          e.printStackTrace();
        } finally {
          // 完毕,关闭所有链接
          try {
            os.close();
            inputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }

    private String getFileNameNew() {
        SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return fmt.format(new Date());
      }

}





目前上传的文件是接收后保存到本地服务器中,如果要上传到远程服务器或者七牛云等,只需要修改UploadController.java即可。



这里只给出了 uploadify 上传图片的案例。 但其实 这个DEMO也可以用于上传 zip等文件。 只需要稍微调整jsp页面把图片显示去掉即可。