一文完全弄懂wx.chooseImage

最近在作一個複雜的跨五端(PC、H五、小程序、iOS、android)的需求,歷時將近兩個多月,其中酸甜苦辣冷暖自知,近日終於能夠封板上線,回憶整個開發過程,深坑不斷,收穫頗多,今天先分享一下在微信小程序的web-view裏選取照片的功能,此文完整的記錄整個開發過程,完全解決各類疑難雜症。javascript

深度調研

由於跨端因此最開始使用了<input type="file" accept="image/*" capture="camera">的方式,簡單粗暴可以使用,五端勉強均可以打個及格分,一直到聯調結束PM檢測時說體驗太差了,趁着還有時間(其實我也看不下去),索性就按PM的要求來,開始計劃調用原生的,由於客戶端以前已經提供了這樣的bridge,因此和客戶端的頭像調試輕鬆完成,剩下的小程序歷經萬千磨難,最終完美謝幕。php

首先沒有作過這方面的經驗,兩眼一抹黑,因爲是把H5頁面嵌套在小程序的web-view裏,因此直接查看官方文檔,小程序向web-view提供了三十多個API-詳情文檔。至此覺得能夠輕鬆搞定,因而開始了不斷的趟坑。。。html

按步開發

剛開始直接在H5裏使用了wx.chooseImage,發如今開發者工具中不斷的報錯the permission value is offline verifying,慢慢開始搜索才發如今小程序的web-view裏也必須使用jweixin,其實就是個公衆號網頁,接下來開始按這套流程走:java

  1. 引入JS文件
// 推薦使用1.3.2以上的版本,以前的版本不少坑😭
https://res.wx.qq.com/open/js/jweixin-1.3.2.js
// 我是直接用的npm包,目前基於1.4.0-test的版本
npm install weixin-js-sdk
複製代碼
  1. 經過config接口注入權限驗證配置
wx.config({
    debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
    appId: '', // 必填,微信公衆號的惟一標識,此處填寫公衆號的appId
    timestamp: , // 必填,生成簽名的時間戳
    nonceStr: '', // 必填,生成簽名的隨機串
    signature: '',// 必填,簽名
    jsApiList: [] // 必填,須要使用的JS接口列表,全部JS接口列表見附錄2
});
複製代碼
  1. 經過ready接口處理成功驗證
wx.ready(function(){
    // config信息驗證後會執行ready方法,全部接口調用都必須在config接口得到結果以後,config是一個客戶端的異步操做,因此若是須要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則能夠直接調用,不須要放在ready函數中。
});
複製代碼
  1. 經過error接口處理失敗驗證
wx.error(function(res){
    // config信息驗證失敗會執行error函數,如簽名過時致使驗證失敗,具體錯誤信息能夠打開config的debug模式查看,也能夠在返回的res參數中查看,對於SPA能夠在這裏更新簽名。
});
複製代碼
  1. 判斷當前客戶端版本是否支持指定JS接口
wx.checkJsApi({
    jsApiList: ['chooseImage'] // 須要檢測的JS接口列表
    success: function(res) {
    // 以鍵值對的形式返回,可用的api值true,不可用爲false
    // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
    },
    fail: function(err) {
    // checkJsApi接口調用失敗
    }
});
複製代碼
  1. 接口調用
wx.chooseImage({
    count: 1, // 默認9
    sizeType: ['original', 'compressed'], // 能夠指定是原圖仍是壓縮圖,默認兩者都有
    sourceType: ['album', 'camera'], // 能夠指定來源是相冊仍是相機,默認兩者都有
    success: function (res) {
        var localIds = res.localIds; // 返回選定照片的本地ID列表,localId能夠做爲img標籤的src屬性顯示圖片
    }
});
複製代碼

本部分要特別注意如下幾點:android

  • invalid url domain:當前頁面所在域名與使用appid沒有綁定(可在該公衆號後臺的應用可信域名中配置當前H5頁面的域名);
  • invalid signature簽名錯誤:建議按以下順序檢查,主要多是域名通常帶的參數,因此域名每次都不同,必須在調用前經過域名來獲取signature;
  • 服務上線以後沒法獲取jsapi_ticket,通常是access_token調用次數過多被限制了,須要在服務端作緩存;
  • permission denied:該公衆號號沒有權限使用這個JSAPI(部分接口須要認證以後才能使用);

處理圖片數據爲base64

wx.chooseImage獲取到的圖片爲一個臨時路徑,微信同時提供了wx.getLocalImgData方法能夠把獲取到的路徑轉爲base64格式的數據,至此就能夠輕鬆許多了,可是轉出來的base64在android和iOS中稍有不一樣,須要特別注意一下:web

wx.getLocalImgData({
    localId: req.localIds[0].toString(),
    success: function (res) {
        const localData = res.localData;
        let imageBase64 = '';
        if (localData.indexOf('data:image') == 0) {
            //蘋果的直接賦值,默認生成'data:image/jpeg;base64,'的頭部拼接
            imageBase64 = localData;
        } else {
            //此處是安卓中的惟一得坑!在拼接前須要對localData進行換行符的全局替換
            //此時一個正常的base64圖片路徑就完美生成賦值到img的src中了
            imageBase64 = 'data:image/jpeg;base64,' + localData.replace(/\n/g, '');
        }
    }
});
複製代碼

上傳圖片

獲取到圖片的base64數據以後其實咱們就能夠隨心所欲了,不須要使用wx.uploadImagewx.downloadImage了,不只須要麻煩的上傳到微信服務器,還有三天的時間限制反而增長了成本,最好的方式就是直接上傳至咱們本身的服務器。npm

通常與服務端交互的這種文件類型通常採用表單提交--multipart/form-data的方式,這就要求咱們熟悉傳輸form-data的數據交互,因此須要把剛纔獲取到的base64轉爲可post的二進制數據,JavaScript提供了原生的atob/btoa用來對base64進行編碼和解碼;json

base64ToBlob(dataurl) {
    let arr = dataurl.split(',');
    let mime = arr[0].match(/:(.*?);/)[1];
    let bstr = atob(arr[1]);
    let n = bstr.length;
    let u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], { type: mime });
}
複製代碼

ok,處理好可交互的數據就能夠上傳到本身的服務器了,以後至關於普通H5頁面的圖片交互了,可上傳可下載😄小程序

let param = new FormData();
param.append('headPic', imageData);
this.$http.post('/upload.json', {
    headers: {
        'Content-Type': 'multipart/form-data',
    },
    params: param,
}).then(res => {
    console.log('上傳成功', res);
});
複製代碼

最終效果

wx.chooseImage成功調用後iOS和android分別調起系統原生的拍照和相冊,分別以下圖:微信小程序

看到這樣的界面就能夠會心一笑了😄😄😄

完整代碼

終於。。。能夠告一段落了,至此小程序的web-view裏成功使用了wx.chooseImage,下面把完整代碼貼出來,敬請參考😄

<template>
    <div class="card-avatar">
      <img
        :src="avatarUrl"
      >
      <div
        class="choose-image"
        @click="chooseImage"
      />
    </div>
</template>

<script>
import wx from 'weixin-js-sdk';
const imgUrl = require('../../common/assets/images/default-avatar.png');

export default {
  name: 'Card',
  components: {
  },
  props: {
  },
  data() {
    return {
      avatarUrl: imgUrl,
    };
  },
  computed: {
  },
  watch: {
  },
  mounted() {
    this.getWxConfigDatas();
  },
  methods: {
    getWxConfigDatas() {
      const params = {
        checkCode: getUrlParams().checkCode,  // 從地址欄獲取checkCode用於校驗signature
      };
      this.$http.get('/signature.json', { params }).then(res => {
        if (res.content.data && res.content.data.signature) {
          this.registereWxApi(res.content.data.signature);
        }
      });
    },
    registereWxApi(data) {
      wx.config({
        // debug: true,
        jsApiList: ['chooseImage', 'getLocalImgData'],
        appId: data.appId,
        timestamp: data.timestamp,
        nonceStr: data.nonceStr,
        signature: data.signature,
      });
    },
    chooseImage() {
      let that = this;
      if (window.__wxjs_environment === 'miniprogram') {
        wx.miniProgram.getEnv(function(res) {
          if (res.miniprogram) {
            wx.checkJsApi({
              jsApiList: ['chooseImage', 'getLocalImgData'],
              success: function(res) {
                if (res.checkResult.chooseImage) {
                  wx.chooseImage({
                    count: 1,
                    sizeType: ['compressed'],
                    sourceType: ['album', 'camera'],
                    success: function(req) {
                      wx.getLocalImgData({
                        localId: req.localIds[0].toString(),
                        success: function (res) {
                          const localData = res.localData;
                          let imageBase64 = '';
                          if (localData.indexOf('data:image') == 0) {
                            //蘋果的直接賦值,默認生成'data:image/jpeg;base64,'的頭部拼接
                            imageBase64 = localData;
                          } else {
                            //此處是安卓中的惟一得坑!在拼接前須要對localData進行換行符的全局替換
                            //此時一個正常的base64圖片路徑就完美生成賦值到img的src中了
                            imageBase64 = 'data:image/jpeg;base64,' + localData.replace(/\n/g, '');
                          }
                          that.avatarUrl = imageBase64;
                          that.handleAvatar(that.dataURLtoBlob(imageBase64));
                        }
                      });
                    },
                    fail() {
                      that.$toast.show({
                        type: 'text',
                        text: '選擇頭像失敗!',
                      });
                    }
                  });
                } else {
                  that.$toast.show({
                    type: 'text',
                    text: '暫不支持修改頭像!',
                  });
                }
              },
              fail: function() {
                that.$toast.show({
                  type: 'text',
                  text: '暫不支持修改頭像!',
                });
              },
            });
          } else {
            that.lgBridgeChooseImage();
          }
        });
      } else {
        that.lgBridgeChooseImage();
      }
    },
    handleAvatar(imageData) {
        let that = this;
        let param = new FormData();
        param.append('headPic', imageData);
        that.$http.post('/upload.json', {
            headers: {
                'Content-Type': 'multipart/form-data',
            },
        params: param,
        }).then(res => {
        if (parseInt(res.state, 10) === 1) {
            that.$toast.show({
                type: 'success',
                text: '上傳成功!',
            });
          that.avatarUrl = res.content && res.content.data && res.content.data.url;
        }
      });
    },
    dataURLtoBlob(dataurl) {
      let arr = dataurl.split(',');
      let mime = arr[0].match(/:(.*?);/)[1];
      let bstr = atob(arr[1]);
      let n = bstr.length;
      let u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    }
  }
};
</script>

<style lang="less" scoped>
.card-avatar {
    position: absolute;
    top: 0;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 180px;
    height: 180px;
    border-radius: 100%;
    overflow: hidden;
    img {
      width: 100%;
      height: 100%;
    }
    .choose-image, input {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      opacity: 0;
    }
}
</style>
複製代碼

這個需求作下來參考了很多文章,其實大多都是隻知其一;不知其二,每一個人遇到的問題不一樣,這篇文章能夠說完全解決wx.chooseImage的各類疑難雜症,一步到位,所以趁着今天休息把整個過程記錄下來,分享給以後有須要的各位同行!

精彩預告:最近歷時兩個月的大需求遇到不少深坑,接下來會相繼分享出來,以此共勉😂

更多精彩內容歡迎關注個人公衆號【天道酬勤Lewis】

相關文章
相關標籤/搜索