在做微信公眾號或者企業微信開發業務應用的時候,我們常常會涉及到圖片預覽、上傳等的處理,往往業務需求不止一張圖片,因此相對來說,需要考慮的全面一些,用戶還需要對圖片進行預覽和相應的處理,在開始的時候我使用JSSDK方式,使用微信的SDK介面進行圖片的上傳、預覽操作,後來發現通過URL.createOb... ...
在做微信公眾號或者企業微信開發業務應用的時候,我們常常會涉及到圖片預覽、上傳等的處理,往往業務需求不止一張圖片,因此相對來說,需要考慮的全面一些,用戶還需要對圖片進行預覽和相應的處理,在開始的時候我使用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/[email protected]"; 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="width: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/[email protected]"; 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/[email protected]"; $.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的支持也非常不錯,可以在微信開發工具和實際環境上都正常使用。