第一次使用bootstrap fileinput碰到了许多坑,做下记录

需求

  1. 本次使用bootstrap fileinput文件上传组件,主要用来上传和预览图片。作为一个后台管理功能,为某个表的某个字段,设置1对n的图片记录
  2. 网上搜索相关文章大多是一个简单的上传功能,对图片文件预览显示,前后端交互并没多少详细描述

实现功能

  1. 后台界面例子



2. 在新增和编辑里,需要添加图片上传显示需求,在这里我设置的字段名以_img结尾的图片都会在编辑新增里显示bootstrap fileinput组件



3. 点击选择,选择文件后会变成一下情况

多出个上传按钮,图片也会多几个按钮,我选择了删除和放大图片的按钮,还可以显示图片单独上传按钮,这里我把它去掉了,统一在下方点击上传时,全部上传。这里重点说下,我选择的异步上传方式,选择多个图片,上传后台组件采用的是多个图片轮询一张一张上传

4. 点击放大按钮



5. 点击删除按钮,会调用删除方法,点击上传按钮,显示如下:



6. 若本来就保存了图片,在点击修改的时候,我需要回显图片,显示如下

在没有继续上传图片的时候,它不会显示上传按钮,图片左下角小图标会有所变化

7. 点击保存后



8. 以上为需要实现的具体需求,下面分析代码

问题和解决方案

前端

  1. 此处只列出关键代码

    html:
<#elseif modifyField["field_name"]?ends_with("_img")?string == "true">
<div class="form-group">
<label class="control-label">${modifyField["field_description"]}:</label>
<!-- 用来作为form表单提交上传图片返回id的集合,这里我采用`,`隔开的字符串形式保存 -->
<input type="text" hidden id="${modifyField["field_name"]}" name="${modifyField["field_name"]}" value=""/>
<!-- 用来初始化上传组件 -->
<input type="file" name="file" id="${modifyField["field_name"]}_uploadfile" multiple="multiple" class="file-loading" />
</div>
<#else>

js:

/**
* 销毁图片上传组件
* @param initUrl
*/
function destroyUploadImg(){
//这里我用jquery找到我约定的上传组件,使用_uploadfile结尾,并遍历销毁
$("#dlg input[id$='_uploadfile']").each(function(index,html){
var upfile = $(html);
upfile.fileinput("destroy");
});
} /**
* 初始化fileinput组件
*/
var getFileInput = function(){
//初始化方法,同样找到约定组件遍历
$("#dlg input[id$='_uploadfile']").each(function(index,html){
var upfile = $(html); var imgId = upfile.attr("id").substring(0,upfile.attr("id").lastIndexOf("_"))
var val = $("#"+imgId).val();
var preImageList = val.split(","); var initialPreview = [];
var initialPreviewConfig = [];
for (var i = 0; i < preImageList.length; i++) {
if(preImageList[i] === "") {
preImageList.splice(i,1);
continue
}
initialPreview.push("${request.contextPath}/showPic?image_id=" + preImageList[i] + "&count=" + Math.random()) var previewConfig = new Object();
previewConfig.url = "${request.contextPath}/deleteImg.do";
previewConfig.key = preImageList[i]; var imageId = preImageList[i];
previewConfig.extra = {"image_id": imageId,"pRow":JSON.stringify(pRow),"currPageId":${curr_page_id},"updateCol":imgId};//上传的额外参数,会作为post请求参数提交
initialPreviewConfig.push(previewConfig);
}
if(upfile.length > 0) {
//元素存在时执行的代码
upfile.fileinput({
theme: 'fa',
allowedFileExtensions: ['jpg', 'png', 'gif'],//接收的文件后缀
// maxFileSize:0,
language: 'zh', //设置语言
uploadUrl: "${request.contextPath}/uploadImg.do", //上传的地址
uploadAsync: true, //默认异步上传
uploadExtraData:{"currPageId":${curr_page_id},"pRow":JSON.stringify(pRow),"colName":imgId},//上传额外的post请求参数
// showUpload: false, //是否显示上传按钮
showRemove : false, //显示移除按钮
showPreview : true, //是否显示预览
showCaption: false,//是否显示标题
browseClass: "btn btn-primary", //按钮样式
dropZoneEnabled: true,//是否显示拖拽区域
maxFileCount: 10, //表示允许同时上传的最大文件个数
enctype: 'multipart/form-data',
validateInitialCount:true,
overwriteInitial: false,//是否在上传下一个文件的时候覆盖前一个
initialPreviewAsData: true,//实现初始化预览
removeFromPreviewOnError:true,//碰到上传错误的文件,不显示在框内
layoutTemplates:{
actionUpload:'' //设置为空可去掉上传按钮
//actionDelete:'' //设置为空可去掉删除按钮
},
initialPreview: initialPreview,//初始化预览文件的地址集合
initialPreviewConfig:initialPreviewConfig//初始化预览文件配置
}).on('filepreupload', function(event, data, previewId, index) { //上传中
var form = data.form, files = data.files, extra = data.extra,
response = data.response, reader = data.reader;
console.log('文件正在上传');
}).on("fileuploaded", function (event, data, previewId, index) { //一个文件上传成功
console.log('文件上传成功!');
if(data.response['result'] === "success"){
var imageId = data.response['imageId'];
preImageList.push(imageId);
var preImageStr = preImageList.join(",");
$("#"+imgId).val(preImageStr);
}else{
return false;
}
}).on('fileerror', function(event, data, msg) { //一个文件上传失败
console.log('文件上传失败!');
}).on('filesuccessremove', function(event, id) {
console.log('文件删除');
}).on('filedeleteerror', function(event, id) {
console.log('文件删除错误');
}).on('filedeleted', function(event, key,data) {
//删除返回结果
$("#"+imgId).val(data.responseJSON["imageIdStr"]);
$('#dg').datagrid('reload');
});
}
});
};

明白怎么回事,使用起来还是蛮简单的,就简单的两个创建和销毁方法,注释写的也蛮详细了,除了业务逻辑,组件的必要注释都在了

2. 重点的地方

- 文件上传只要填写上传地址和额外参数

- 在fileuploaded方法中做上传完毕的业务逻辑

- 文件删除只需要在预览配置里加上删除的地址和额外参数,新增的不管有没有上传的文件,删除的仅仅是前端

- 在filedeleted方法中做删除完毕的业务逻辑

后端

  1. 先上代码段
 /**
* 显示图片
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value = {"/showPic.do", "showPic"})
public ResponseEntity<byte[]> showPic(HttpServletRequest request) throws Exception {
HashMap<String, Object> hashMap = this.getHashMap(request);
String imageId = (String) hashMap.get("image_id");
Map<String, Object> imgConditions = new HashMap<>();
imgConditions.put("image_id", imageId);
Map<String, Object> imgInfo = tableService.getRecord(Arrays.asList("image_path", "uuid", "image_type"), TableConstant.TB_IMG, imgConditions);
String imageType = (String) imgInfo.get("image_type");
String fullPath = imgInfo.get("image_path") + (String) imgInfo.get("uuid") + "." + imageType; FileInputStream inputStream = new FileInputStream(fullPath);
int available = inputStream.available();
byte[] data = new byte[available];
inputStream.read(data);
inputStream.close();
HttpHeaders he = new HttpHeaders();
he.setContentType(MediaType.valueOf("image/" + imageType));
log.info("imageId:" + imageId + "读取");
return new ResponseEntity<>(data, he, HttpStatus.OK);
} /**
* 上传图片
* @param file
* @return
*/
@RequestMapping(value = "/uploadImg.do")
public @ResponseBody
Map<String, Object> upload(HttpServletRequest request,@RequestParam("file") MultipartFile file) {
HashMap<String, Object> hashMap = this.getHashMap(request);
String originalFilename = file.getOriginalFilename();
Map<String, Object> pRowMap = JSON.parseObject(hashMap.get("pRow").toString(), Map.class);
String colName = (String) hashMap.get("colName");
Map<String, Object> result = new HashMap<>();
result.put("result", "fail");
if (StringUtils.isNotEmpty(originalFilename)) {
String[] splitFileName = originalFilename.split("\\.");
String uuid = UUIDUtil.getUUID();
Map<String, Object> rowMap = new HashMap<>(); if (!StringUtils.isEmpty(originalFilename)) {
splitFileName = originalFilename.split("\\.");
String refPageId = (String) hashMap.get("currPageId");
Map<String, Object> pageCondition2 = new HashMap<>();
pageCondition2.put("page_id", refPageId);
pageCondition2.put("del_flag", 0);
Map<String, Object> refPageInfo = tableService.getRecord(Arrays.asList("tb_name", "primary_key"), TableConstant.TB_PAGE, pageCondition2); String tbName = (String) refPageInfo.get("tb_name");
String[] tbNameSpl = tbName.split("_");
String middlePath = tbNameSpl[tbNameSpl.length - 1];
String filePath = sysconfig.getProperties().get("image.path") +
"/" + middlePath +
"/" + DateUtil.format2str("yyyyMMdd") +
"/";
rowMap.put("image_type", splitFileName[splitFileName.length - 1]);
rowMap.put("image_path", filePath);
rowMap.put("uuid", uuid);
rowMap.put("image_name", originalFilename);
rowMap.put("page_id", refPageId);
Long incr = redisService.incr(sysconfig.getProperties().get("sys.id") + "_primary_key_" + refPageInfo.get("tb_name") + "_" + refPageInfo.get("primary_key"), 1);
rowMap.put("image_id", incr);
rowMap.put("col_name", colName); String primaryKey = (String) refPageInfo.get("primary_key");
String pk = String.valueOf(pRowMap.get(primaryKey));
rowMap.put("ref_value", pk);
int ret = 0;
try { ret = tableService.addRecord(TableConstant.TB_IMG, rowMap);
} catch (Exception e) {
String message = e.getMessage();
if (message.contains("Duplicate entry")) {
log.info("主键冲突,redis刷新主键");
List<Map<String, Object>> recordsBySQL = tableService.getRecordsBySQL(String.format("select %s from %s order by %s desc limit 1", "image_id", TableConstant.TB_IMG, "image_id"));
for (Map<String, Object> objectMap : recordsBySQL) {
redisService.set(sysconfig.getProperties().get("sys.id") + "_primary_key_" + TableConstant.TB_IMG + "_" + "image_id", String.valueOf(Integer.parseInt(objectMap.get("image_id").toString())));
}
incr = redisService.incr(sysconfig.getProperties().get("sys.id") + "_primary_key_" + TableConstant.TB_IMG + "_" + "image_id", 1);
rowMap.put("image_id", incr);
ret = tableService.addRecord(TableConstant.TB_IMG, rowMap);
}
} if (ret > 0) {
log.info(String.format("插入图片记录imageId:%s成功", incr));
try {
boolean b = FileUtil.uploadFile(file.getBytes(), filePath, uuid + "." + splitFileName[1]);
if (b) {
log.info(String.format("插入图片imageId:%s成功", incr));
result.put("result", "success");
result.put("imageId", incr);
} else {
log.info(String.format("插入图片imageId:%s失败", incr));
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
log.info(String.format("插入图片记录imageId:%s失败", incr));
}
} return result;
} return result;
} /**
* 图片删除
*/
@RequestMapping(value = "deleteImg.do")
@ResponseBody
public Map<String, Object> deleteImg(HttpServletRequest request) {
Map<String, Object> data = new HashMap<>();
HashMap<String, Object> hashMap = this.getHashMap(request);
String imageId = (String) hashMap.get("image_id");
String pageId = (String) hashMap.get("currPageId");
String updateCol = (String) hashMap.get("updateCol");
Map<String, Object> pRowMap = JSON.parseObject(hashMap.get("pRow").toString(), Map.class); List<Map<String, Object>> records = tableService.getRecordsBySQL(String.format("select image_id,image_path,uuid,image_type from t_uls_image where image_id=%s", imageId));
String imageIdStr = "";
for (Map<String, Object> record : records) {
String imagePath = (String) record.get("image_path");
String uuid = (String) record.get("uuid");
String imageType = (String) record.get("image_type");
String path = imagePath + "/" + uuid + "." + imageType;
File file = new File(path);
if (file.exists()) {
boolean delete = file.delete();
if (delete) {
log.info(String.format("图片imageId:%s删除成功", imageId)); // 查找哪张表
Map<String, Object> tableCondition = new HashMap<>();
tableCondition.put("page_id", pageId);
Map<String, Object> pageInfo = tableService.getRecord(Arrays.asList("tb_name", "primary_key"), TableConstant.TB_PAGE, tableCondition); String tbName = (String) pageInfo.get("tb_name");
String primaryKey = (String) pageInfo.get("primary_key");
String pk = String.valueOf(pRowMap.get(primaryKey));
List<Map<String, Object>> recordsBySQL = tableService.getRecordsBySQL(String.format("select %s from %s where %s='%s'", updateCol, tbName, primaryKey, pk));
for (Map<String, Object> objectMap : recordsBySQL) {
String o = String.valueOf(objectMap.get(updateCol));
String[] split = o.split(",");
List<String> strings = new ArrayList<>(Arrays.asList(split));
if(strings.contains(imageId)){
strings.remove(imageId);
}
imageIdStr = StringUtils.join(strings, ",");
}
tableService.updateRecordsBySQL(String.format("update %s set %s='%s' where %s='%s'", tbName, updateCol, imageIdStr, primaryKey, pk));
} else {
log.info(String.format("图片imageId:%s删除失败", imageId));
}
}
}
data.put("data", "success");
data.put("imageIdStr", imageIdStr);
return data;
}
  1. 后端和代码架构相关了,大致参考下,遇到的问题有

    • 已上传的图片有多张的情况,显示不正确,也就是同时请求后端显示图片的时候可能报各种异常,包括不知道哪来的NULLPointException
    • 之后发现,还是代码架构的问题,继承类把HttpServletRequest request封装成了全局变量,并从中取出了hashmap参数封装。导致在并发的时候,hashmap被覆盖
  2. 后端主要为框架设计的逻辑,和业务结合,根据实际情况编写,主要三大块,上传,读取,删除,问题不大

注意事项

  1. 上传和删除操作,后端返回的一定要是json数据,否则会解析错误,就算后台上传成功,前台也显示失败

参考API http://plugins.krajee.com/file-input

补充

  1. 后续需要实现上传图片后没提交表单,立即删除。组件只提供了删除回调函数。但我需要将上传文件绑定删除地址以便调用后台删除方法
  2. 参考

bootstrap fileinput 使用记录的更多相关文章

  1. JS组件系列——Bootstrap文件上传组件:bootstrap fileinput

    前言:之前的三篇介绍了下bootstrap table的一些常见用法,发现博主对这种扁平化的风格有点着迷了.前两天做一个excel导入的功能,前端使用原始的input type='file'这种标签, ...

  2. 结合bootstrap fileinput插件和Bootstrap-table表格插件,实现文件上传、预览、提交的导入Excel数据操作流程

    1.bootstrap-fileinpu的简单介绍 在前面的随笔,我介绍了Bootstrap-table表格插件的具体项目应用过程,本篇随笔介绍另外一个Bootstrap FieInput插件的使用, ...

  3. 关于Bootstrap fileinput 上传新文件,移除时触发服务器同步删除的配置

    在Bootstrap fileinput中移除预览文件时可以通过配置initialPreviewConfig: [ { url:'deletefile',key:fileid } ] 来同步删除服务器 ...

  4. JS文件上传神器bootstrap fileinput详解

    Bootstrap FileInput插件功能如此强大,完全没有理由不去使用,但是国内很少能找到本插件完整的使用方法,于是本人去其官网翻译了一下英文说明文档放在这里供英文不好的同学勉强查阅.另外附上一 ...

  5. bootstrap fileinput 文件上传

    最近因为项目需要研究了下bootstrap fileinput的使用,来记录下这几天的使用心得吧. 前台html页面的代码 <form role="form" id=&quo ...

  6. Bootstrap fileinput v1.0(ssm版)

    前言bootstrap fileinput是一个很好的文件上传插件.但是官方不出api,这就尴尬了.百度一下,每个人写法都不相同,好多代码本身都是错的.我修改后才能跑起来.综上所述:所以今天我摸索了一 ...

  7. bootstrap+fileinput插件实现可预览上传照片功能

    实际项目中运用: 功能:实现上传图片,更改上传图片,移除图片的功能 <!DOCTYPE html> <html> <head> <meta charset=& ...

  8. bootstrap fileinput添加上传成功回调事件

    国外牛人做的bootstrap fileinput挺酷的,但是可惜没有提供自定义上传成功回调事件的接口,因此感到非常头疼,但是很幸运的是,我在网上搜索到一个提问帖子,它问到使用Jquery的on函数绑 ...

  9. BootStrap fileinput.js文件上传组件实例代码

    1.首先我们下载好fileinput插件引入插件 ? 1 2 3 <span style="font-size:14px;"><link type="t ...

随机推荐

  1. C++中如何对输出几位小数进行控制(setprecision)

  2. php 安装gzip

    https://jingyan.baidu.com/article/636f38bb3e538ad6b84610e6.html http://w3cgeek.com/configure-error-p ...

  3. iOS 拨打电话三种方式

    ,这种方法,拨打完电话回不到原来的应用,会停留在通讯录里,而且是直接拨打,不弹出提示 NSMutableString * str=[[NSMutableString alloc] initWithFo ...

  4. 安装配置python环境,并跑一个推荐系统的例子

    1.官网下载python2.7,安装完后,在环境变量Path中加上这个路径 在控制台输入python,出现版本信息,就成功了. 2.我使用的是 pycharm,注册后,在 把自己的python.exe ...

  5. leveldb 源码编译 vs版本

    为什么要windows版本? 因为方便调试跟进 VS的体验真的很不错. 搜索了一段时间才发现GITHUB有windows版本的leveldb 但是使用VS编译也有不少坑 可以下载网络上的其他朋友的版本 ...

  6. Scrum冲刺阶段1

    各个成员在 Alpha 阶段认领的任务 人员 任务 何承华 美化设计 部分后端设计 陈宇 后端设计 丁培辉 美化设计 部分后端设计 温志铭 前端设计 杨宇潇 服务器搭建 张主强 前端设计 明日各个成员 ...

  7. S 实现精确加减乘除

    //加法函数 function accAdd(arg1, arg2) { var r1, r2, m; try { r1 = arg1.toString().split(".")[ ...

  8. 12. The Biggest Safety Threat Facing Airlines 航空公司面临的最大安全威胁

    12. The Biggest Safety Threat Facing Airlines 航空公司面临的最大安全威胁 (1) The biggest safety threat facing air ...

  9. android 学习网站

    菜鸟教程 http://www.runoob.com/android/android-tutorial.html Android基础入门教程  http://www.runoob.com/w3cnot ...

  10. php判断语句

    编写代码时,可以为不同的情况执行不同的动作.可以使用判断条件语句来实现. if...else...elseif 例子一: <?php $t=date("H"); if ($t ...