在做微信公众号或者企业微信开发业务应用的时候,我们常常会涉及到图片预览、上传等的处理,往往业务需求不止一张图片,因此相对来说,需要考虑的全面一些,用户还需要对图片进行预览和相应的处理,在开始的时候我使用JSSDK方式,使用微信的SDK接口进行图片的上传、预览操作,后来发现通过URL.createObjectURL选定本地图片预览、上传也是非常方便的,本篇随笔针对同一个多图片的业务需求,使用JSSDK和URL.createObjectURL两种方式进行图片预览、上传、删除等常规的处理。
1、使用JSSDK对图片的处理
在一个公众号页面-问诊界面里面,我们需要让用户上传相关的图片,包括症状图片、处方图片等,每个列表可以上传多张图片,如下界面所示。
这里使用了SDK进行图片的上传处理,参考Weui的上传样式,选择本地几张图片,可以看到缩略图展示在图框里面,但是图片还没有上传,我们在保存问诊信息的时候,才启动图片文件的上传处理。
如果图片是在编辑界面中,我们需要考虑对现有图片进行删除的处理,删除前确认即可。
单击删除图标的按钮,提示用户进行图片删除确认即可。
以上就是我们几个图片处理的场景,我们来看看如何实现的。
我们以症状图片为例,它的界面HTML部分的代码如下所示。
<div class="weui-cells__title">症状图片</div> <div class="weui-cells weui-cells_form"> <div class="weui-cell"> <div class="weui-cell__bd"> <div class="weui-uploader"> <!--编辑的时候,放置已有图片进行预览--> <ul class="weui-weui-uploader__files" style="list-style-type: none" id="imgSickPreview"></ul> <div class="weui-uploader__bd"> <!--放置选择的图片进行预览--> <ul class="weui-weui-uploader__files" style="list-style-type: none" id="imgSick"></ul> <div class="weui-uploader__input-box"> <!--图片上传的图标处理--> <span id="uploaderSick" class="weui-uploader__input"></span> </div> </div> </div> </div> </div> </div>
为了使用微信JSSDK来实现上传、预览图片的功能,我们需要定义好对应的JS接口,如下代码所示。
<script language="javascript"> var appid = '@ViewBag.appid'; var noncestr = '@ViewBag.noncestr'; var signature = '@ViewBag.signature'; var timestamp = '@ViewBag.timestamp'; wx.config({ debug: false, appId: appid, // 必填,公众号的唯一标识 timestamp: timestamp, // 必填,生成签名的时间戳 nonceStr: noncestr, // 必填,生成签名的随机串 signature: signature, // 必填,签名,见附录1 jsApiList: [ 'checkJsApi', 'chooseImage', 'previewImage', 'uploadImage', 'downloadImage', 'getLocalImgData' ] }); ...... </script>
在上传图片之前,我们需要通过JSSDK的方式选择图片,这里用到了chooseImage的接口,大概所需的代码如下所示。
//上传图片集合[用微信上传的时候,记录微信mediaId集合] var images = { localSickId: [],//病情 localPresId: [],//处方 serverSickId: [], serverPresId: [] }; //图片选择 $("#uploaderSick").click(function () { chooseImage("imgSick", "sick"); }); $("#uploaderPres").click(function () { chooseImage("imgPres", "pres"); }); //选择图片显示 function chooseImage(ctrlName, type) { //清空集合 if (type == "sick") { images.localSickId = []; } else { images.localPresId = []; } wx.chooseImage({ count: 3, // 默认9 sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 success: function (res) { var ctrl = $("#" + ctrlName); ctrl.html("");//清空图片显示 //localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片 if (type == "sick") { images.localSickId = res.localIds; } else { images.localPresId = res.localIds; } //动态增加img标识 $.each(res.localIds, function (index, item) { ctrl.append("<img class='weui-uploader__file' src='" + item + "' />"); }); } }); }
选择图片后,就是将图片的缩略图动态的增加在指定图片框里面。然后在保存数据的时候,使用JSSDK提交图片到微信服务器,我们服务器后台再从微信服务器获取图片(通过媒体id)。
这里我们定义了两类的图片,方便区分,分别是症状图片和处方图片,因此需要定义两个类别的变量,分别存储本地和服务器返回的id集合。
我们在进行表单提交的时候,需要确认一些必填项,然后在检查是否有文件需要上传,如果有则执行上传处理后提交表单,大概的处理代码如下所示。
//上传数据 $("#btnOK").click(function () { var PatientName = $("#PatientName").val(); if (PatientName == '' || PatientName == undefined) { $.toast('患者姓名不能为空', "forbidden"); return; } var ProblemDetail = $("#ProblemDetail").val(); if (ProblemDetail == '' || ProblemDetail == undefined) { $.toast('详细描述不能为空', "forbidden"); return; } //上传图片 if (images.localSickId.length > 0 || images.localPresId.length > 0) { uploadImage(submitCallback);//通过就提交数据 } else { submitCallback(); } });
这里主要的图片上传处理,就是 uploadImage 函数的处理了,而submitCallback这是定义一个函数上传表单数据的。
由于微信JSSDK上传图片,是一个个上传的,我们需要把它们串联起来,一并上传。uploadImage 里面定义了一个内部函数,依次进行图片的上传。
我们通过序号来标识两类图片,图片上传成功后,我们把图片媒体的id(JSSDK返回的)记录下来,统一提交给对应数据库记录,在后台进行图片文件的提取即可。
//上传图片 function uploadImage(callback) { var localIds = images.localSickId.concat(images.localPresId);//合并数组 var i = 0, length = localIds.length; //$.toast(length); images.serverSickId = []; images.serverPresId = []; //定义一个子级函数,方便递归调用 function upload(callback) { wx.uploadImage({ localId: localIds[i], success: function (res) { i++; //成功后加入不同的集合 if (i <= images.localSickId.length) { images.serverSickId.push(res.serverId);//第一部分 } else { images.serverPresId.push(res.serverId);//第二部分 } if (i < length) { upload(callback); }else if (callback != undefined) { callback();//回调函数 } }, fail: function (res) { alert(JSON.stringify(res)); } }); }; upload(callback); }
其中我们的定义的callback函数,是用来最后上传完成后,执行表单的记录存储的,表单包含各种输入和图片的ID信息,如下是详细的表单保存操作代码。
//在上传图片后触发的回调函数 function submitCallback() { var druglist = [];//构造集合对象 for (var key in itemDict) { //Drug_ID,DrugName,How,Freq druglist.push({ 'Drug_ID': key, "DrugName": itemDict[key], 'How': howDict[key], 'Freq': freqDict[key], 'Quantity': quantityDict[key] }); } var url = "/H5/DrugInquirySave?openid=@ViewBag.openid"; var postData = { PatientName: $("#PatientName").val(), Gender: $("#Gender").val(), BirthDate: $("#BirthDate").val(), Telephone: $("#Telephone").val(), ProblemDetail: $("#ProblemDetail").val(), Creator: $("#Creator").val(), ProblemItems: $("input[name='ProblemItems']:checked").val(), @if (ViewBag.Info != null) { <text> ID: '@ViewBag.Info.ID', </text> } SickAttachGUID: $("#SickAttachGUID").val(), PresAttachGUID: $("#PresAttachGUID").val(), ServerSickId: JSON.stringify(images.serverSickId), ServerPresId: JSON.stringify(images.serverPresId), DrugList: JSON.stringify(druglist) }; $.post(url, postData, function (json) { //转义JSON为对象 var data = $.parseJSON(json); if (data.Success) { $.toast("处方已提交审核中,稍后请到处方查询查看。"); //WeixinJSBridge.call('closeWindow');//关闭窗口 location.href = "/h5/Prescription";//跳转到处方页面 } else { $.toast("保存失败:" + data.ErrorMessage, "forbidden"); } }); };
我们注意到,我们服务端返回的ID集合,我们分别放在了两个字段里面提交到后台处理。
ServerSickId: JSON.stringify(images.serverSickId),
ServerPresId: JSON.stringify(images.serverPresId),
在后台,我们首先需要提取用户提交的基础表单数据,如下是后台定义的函数处理
这些是常规的表单信息,我们提交到微信服务器的图片信息也需要提取出来的,这些图片分两类,每类都包含多个字符串组成的图片ID集合。
后台主要就是根据这些ID,使用微信基础接口,获取临时图片的接口方式,把图片从服务器上下载下来存储到本地服务器上。
其中UploadFile函数就是封装了如何实现图片获取、图片存储的处理逻辑,主要的代码部分逻辑如下所示。
这种方式很好的利用了JSSDK的图片选择、上传的处理,实现了我们所需要的图片预览、选择、上传等一系列操作,也能够满足实际的功能需要。
不过总感觉把图片绕了一圈再回来不太好而已。
2、使用URL.createObjectURL对图片的处理
前面介绍了使用微信JSSDK方式实现图片预览、选择、上传等一系列操作,在上传文件的时候,感觉绕了一圈再回来,一直希望能够直接把文件直接提交到服务器上更好,就像我们一般的Web应用上传附件一样感觉更好一些,后来发现了可以通过URL.createObjectURL进行相关的处理,参考了一些案例,对前面介绍的JSSDK的图片上传方式进行改良,从而实现了把图片附件通过表单的方式直接提交到自己后台服务器上了,下面开始介绍一下这种方式的思路和实现代码。
首先我们定义一个预览图片的列表和一个Input的文件控件元素,替代前面的做法,如下所示。
<div class="weui-cells__title">症状图片</div> <div class="weui-cells weui-cells_form"> <div class="weui-cell"> <div class="weui-cell__bd weui-cell_primary"> <div class="weui-uploader"> <!--预览图片的列表--> <ul class="weui-uploader__files" id="imgSick"> </ul> <div class="weui-uploader__input-box"> <!--上传图片附件控件--> <input id="uploaderSick" class="weui-uploader__input" type="file" accept="image/*" multiple=""> </div> </div> </div> </div> </div>
为了实现选择图片文件的时候,预览图片的列表可以动态变化(动态增加 li 项目),我们需要定义对应的事件来实现这个操作。
//存放文件图片的集合 var fileSick = new Array(); var filePres = new Array(); function initImage() { var tmpl = '<li class="weui-uploader__file" style="background-image:url(#url#)"></li>', $gallery = $("#gallery"), $galleryImg = $("#galleryImg"), $uploaderSick = $("#uploaderSick"), $imgSick = $("#imgSick"), $uploaderPres = $("#uploaderPres"), $imgPres = $("#imgPres"); //症状图片上传 $uploaderSick.on("change", function (e) { var src, url = window.URL || window.webkitURL || window.mozURL, files = e.target.files; for (var i = 0, len = files.length; i < len; ++i) { var file = files[i]; fileSick.push(file);//加入集合 if (url) { src = url.createObjectURL(file); } else { src = e.target.result; } $imgSick.append($(tmpl.replace('#url#', src))); } }); ..............
我们注意到了,这里没有使用chooseImage的JSSDK接口,而是通过 url.createObjectURL(file) 的方式获取路径,展示在图片列表控件里面。
对于动态增加的图片,我们可以让它支持单击预览的方式,预览其实是把图片放在一个预览层里面。
var index; //第几张图片 var category;//那个类别 var imgid;//图片ID //症状图片单击处理 $imgSick.on("click", "li", function() { index = $(this).index(); category = "sick"; imgid = $(this).attr("id"); $galleryImg.attr("style", this.getAttribute("style")); $gallery.fadeIn(100); });
预览层的DIV是放在主界面上的,主界面是一个放置图片的区域,底部是一个删除按钮,用来我们实现图片删除操作的。
<!--图片预览层--> <div class="weui-gallery" id="gallery"> <span class="weui-gallery__img" id="galleryImg" style="auto"></span> <div class="weui-gallery__opr"> <a href="javascript:" class="weui-gallery__del"> <i class="weui-icon-delete weui-icon_gallery-delete"></i> </a> </div> </div>
预览层再次单击的时候关闭,执行的JS代码如下所示。
$gallery.on("click", function() { $gallery.fadeOut(100); });
删除图片的时候,我们区分是存在服务器的图片,还是本地临时选择的图片,区别对待。如果服务器图片,需要提示确认删除,如果是本地临时图片,直接移除即可。
//删除图片(根据类别和序号处理) $(".weui-gallery__del").click(function () { console.log(index + ',' + category + ',' + imgid);//记录显示 //如果是在服务端的图片,确认后移除 if (imgid != undefined && imgid != '') { $.confirm("您确定要永久删除该图片吗?", "永久删除?", function () { var url = "/H5/DeleteAttachment?openid=@ViewBag.openid"; var postData = { id: imgid.replace(/img_/, '') //控件id去掉前缀为真正附件ID }; $.post(url, postData, function (json) { //转义JSON为对象 var data = $.parseJSON(json); if (data.Success) { $.toptip("删除成功!", 'success'); //在界面上找到对应控件ID,移除控件 RemoveImg(); } else { $.toast("操作失败:" + data.ErrorMessage, "forbidden"); } }); }); } else { RemoveImg(); //普通图片快速移除 }; });
其中移除图片显示的JS代码如下所示。
//移除对应类别的图片 function RemoveImg() { if (category == "sick") { $imgSick.find("li").eq(index).remove(); fileSick.splice(index, 1); } else { $imgPres.find("li").eq(index).remove(); filePres.splice(index, 1); } };
我们要使用表单上传文件的方式,就需要在JS里面创建一个FormData的对象,用来承载文件内容,如下所示
var formData = new FormData();//构建一个FormData存储复杂对象
如果是常规的表单数据,我们通过键值,把内容填入FormData即可,如下所示。
var formData = new FormData();//构建一个FormData存储复杂对象 formData.append("PatientName", $("#PatientName").val());
如果是图片附件的,我们则需要遍历集合文件,把它们逐一加入对应键值里面,为了区分不同的类别文件,我们使用不同的前缀方式,如下代码所示。
//加入症状图片 for (var i = 0; i < fileSick.length; i++){ formData.append("sick_" + i, fileSick[i]); }; //加入处方图片 for (var i = 0; i < filePres.length; i++){ formData.append("pres_" + i, filePres[i]); };
//提交表单数据和文件 var url = "/H5/DrugInquirySave2?openid=@ViewBag.openid"; $.ajax({ url: url, type: 'post', processData: false, contentType: false, data: formData, success: function (json) { //转义JSON为对象 var data = $.parseJSON(json); if (data.Success) { $.toast("处方已提交审核中,稍后请到处方查询查看。"); //WeixinJSBridge.call('closeWindow');//关闭窗口 location.href = "/h5/Prescription";//跳转到处方页面 } else { $.toast("保存失败:" + data.ErrorMessage, "forbidden"); } } });
在后台的处理函数 DrugInquirySave2 里面,我们需要把文件按键名提取出来,根据文件键名的不同,放到不同给的集合里面存储起来即可。
如下是DrugInquirySave2 函数里面的部分代码,用来处理收到的表单文件集合。然后我们在把文件写入文件系统即可,这样省却了对JSSDK提交文件,再去微信服务器提取文件方式的麻烦,直接由客户端把文件上传的自己的文件服务器了。
#region 通过文件附件方式获取 var files = Request.Files; if (files != null && files.Count > 0) { LogTextHelper.Info(string.Format("收到文件:{0}", files.Count));//测试 foreach (string key in files.Keys) { LogTextHelper.Info(string.Format("收到文件key:{0}", key)); var fileData = files[key]; bool isSickImage = key.ToLower().IndexOf("sick") >= 0;//判断是否为问诊图片分类 if (fileData != null) { HttpContext.Request.ContentEncoding = Encoding.GetEncoding("UTF-8"); HttpContext.Response.ContentEncoding = Encoding.GetEncoding("UTF-8"); HttpContext.Response.Charset = "UTF-8"; string fileName = Path.GetFileName(fileData.FileName); //原始文件名称 string fileExtension = Path.GetExtension(fileName); //文件扩展名 FileUploadInfo fileInfo = new FileUploadInfo(); fileInfo.FileData = ReadFileBytes(fileData); if (fileInfo.FileData != null) { fileInfo.FileSize = fileInfo.FileData.Length; } //判断图片类别分组 fileInfo.Category = isSickImage ? "问诊图片" : "处方图片"; fileInfo.FileName = fileName; fileInfo.FileExtend = fileExtension; //判断属于那个分组【这里只有两个分组】 fileInfo.AttachmentGUID = isSickImage ? SickAttachGUID : PresAttachGUID; fileInfo.AddTime = DateTime.Now;//创建时间 fileInfo.Editor = openid;//记录人 fileInfo.Owner_ID = info.ID;//属于主表记录 result = BLLFactory<FileUpload>.Instance.Upload(fileInfo); if (!result.Success) { LogTextHelper.Error("上传文件失败:" + result.ErrorMessage); } } } } #endregion
编辑现有记录的时候,也可以实现对已有图片的删除操作,临时文件的预览处理和再次上传等操作。
3、两种图片处理方式的总结
本篇随笔是基于公众号上传图片文件的两种方式的处理,分别是使用微信JSSDK和使用URL.createObjectURL上传预览图片的不同处理对比,两种方式都能够满足图片的处理操作。对比处理代码,可能使用后者可能更加简洁一些。而且微信浏览器对URL.createObjectURL的支持也非常不错,可以在微信开发工具和实际环境上都正常使用。