應廣大讀者建議,已經將該項目源碼提交到地址:
https://download.csdn.net/download/u014466109/10465677
與本博客相關的多圖壓縮上傳代碼在dashen/service/ask.html,請解壓項目並移動到hbuilder中打開。html
下面一個個實現上述需求,從簡單到複雜:html5
用戶返回彈出提示框,使用mui.confirm以下:算法
var oldBack = mui.back; mui.back=function(e){ mui.confirm("還沒有提交,返回後將會丟失填寫內容物,是否返回?","返回確認",['返回','取消'],function(e){ if(e.index==0) oldBack(); }) }
(以上代碼買應寫在mui.plusReady()之中,由於裏面須要用到html5+的方法,mui.back()就是5+方法)數組
第一步克隆了一個mui.back,由於下面他本身會被重寫了,而真正返回的時候仍是須要用到原來的mui.back()!服務器
重寫是爲了再返回前執行一段邏輯,按照官方的說法,confirm彈出是異步執行(非阻塞)因此另一種在mui.init({beforeBack:function})的方式是不合適,由於beforeBack選項要求的是阻塞的,可能會致使尚未按下confirm中的按鈕,由於執行了return true而退出了,彈出窗口就顯得沒有意義了!只能使用重寫back方法的辦法了!網絡
mui.confirm傳入四個參數,提示主內容,標題,按鈕數組,回調函數(對按鈕數組的下表進行判斷)app
qq空間發表說說能夠攜帶圖片,經過縮略圖的形式讓用戶修改本身要上傳的圖片,不過騰訊作的那個高級多了,還能夠拖拽圖片打開大圖編輯等等,在此只實現最簡單的功能。dom
專門寫一個函數來實現這個功能:init_image_add()異步
//初始化圖片添加器 function init_image_add() { //上傳圖片上限,超過不現實加號 if(question.files.length >= IMG_MAX_NUM) return; var placeholder = document.createElement('div'); placeholder.setAttribute('class', 'image-item space'); //刪除圖片 var closeButton = document.createElement('div'); closeButton.setAttribute('class', 'image-close'); closeButton.innerHTML = 'X'; //小X的點擊事件 closeButton.addEventListener('tap', function(event) { removeFile(getChildrenIndex(placeholder)); //刪除實際圖片數組元素,先刪除數組中的 imageList.removeChild(placeholder); //刪除ui,必須後刪除,不然節點會找不到了 if(question.files.length >= IMG_MAX_NUM - 1) init_image_add(); event.stopPropagation(); }, false); placeholder.addEventListener('tap', function(event) { var btnArray = [{ title: "相冊" }, { title: "拍照" }]; //actionsheet plus.nativeUI.actionSheet({ title: "選擇圖片", cancel: "取消", buttons: btnArray }, function(e) { var i = e.index; switch(i) { case 0: break; case 1: plus.gallery.pick(function(e) { plus.io.resolveLocalFileSystemURL(e, function(entry) { var url = entry.toLocalURL(); var name = url.substr(e.lastIndexOf('/') + 1); //壓縮取得縮略圖 plus.zip.compressImage({ src: url, dst: '_doc/' + name, overwrite: true, quality: 50, height: '300px', clip: { top: "25%", left: "25%", width: "300px", height: "300px" } }, function(zip) { placeholder.style.backgroundImage = "url('" + zip.target + "')"; if(!placeholder.classList.contains('space')) { //已有圖片 exFile(getChildrenIndex(placeholder), url); } else { //加號 placeholder.classList.remove('space'); addFile(url); init_image_add(); } }, function(zip) { mui.toast('壓縮失敗!'); }); }, function(e) { console.log("讀取相冊文件錯誤:" + e.message); }); }, function(e) { console.log(e.message); }, {}); break; case 2: plus.camera.getCamera().captureImage(function(e) { plus.io.resolveLocalFileSystemURL(e, function(entry) { var url = entry.toLocalURL(); var name = url.substr(e.lastIndexOf('/') + 1); //壓縮 plus.zip.compressImage({ src: url, dst: '_doc/' + name, overwrite: true, quality: 50, height: '300px', clip: { top: "25%", left: "25%", width: "300px", height: "300px" } }, function(zip) { placeholder.style.backgroundImage = "url('" + zip.target + "')"; if(!placeholder.classList.contains('space')) { //已有圖片 exFile(getChildrenIndex(placeholder), url); } else { //加號 placeholder.classList.remove('space'); addFile(url); init_image_add(); } }, function(zip) { mui.toast('壓縮失敗!'); }); }, function(e) { console.log("讀取拍照文件錯誤:" + e.message); }); }, function(s) { console.log("error" + s); }, {}); break; } }); }, false);
代碼有點長,只能做爲參考,講一下其中的算法:函數
首先這個函數應該在plusReady()內調用,而且在載入頁面的時候就要調用了!
這個函數做用:生成一個圖片添加按鈕:
如圖所示的「+」號,而且爲這個新增的加號添加監聽事件
添加的方式是使用js生成dom而且插入到相應的節點
var placeholder = document.createElement('div'); placeholder.setAttribute('class', 'image-item space'); //刪除圖片 var closeButton = document.createElement('div'); closeButton.setAttribute('class', 'image-close'); closeButton.innerHTML = 'X';
如下是將會生成的對應的html代碼
<div class="image-item space" id="img1"> <div class="image-close">x</div> </div>
能夠想到這個函數應該要遞歸調用,這個遞歸是基於事件的,什麼事件呢?
就是每一次添加完一張圖片的事件,好比途中加號前面一張圖被添加完成後馬上就會遞歸一次,調用init_image_add()本身
給個流程圖:
使用:plus.zip.compressImage
html5+官方文檔:
例:
//壓縮取得縮略圖 plus.zip.compressImage({ src: url, dst: '_doc/' + name, overwrite: true, quality: 50, height: '300px', clip: { top: "25%", left: "25%", width: "300px", height: "300px" } }, function(zip) { placeholder.style.backgroundImage = "url('" + zip.target + "')"; if(!placeholder.classList.contains('space')) { //已有圖片 exFile(getChildrenIndex(placeholder), url); } else { //加號 placeholder.classList.remove('space'); addFile(url); init_image_add(); } }, function(zip) { mui.toast('壓縮失敗!'); });
方法傳入的參數:
成功回調函數
失敗回調函數
上傳原圖不在贅述,直接跳過此處,參考uploader上傳便可^_^
這裏是有一點小麻煩,我的折騰了一個小時纔算弄完美了
千萬注意,compressImage方法是異步執行的,也就是說,若是你打算將全部要上傳的圖片在for循環中遍歷而且壓縮是不穩當的,由於這些圖片將會並行壓縮,而因爲是多圖上傳,你不知道全部圖片壓縮完成是何時,一張圖的話能夠直接在成功的回調函數中執行後面的邏輯
我採用了遞歸的方法解決了多圖壓縮而且壓縮所有完成後再執行後面的邏輯,至關於強行把一個異步的函數寫成了同步(阻塞)函數,須要結合「回調函數」+「遞歸調用」!
代碼以下:
//用戶未選取上傳原圖時上傳前調用 function zip_upload_imgs(len = question.files.length - 1) { //第一次遞歸顯示等待 if(len == question.files.length - 1) plus.nativeUI.showWaiting("正在壓縮圖片...", { back: "none" }); //當長度小於0時,結束遞歸 if(len < 0) { //關閉等待 plus.nativeUI.closeWaiting(); return submitAsk(); } //上傳壓縮圖 var url = question.files[len]; plus.zip.compressImage({ src: url, dst: '_doc/zip_' + url.substr(url.lastIndexOf('/') + 1), overwrite: true, quality: 50, height: "90%", }, function(zip) { //壓縮成功,替換原圖路徑 question.files[len] = zip.target; zip_upload_imgs(--len); }, function() { //壓縮失敗 mui.toast('壓縮失敗!'); }); }
這個函數就有意思了,遞歸出如今當前圖片壓縮成功後調用(在回調函數中遞歸),這樣就解決了異步的問題,等待壓縮而不執行以後的邏輯!
固然這裏由於壓縮時間可能會長一點,須要用到等待窗口提供用戶友好,以避免用戶不知道這段時間是在壓縮!
這個遞歸函數默認傳入的是須要壓縮的圖片的數組長度值,這個圖片數組(question.files)是個全局或者說相對於函數來講是更加全局的,他不會由於函數結束而回收!處於函數做用域以外!
數組是反向遞歸的,下標從大到小,最大的時候第一次執行函數因此顯示等待提示,最後一次是當下標小於0時(數組下標越界)結束遞歸,中間每一次執行完,遞歸前將下標-1,傳入下一次遞歸!
在return後面緊跟着的是一個一直在等待着的sumitAsk()函數,這個函數是最終上傳圖片+提交表單的!之因此說是「一直等待着的提交函數」,由於他原本會由於異步執行的「plus.zip.compressImage」而先執行掉,致使圖片沒有壓縮就上傳了,我以前折騰了好久就是由於這個問題,若是不按照上述的回調+遞歸模式,圖片還在壓縮,sumit就執行了,那麼圖片數組沒有變,依然上傳了原圖!
JavaScript中有的是異步函數,有的是同步函數,須要嚴格注意,異步函數會從新打開一個「時間線」去執行本身,忽略掉同步的函數,因此應該要作到等待異步函數執行完成後繼續執行同步函數!
使用函數:plus.uploader.createUoload()
官方說明
我須要上傳
示例以下:
function submitAsk() { //創建鏈接 var url = HTTP_DOMAIN + "Service/ask"; var uploader = plus.uploader.createUpload(url, { method: 'POST' }, function(upload, status) { plus.nativeUI.closeWaiting(); if(status == 200) { var res = JSON.parse(upload.responseText); //服務器方登錄失效 if(res.login == 0) { plus.nativeUI.toast(res.info); app.clearToken(); app.toLogin(); return false; } console.log(upload.responseText); if(res.status == 1) { mui.alert("您的問題已提交,等待附近的人解答", "發表成功", "肯定", function() { mui.back(); }); } else { mui.toast(res.info); } } else { mui.toast("網絡服務器鏈接失敗!稍後重試"); } }); //添加上傳數據 for(key in question) { if(key == "files") continue; uploader.addData(key, question[key]); } //若是有禮物圖片就上傳 if(question.gift_img != "") { uploader.addFile(question.gift_img, { key: "gift_img" }); } //添加上傳文件 for(var i = 0; i < question.files.length; i++) { uploader.addFile(question.files[i], { key: "img" + i }); } //開始上傳任務 plus.nativeUI.showWaiting("正在提交...",{back:"none"}); uploader.start(); }
注意addData和addFile的使用:
其中addData是上傳數據的鍵值對,就像表單name和value同樣一一對應,在這以前已經放入了question對象之中:
document.getElementById("submit").addEventListener("tap", function() { //獲取表單數據 question.title = document.getElementById("title").value; question.content = document.getElementById("content").value; question.gift_img = document.getElementById("gift_img").getAttribute("src"); question.reward = document.getElementById("reward").value; question.message = document.getElementById("message").value; question.price = document.getElementById("price").value; console.log(JSON.stringify(question)); /* * 必填項目:標題,內容,難度 */ if(plus.networkinfo.getCurrentType() == plus.networkinfo.CONNECTION_NONE) return mui.toast("鏈接網絡失敗,請稍後再試"); if(trim(question.title) == "") return mui.toast("請給出問題標題!"); if(trim(question.content) == "") //判斷網絡鏈接 return mui.toast("沒法提交,請詳細填寫如下問題內容!"); if(question.star <= 0 || question.star > 5) return mui.toast("請給出問題難度!"); //用戶未選擇上傳原圖時,壓縮全部上傳圖片 if(!document.getElementById("high_img").classList.contains("mui-active") && question.files.length > 0) { zip_upload_imgs(); } else { submitAsk(); } }, false);
關於異步上傳plus.uploader,詳細請參閱:
http://www.html5plus.org/doc/zh_cn/uploader.html#plus.uploader.createUpload