微信端項目是基於Vux + Axios構建的,關於圖片上傳的業務場景有如下幾點需求:javascript
一、單張圖片上傳(如我的頭像,實名認證等業務) 二、多張圖片上傳(如某類工單記錄) 三、上傳圖片時指望能按指定尺寸壓縮處理 四、上傳圖片能夠從相冊中選擇或者直接拍照html
在以前開發的項目中(mui + jquery),有使用過微信JSSDK的接口上傳圖片,本想應該能快速遷移至此項目。事實證實編程沒有簡單的事: 一、按指定尺寸壓縮圖片 JSSDK提供的接口wx.chooseImage 是不能指定圖片壓縮尺寸的,只能在後端的接口經過localId獲取圖片時,再轉換成指定的尺寸。 二、微信JSSDK的接口權限驗證 只要是單頁面應用項目,微信JSSDK注入權限驗證都會有這個坑,而這個與路由模式(hash 或 history)也有關聯。有關此坑, 後續會再次寫文總結。參考解決方案[微信JSSDK] 解決SDK注入權限驗證 安卓正常,IOS出現config fail 通過權衡考慮網頁可能須要在微信之外的瀏覽器上也能上傳文件,顧後來放棄了採用微信JSSDK接口上傳圖片的方式。前端
這個坑,圈內有不少人踩過了。在PC端測試是正常的,發佈以後,微信端上傳時能選擇文件,但以後沒有任何效果。日誌跟蹤,後臺的api都未調用,由此判斷是input的onchange事件未被觸發。 解決方案, 更改input的 accept屬性:java
<input ref="file" type="file" accept="image/jpeg,image/png" @change="selectImgs" />
將以上代碼更改成:jquery
<input ref="file" type="file" accept="image/*" @change="selectImgs" />
若是不容許從相冊中選擇,只能拍照,增長capture="camera":android
<input ref="file" type="file" accept="image/*" capture="camera" @change="selectImgs" />
(<font color ="red">注:若是場景支持從相冊選擇或拍照,測試發現某些機型拍照後返回到了主頁。哈哈,也有多是其餘因素引發的問題,未作深究了</font>)ios
目前手機拍照的圖片文件大小通常在3-4M,若是在上傳時不作壓縮處理會至關浪費流量而且佔用服務器的存儲空間(指望上傳原圖的另作討論)。若是可以在前端壓縮處理,那確定是最理想的方案。而lrz.js則提供了前端圖片文件的壓縮方案,而且能夠指定尺寸壓縮。實測:3M左右的圖片文件,按寬度450px尺寸壓縮上傳後的文件大小在500kb左右,上傳時間2s之內。 其核心源碼,以下:git
selectImgs () { let file = this.$refs.file.files[0] lrz(file, { width: 450, fieldName: 'file' }).then((rst) => { var xhr = new XMLHttpRequest() xhr.open('POST', 'http://xxx.com/upload') xhr.onload = () => { if (xhr.status === 200 || xhr.status === 304) { // 不管後端拋出何種錯誤,都會走這裏 try { // 若是後端跑異常,則能解析成功, 不然解析不成功 let resp = JSON.parse(xhr.responseText) console.log('response: ', resp) } catch (e) { this.imageUrl = xhr.responseText } } } // 添加參數 rst.formData.append('folder', 'wxAvatar') // 保存的文件夾 rst.formData.append('base64', rst.base64) // 觸發上傳 xhr.send(rst.formData) return rst }) }
單個圖片上傳組件完整代碼,以下(<font color ="red">注: icon圖標使用的是svg-icon組件</font>):github
<template> <div class="imgUploader"> <section v-if="imageUrl" class="file-item "> <img :src="imageUrl" alt=""> <span class="file-remove" @click="remove()">+</span> </section> <section v-else class="file-item"> <div class="add"> <svg-icon v-if="!text" class="icon" icon-class="plus" /> <span v-if="text" class="text">{{text}}</span> <input type="file" accept="image/*" @change="selectImgs" ref="file"> </div> </section> </div> </template> <script> import lrz from 'lrz' export default { props: { text: String, // 壓縮尺寸,默認寬度爲450px size: { type: Number, default: 450 } }, data () { return { img: { name: '', src: '' }, uploadUrl: 'http://ff-ff.xxx.cn/UploaderV2/Base64FileUpload', imageUrl: '' } }, watch: { imageUrl (val, oldVal) { this.$emit('input', val) }, value (val) { this.imageUrl = val } }, mounted () { this.imageUrl = this.value }, methods: { // 選擇圖片 selectImgs () { let file = this.$refs.file.files[0] lrz(file, { width: this.size, fieldName: 'file' }).then((rst) => { var xhr = new XMLHttpRequest() xhr.open('POST', this.uploadUrl) xhr.onload = () => { if (xhr.status === 200 || xhr.status === 304) { // 不管後端拋出何種錯誤,都會走這裏 try { // 若是後端跑異常,則能解析成功, 不然解析不成功 let resp = JSON.parse(xhr.responseText) console.log('response: ', resp) } catch (e) { this.imageUrl = xhr.responseText } } } // 添加參數 rst.formData.append('folder', this.folder) // 保存的文件夾 rst.formData.append('base64', rst.base64) // 觸發上傳 xhr.send(rst.formData) return rst }) }, // 移除圖片 remove () { this.imageUrl = '' } } } </script> <style lang="less" scoped> .imgUploader { margin-top: 0.5rem; .file-item { float: left; position: relative; width: 100px; text-align: center; left: 2rem; img { width: 100px; height: 100px; border: 1px solid #ececec; } .file-remove { position: absolute; right: 0px; top: 4px; width: 14px; height: 14px; color: white; cursor: pointer; line-height: 12px; border-radius: 100%; transform: rotate(45deg); background: rgba(0, 0, 0, 0.5); } &:hover .file-remove { display: inline; } .file-name { margin: 0; height: 40px; word-break: break-all; font-size: 14px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } } .add { width: 100px; height: 100px; float: left; text-align: center; line-height: 100px; font-size: 30px; cursor: pointer; border: 1px dashed #40c2da; color: #40c2da; position: relative; background: #ffffff; .icon { font-size: 1.4rem; color: #7dd2d9; vertical-align: -0.25rem; } .text { font-size: 1.2rem; color: #7dd2d9; vertical-align: 0.25rem; } } } input[type="file"] { position: absolute; left: 0; top: 0; width: 100%; height: 100%; border: 1px solid #000; opacity: 0; } </style>
後端api對圖片的處理,是必不可少的環節,須要將前端提交過來的base64字符串轉換成圖片格式,並存放至指定的文件夾,接口返回圖片的Url路徑。各項目後端對圖片的處理邏輯都不一致,如下方案僅供參考(<font color='red'>咱們使用asp.net MVC 構建了獨立的文件存儲站點</font>)。 其核心源碼,以下:web
/// <summary> /// 圖片文件base64上傳 /// </summary> /// <param name="folder">對應文件夾位置</param> /// <param name="base64">圖片文件base64字符串</param> /// <returns></returns> public ActionResult Base64FileUpload(string folder, string base64) { var context = System.Web.HttpContext.Current; context.Response.ClearContent(); // 由於前端調用時,須要作跨域處理 context.Response.AddHeader("Access-Control-Allow-Origin", "*"); context.Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); context.Response.AddHeader("Access-Control-Allow-Headers", "content-type"); context.Response.AddHeader("Access-Control-Max-Age", "30"); if (context.Request.HttpMethod.Equals("OPTIONS")) { return Content(""); } var resultStr = base64.Substring(base64.IndexOf(",") + 1);//須要去掉頭部信息,這很重要 byte[] bytes = Convert.FromBase64String(resultStr); var fileName = Guid.NewGuid().ToString() + ".png"; if (folder.IsEmpty()) folder = "folder"; //本地上傳 string root = string.Format("/Resource/{0}/", folder); string virtualPath = root + fileName; string path = Server.MapPath("~" + virtualPath); //建立文件夾 if (!Directory.Exists(Path.GetDirectoryName(path))) { Directory.CreateDirectory(Path.GetDirectoryName(path)); } System.IO.MemoryStream ms = new System.IO.MemoryStream(bytes);//轉換成沒法調整大小的MemoryStream對象 System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(ms); bitmap.Save(path, System.Drawing.Imaging.ImageFormat.Png);//保存到服務器路徑 ms.Close();//關閉當前流,並釋放全部與之關聯的資源 return Content(Net.Url + virtualPath); //返回文件路徑 }
因爲項目實際狀況,上述的方案中還存在諸多未完善的點: 一、多張圖片上傳,仍是採用的與單張圖片相同的接口處理, 更爲完善的方案是,前端的多圖上傳組件只綁定一個關聯Id,便可經過實現上傳和將圖片列表查詢展現(<font color ="red">注:該功能在微信端未實現</font>)。 二、後端圖片上傳的接口,未作嚴格的安全校驗,更爲完善的方案是,每一個上傳的場景,都應該限制文件類型,限制文件大小,以及文件數據來源校驗(<font color ="red">注: 如軟件須要按二級等保標準測評,則後端接口會檢測通不過</font>)。 三、上傳組件,未顯示上傳進度,體驗性稍差。 正如前文所述,出於項目實際狀況考慮,只是簡單實現圖片壓縮上傳功能,如要支持更多的場景,還得細細雕琢。
一、移動端H5實現圖片上傳 二、安卓版微信 input onchange事件不生效
原文出處:https://www.cnblogs.com/xyb0226/p/11080137.html