博客 / 詳情

返回

從實際營銷活動案例看 H5 與 WebView 的交互

從實際案例看 H5 與 WebView 交互

現在運營需要設計一個活動頁面,這個頁面需要支持如下功能:

<img src="https://img-blog.csdnimg.cn/direct/9cf7eeedd91d48088c3c1695941521cf.jpeg" width="375px"/>
<img src="https://img-blog.csdnimg.cn/direct/a78eef74969a46d0bc7f9348275329eb.jpeg" width="375px"/>

  • 支持分享到微信的好友和朋友圈
  • 支持長按保存圖片
  • 支持打開小程序(一鍵獲取乾貨)

同時,這個活動頁面不僅能在微信瀏覽器打開,還希望能在 APP 內部打開,並且同樣支持上述功能。

現在按終端把需求進行拆解:

  • 微信端
  • APP 端

當前專注於在微信中實現各項功能,接下來我們將一一實現這些功能。

1. 微信內

1.1 實現分享到朋友圈、會話功能

需要藉助 JS-SDK 來完成相關的功能,大概步驟如下:

  • 先登錄微信公眾平台進入公眾號設置功能設置裏填寫JS 接口安全域名

在這裏插入圖片描述

  • 引入 JS 文件

大部分情況下,都是使用單頁應用模式,可以通過直接在入口文件中引入或者通過動態加載的方式來使用。在這裏,直接在入口文件中引入的方式更為方便。

<script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
  • 通過 config 接口注入權限驗證配置
// 服務端與微信後端交互,生成簽名相關信息
const sha1 = require('crypto-js/sha1');
router.post('/getWxConfig', async function (req, res) {
  try {
    // 1. 獲取 access_token
    const { access_token } = await request.get('/cgi-bin/token', {
      grant_type: 'client_credential',
      appid,
      secret,
    });
    // 2. 獲取 ticket
    const { ticket } = await request.get('/cgi-bin/ticket/getticket', {
      access_token,
      type: 'jsapi',
    });
    // 3. 獲取簽名
    const { url } = req.body;
    const noncestr = Math.random().toString(36).substring(2, 15);
    const timestamp = parseInt(new Date().getTime() / 1000) + '';
    const str = `jsapi_ticket=${ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`;
    const signature = sha1(str).toString();
    // 4. 返回數據
    res.json(
      new Result({
        data: {
          appid,
          timestamp,
          nonceStr: noncestr,
          signature,
        },
      })
    );
  } catch (error) {
    res.json(
      new Result({
        code: 'BIZ_ERROR',
        msg: error.errmsg || error.message,
      })
    );
  }
});
// H5 頁面,初始化 wx 配置
import { onMounted } from 'vue';
export default {
  setup() {
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;
          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };
    onMounted(() => {
      initWechatConfig();
    });
  },
};
  • 通過 ready 接口處理成功驗證

當前面簽名相關信息配置成功後,就會正常執行 ready方法。

import { onMounted } from 'vue';
const shareConfig = {
  title: '測一測你是哪種類型的程序員?',
  desc: '代碼世界不止0和1,還有獨特的你!',
  link: `${window.location.origin}/`,
  imgUrl: `${window.location.origin}/thumbnail.png`,
};
export default {
  setup() {
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;
          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });

          wx.ready(() => {
            const { title, desc, link, imgUrl } = shareConfig;
            // 分享會話
            wx.updateAppMessageShareData({
              title, // 分享標題
              desc, // 分享描述
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
            // 朋友圈
            wx.updateTimelineShareData({
              title, // 分享標題
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };

    onMounted(() => {
      initWechatConfig();
    });
  },
};
  • 通過 error 接口處理失敗驗證
<script>
import { onMounted } from 'vue';
const shareConfig = {
  title: '測一測你是哪種類型的程序員?',
  desc: '代碼世界不止0和1,還有獨特的你!',
  link: `${window.location.origin}/`,
  imgUrl: `${window.location.origin}/thumbnail.png`,
};
export default {
  setup() {
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;
          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });

          wx.ready(() => {
            const { title, desc, link, imgUrl } = shareConfig;
            // 分享會話
            wx.updateAppMessageShareData({
              title, // 分享標題
              desc, // 分享描述
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
            // 朋友圈
            wx.updateTimelineShareData({
              title, // 分享標題
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
          });

          wx.error((res) => {
              // todo 
              // 可以在這裏保存狀態,並在操作相關地方提醒用户,防止用户點擊沒任何反饋
            console.log('res', res);
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };

    onMounted(() => {
      initWechatConfig();
    });
  },
};
</script>

上面完成微信內如何實現分享到朋友圈、分享到會話的功能。包括公眾號配置、服務端生成簽名、客户端請求對應簽名信息以及完成相應的初始化。

1.2 完成長按保存圖片到相冊

在微信內部,默認是支持長按圖片進行保存到相冊的。由於海報是通過 canvas 進行繪製的,我們可以使用 canvas 提供的方法 toDataURL 將圖像轉換為對應的 base64 格式的圖片地址,然後通過 img 標籤進行渲染。

<template>
  <img v-if="base64URL" src="base64URL" />
</template>

<script>
import { onMounted, ref } from 'vue';
const shareConfig = {
  title: '測一測你是哪種類型的程序員?',
  desc: '代碼世界不止0和1,還有獨特的你!',
  link: `${window.location.origin}/`,
  imgUrl: `${window.location.origin}/thumbnail.png`,
};
export default {
  setup() {
    const base64URL = ref('');
    // 初始化公眾號配置
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;

          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });

          wx.ready(() => {
            const { title, desc, link, imgUrl } = shareConfig;
            // 分享會話
            wx.updateAppMessageShareData({
              title, // 分享標題
              desc, // 分享描述
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
            // 朋友圈
            wx.updateTimelineShareData({
              title, // 分享標題
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
          });

          wx.error((res) => {
            console.log('res', res);
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };

    const drawPoster = () => {
      // 直接創建元素,在內存中直接完成繪製
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      const dpr = window.devicePixelRatio;
      canvas.width = 375;
      canvas.height = 1334;
      // 繪製圖片
      // 繪製文本
      // ...
      base64URL.value = canvas.toDataURL();
    };

    onMounted(() => {
      initWechatConfig();
      drawPoster();
    });

    return {
      base64URL,
    };
  },
};
</script>

1.3 打開小程序

這就非常簡單了,只需要獲取到小程序對應 URL Scheme 即可。

<template>
  <img v-if="base64URL" src="base64URL" />
  <button @click="onGetCourse">一鍵獲取乾貨</button>
</template>

<script>
import { onMounted, ref } from 'vue';
const shareConfig = {
  title: '測一測你是哪種類型的程序員?',
  desc: '代碼世界不止0和1,還有獨特的你!',
  link: `${window.location.origin}/`,
  imgUrl: `${window.location.origin}/thumbnail.png`,
};
export default {
  setup() {
    const base64URL = ref('');
    // 初始化公眾號配置
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;

          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });

          wx.ready(() => {
            const { title, desc, link, imgUrl } = shareConfig;
            // 分享會話
            wx.updateAppMessageShareData({
              title, // 分享標題
              desc, // 分享描述
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
            // 朋友圈
            wx.updateTimelineShareData({
              title, // 分享標題
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
          });

          wx.error((res) => {
            console.log('res', res);
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };

    const drawPoster = () => {
      // 直接創建元素,在內存中直接完成繪製
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      const dpr = window.devicePixelRatio;
      canvas.width = 375;
      canvas.height = 1334;
      // 繪製圖片
      // 繪製文本
      // ...
      base64URL.value = canvas.toDataURL();
    };

    const openMiniProgram = () => {
      const miniURLScheme = 'weixin://dl/business/?t=dgQQRtdOgOs';
      location.href = miniURLScheme;
    };

    const onGetCourse = () => {
      openMiniProgram();
    };

    onMounted(() => {
      initWechatConfig();
      drawPoster();
    });

    return {
      base64URL,
      onGetCourse,
    };
  },
};
</script>

目前已經實現了在微信瀏覽器內部的功能,包括微信分享、長按保存圖片到相冊、打開小程序等。接下來我們將優化這些功能,使其能夠在 APP 內部實現

2. APP 內

在微信內打開 H5 時,分享朋友圈、好友等功能通常是在微信宿主環境下結合 jweixin sdk 實現的。然而,當網頁在 APP 中打開時,宿主環境變成了 APP,此時 jweixin sdk 將失效。

要實現這些功能,我們需要從 APP 這個宿主環境上來想辦法。完成這些功能其實並不難。

將功能分解為:

  • APP 與 微信交換
  • APP 與 H5 交互

首先,APP 可以通過引入原生的 share 模塊與微信 APP 進行交互。

現在只需要解決 H5APP 的交互就可以實現這些功能。也就是説,我們需要讓 H5 能夠調用 APP 中的分享功能,這樣就可以實現在 APP 中打開網頁時也能享受到微信的分享功能。

通常,當 webviewH5 頁面需要進行交互時,會使用橋接方式,也就是所謂的"jsbridge"。簡單來説,原生應用會在 webview 的上下文中注入一些 API,使得 H5 頁面可以通過訪問這些 API 與原生應用進行交互。同時,H5 頁面也可以通過特定的渠道來接收原生應用發送的消息,實現雙向通信。

在 uniapp 中也不例外, uniapp 封裝 webviewAPI 成特定的 sdk 供 H5 調用,這樣做的好處是方便維護和更新迭代。即使內部 API 發生變更,對外使用的 API 也可以保持不變。這樣可以減少對 H5 開發者的影響,同時也提高了代碼的穩定性和可維護性。

上面把整個交互流程捋順了,接下來可以進入實際編碼環節了。

2.1 APP 引入原生模塊

在這裏插入圖片描述

  • 微信 appid 申請步驟: https://ask.dcloud.net.cn/article/208
  • iOS 平台微信 SDK 配置通用鏈接: https://ask.dcloud.net.cn/article/36445

説明下,我們的 APP 是通過 uniapp 開發的。上圖是 HBuilderX 配置的截圖。

APP 分享功能配置完成後,接着實現 H5 與 APP 交互。

2.2 實現分享到朋友圈、會話功能

2.2.1 H5 頁面中引入 uni.webview.js
<script
  type="text/javascript"
  src="https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.5.4.js"
></script>
2.2.2 定義 webview 加載 H5 頁面
<template>
  <web-view :src="src"></web-view>
</template>

<script>
let wv;
export default {
  data() {
    return {
      src: '',
    };
  },
  onLoad(options) {
    this.src = options.src;
    // #ifdef APP-PLUS
    // 此對象相當於 html5plus 裏的 plus.webview.currentWebview()。在uni-app裏vue頁面直接使用plus.webview.currentWebview()無效,非v3編譯模式使用this.$mp.page.
    const currentWebview = this.$scope.$getAppWebview();
    setTimeout(function () {
      wv = currentWebview.children()[0];
      wv.setStyle({
        scalable: false,
      });
    }, 200); //如果是頁面初始化調用時,需要延時一下
    // #endif
  },
};
</script>
2.2.3 定義 webviewmessage 事件監聽器,處理 H5 發送的事件
  • APP 監聽消息
<template>
  <web-view :src="src" @message="message"></web-view>
</template>

<script>
let wv;
export default {
  data() {
    return {
      src: '',
    };
  },
  onLoad(options) {
    this.src = options.src;
    // #ifdef APP-PLUS
    // 此對象相當於 html5plus 裏的 plus.webview.currentWebview()。在uni-app裏vue頁面直接使用plus.webview.currentWebview()無效,非v3編譯模式使用this.$mp.page.
    const currentWebview = this.$scope.$getAppWebview();
    setTimeout(function () {
      wv = currentWebview.children()[0];
      wv.setStyle({
        scalable: false,
      });
    }, 200); //如果是頁面初始化調用時,需要延時一下
    // #endif
  },
  methods: {
    message(event) {
      /**
       * 1. webview 需要接受多種消息類型,不同的消息類型業務邏輯不同。 考慮後面擴展把消息格式約定如下:
       * {
       *    eventType: 'share|saveImageToPhotosAlbum'
       *    data: {}
       * }
       */
      const data = event.detail.data;
      if (Array.isArray(data) && data.length > 0) {
        const { eventType, data } = data[0];
        switch (config.eventType) {
          case 'share':
            uni.share(data);
            break;
          default:
            break;
        }
      }
    },
  },
};
</script>
  • H5 發送事件
<template>
  <img v-if="base64URL" src="base64URL" />
  <button @click="onGetCourse">一鍵獲取乾貨</button>
  <button @click="onAppMessageShare">分享至微信好友</button>
  <button @click="onTimelineShare">分享至朋友圈</button>
</template>

<script>
import { onMounted, ref } from 'vue';
const shareConfig = {
  title: '測一測你是哪種類型的程序員?',
  desc: '代碼世界不止0和1,還有獨特的你!',
  link: `${window.location.origin}/`,
  imgUrl: `${window.location.origin}/thumbnail.png`,
};
const wxSceneSession = 'WXSceneSession'; // 微信會話
const wxSceneTimeline = 'WXSceneTimeline'; // 微信朋友圈
export default {
  setup() {
    const base64URL = ref('');
    const isWechatBrowser = () => {
      const ua = window.navigator.userAgent.toString();
      return ua.includes('MicroMessenger');
    };
    // 在 uniapp webview 中
    const inWebviewInner = '__WebVieW_Id__' in window;

    // 初始化公眾號配置
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;

          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });

          wx.ready(() => {
            const { title, desc, link, imgUrl } = shareConfig;
            // 分享會話
            wx.updateAppMessageShareData({
              title, // 分享標題
              desc, // 分享描述
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
            // 朋友圈
            wx.updateTimelineShareData({
              title, // 分享標題
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
          });

          wx.error((res) => {
            console.log('res', res);
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };

    const drawPoster = () => {
      // 直接創建元素,在內存中直接完成繪製
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      const dpr = window.devicePixelRatio;
      canvas.width = 375;
      canvas.height = 1334;
      // 繪製圖片
      // 繪製文本
      // ...
      base64URL.value = canvas.toDataURL();
    };

    const openMiniProgram = () => {
      const miniURLScheme = 'weixin://dl/business/?t=dgQQRtdOgOs';
      location.href = miniURLScheme;
    };

    const onGetCourse = () => {
      openMiniProgram();
    };

    const sendMessage = (scene) => {
      if (window.webUni) {
        window.webUni.postMessage({
          data: {
            eventType: 'share',
            provider: 'weixin',
            scene,
            type: 0,
            href: shareConfig.link,
            title: shareConfig.title,
            summary: shareConfig.desc,
            imageUrl: shareConfig.imgUrl,
          },
        });
      } else {
        uni.showToast({
          title: '分享功能初始化失敗',
        });
      }
    };

    const onAppMessageShare = () => {
      sendMessage(wxSceneSession);
    };

    const onTimelineShare = () => {
      sendMessage(wxSceneTimeline);
    };

    const addEventListener = () => {
      document.addEventListener('UniAppJSBridgeReady', () => {
        console.log(window.webUni);
      });
    };

    onMounted(() => {
      // 這裏按需執行,只在微信瀏覽器才執行對應的初始化操作。理想情況下,對應 wxsdk 也是可以在微信環境才去加載,這裏方便就直接在入口文件中引入了。
      if (isWechatBrowser()) {
        initWechatConfig();
      } else if (inWebviewInner) {
        addEventListener();
      }
      drawPoster();
    });
    return {
      base64URL,
      onGetCourse,
      onAppMessageShare,
      onTimelineShare,
    };
  },
};
</script>

在 H5 頁面上,我們已經成功實現了與 APP 的交互,併成功調用了 APP 提供的分享到朋友圈和會話的功能。

2.3 完成長按保存圖片到相冊

在微信瀏覽器內部,我們可以通過微信提供的長按圖片功能來保存圖片。然而,在 APP 中,我們需要自己來實現長按保存功能。現在我們來分析一下如何實現。

首先,在 H5 中,我們可以獲取到圖片對應的 base64 URL。同樣,在APP 中,我們可以使用 saveImageToPhotosAlbum 函數來保存圖片到相冊。只要將圖片數據發送給 APP,就可以成功將圖片存儲到相冊中。

2.3.1 分析下 saveImageToPhotosAlbum 參數
uni.saveImageToPhotosAlbum({
  filePath:
    '圖片文件路徑,可以是臨時文件路徑也可以是永久文件路徑,不支持網絡圖片路徑',
  success: () => {},
  fail: () => {},
  complete: () => {},
});

filePath 並不支持直接傳入 base64 格式以及網絡圖片,只能把圖片先保存到本地文件系統目錄下。 怎麼把 base64 圖片保存到本地文件系統目錄下? [Bitmap](https://www.dcloud.io/docs/api/zh_cn/nativeobj.html#plus.nativeObj.Bitmap)。 在 HTML5+ 中,提供通過 window.plus.nativeObj.Bitmap 方式創建 bitmap 對象,通過 bitmap 實例中 loadBase64Data 加載 Base64 編碼格式圖片到 Bitmap 對象中,再調用save 方法保存圖片(保存到本地文件系統中,如果圖片為空或者指定的路徑文件已經存在則返回失敗)。

2.3.2 圖片添加長按事件
<!-- H5頁面 -->
<template>
  <img v-if="base64URL" src="base64URL" @longtap="onLongtap" />
  <button @click="onGetCourse">一鍵獲取乾貨</button>
  <button @click="onAppMessageShare">分享至微信好友</button>
  <button @click="onTimelineShare">分享至朋友圈</button>
</template>

<script>
import { onMounted, ref } from 'vue';
const shareConfig = {
  title: '測一測你是哪種類型的程序員?',
  desc: '代碼世界不止0和1,還有獨特的你!',
  link: `${window.location.origin}/`,
  imgUrl: `${window.location.origin}/thumbnail.png`,
};
const wxSceneSession = 'WXSceneSession'; // 微信會話
const wxSceneTimeline = 'WXSceneTimeline'; // 微信朋友圈
export default {
  setup() {
    const base64URL = ref('');
    const isWechatBrowser = () => {
      const ua = window.navigator.userAgent.toString();
      return ua.includes('MicroMessenger');
    };
    // 在 uniapp webview 中
    const inWebviewInner = '__WebVieW_Id__' in window;

    // 初始化公眾號配置
    const initWechatConfig = () => {
      return getWxConfig({
        url: location.href,
      })
        .then(({ data }) => {
          const { appId, timestamp, noncestr, signature } = data;

          wx.config({
            debug: false, // 開發環境使用
            appId, // 必填,公眾號的唯一標識
            timestamp, // 必填,生成簽名的時間戳
            nonceStr, // 必填,生成簽名的隨機串
            signature, // 必填,簽名
            jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'], // 必填,需要使用的JS接口列表
          });

          wx.ready(() => {
            const { title, desc, link, imgUrl } = shareConfig;
            // 分享會話
            wx.updateAppMessageShareData({
              title, // 分享標題
              desc, // 分享描述
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
            // 朋友圈
            wx.updateTimelineShareData({
              title, // 分享標題
              link, // 分享鏈接,該鏈接域名或路徑必須與當前頁面對應的公眾號JS安全域名一致
              imgUrl, // 分享圖標
              success() {
                // todo...
              },
            });
          });

          wx.error((res) => {
            console.log('res', res);
          });
        })
        .catch((e) => {
          console.error('getWxConfig error:::', e);
        });
    };

    const drawPoster = () => {
      // 直接創建元素,在內存中直接完成繪製
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
      const dpr = window.devicePixelRatio;
      canvas.width = 375;
      canvas.height = 1334;
      // 繪製圖片
      // 繪製文本
      // ...
      base64URL.value = canvas.toDataURL();
    };

    const openMiniProgram = () => {
      const miniURLScheme = 'weixin://dl/business/?t=dgQQRtdOgOs';
      location.href = miniURLScheme;
    };

    const onGetCourse = () => {
      openMiniProgram();
    };

    const sendMessage = (scene) => {
      if (window.webUni) {
        window.webUni.postMessage({
          data: {
            eventType: 'share',
            provider: 'weixin',
            scene,
            type: 0,
            href: shareConfig.link,
            title: shareConfig.title,
            summary: shareConfig.desc,
            imageUrl: shareConfig.imgUrl,
          },
        });
      } else {
        uni.showToast({
          title: '分享功能初始化失敗',
        });
      }
    };

    const onAppMessageShare = () => {
      sendMessage(wxSceneSession);
    };

    const onTimelineShare = () => {
      sendMessage(wxSceneTimeline);
    };

    const addEventListener = () => {
      document.addEventListener('UniAppJSBridgeReady', () => {
        console.log(window.webUni);
      });
    };

    const onLongtap = () => {
      if (window.plus) {
        const bitmap = new window.plus.nativeObj.Bitmap('poster');
        bitmap.loadBase64Data(base64URL.value, () => {
          const url = `_doc/${Date.now()}.png`;
          bitmap.save(
            url,
            {
              overwrite: true,
            },
            (i) => {
              window.webUni.postMessage({
                data: {
                  eventType: 'saveImageToPhotosAlbum',
                  data: i.target,
                },
              });
            },
            () => {
              uni.showToast({
                title: '保存圖片失敗',
              });
            }
          );
        });
      }
    };

    onMounted(() => {
      // 這裏按需執行,只在微信瀏覽器才執行對應的初始化操作。理想情況下,對應 wxsdk 也是可以在微信環境才去加載,這裏方便就直接在入口文件中引入了。
      if (isWechatBrowser()) {
        initWechatConfig();
      } else if (inWebviewInner) {
        addEventListener();
      }
      drawPoster();
    });
    return {
      base64URL,
      onGetCourse,
      onAppMessageShare,
      onTimelineShare,
    };
  },
};
</script>
2.3.3 webview 處理自定義的 saveImageToPhotosAlbum 事件
<template>
  <web-view :src="src" @message="message"></web-view>
</template>

<script>
let wv;
export default {
  data() {
    return {
      src: '',
    };
  },
  onLoad(options) {
    this.src = options.src;
    // #ifdef APP-PLUS
    // 此對象相當於 html5plus 裏的 plus.webview.currentWebview()。在uni-app裏vue頁面直接使用plus.webview.currentWebview()無效,非v3編譯模式使用this.$mp.page.
    const currentWebview = this.$scope.$getAppWebview();
    setTimeout(function () {
      wv = currentWebview.children()[0];
      wv.setStyle({
        scalable: false,
      });
    }, 200); //如果是頁面初始化調用時,需要延時一下
    // #endif
  },
  methods: {
    message(event) {
      /**
       * 1. webview 需要接受多種消息類型,不同的消息類型業務邏輯不同。 考慮後面擴展把消息格式約定如下:
       * {
       *    eventType: 'share|saveImageToPhotosAlbum|lanuchMinPro'
       *    data: {}
       * }
       */
      const data = event.detail.data;
      if (Array.isArray(data) && data.length > 0) {
        const { eventType, data } = data[0];
        switch (config.eventType) {
          case 'share':
            uni.share(data);
            break;
          case 'saveImageToPhotosAlbum':
            uni.saveImageToPhotosAlbum({
              filePath: config.data,
              success: () => {
                uni.showToast({
                  title: '圖片保存成功',
                });
              },
              fail: () => {
                uni.showToast({
                  title: '圖片保存失敗',
                });
              },
            });
            break;
          default:
            break;
        }
      }
    },
  },
};
</script>

2.4 打開小程序

APP 中打開小程序,可以通過如下的方式:

// 方式一:
const openMiniProgram = () => {
  const miniURLScheme = 'weixin://dl/business/?t=dgQQRtdOgOs';
  location.href = miniURLScheme;
};

// 方式二:
const openMiniProgram2 = () => {
  // #ifdef APP
  plus.share.getServices(function (res) {
    let sweixin = null;
    //
    for (let i = 0; i < res.length; i++) {
      let t = res[i];
      if (t.id === 'weixin') {
        sweixin = t;
      }
    }

    if (sweixin) {
      sweixin.launchMiniProgram({
        id: 'gh_245f93d8f342',
        path: '/pages/mine/index',
        type: 0,
      });
    } else {
      uni.showToast({
        title: '當前環境不支持微信操作!',
      });
    }
  });
  // #endif
};

到目前為止,我們已經成功在微信APP 的不同宿主環境中分別實現了所有功能。

3. 常見問題

3.1 如何在本地調試微信分享功能

  • 下載花生殼
  • 配置外網映射(將本地 H5 項目 IP 和端口映射到外網)
  • 在公眾號將外網映射域名添加到公眾號中的JS 接口安全域名
  • 這樣就可以很方便調試公眾號相關的分享功能了

    3.2 配置服務器白名單

    在服務端調用微信接口時,如果沒有配置服務器白名單,就會出現如下錯誤:

  • "invalid ip 175.9.143.154 ipv6 ::ffff:175.9.143.154, not in whitelist rid: 6571d1d4-7f45e7df-2d05fc1b"

原因是微信 access_token 刷新需要添加服務器白名單

在這裏插入圖片描述

注意:配置完成後,並不會實時生效。

如果您有任何疑問,請隨時在評論區留言。

總結

本文介紹瞭如何利用 jweixin sdk 在微信內部完成會話和朋友圈分享。同時,還講解了 webviewH5 交互的原理,並介紹瞭如何藉助 APP 的能力實現微信分享功能。最後,還詳細講解了如何利用 bitmapH5 中的 base64 圖片格式存儲到本地系統目錄中,並最終將圖片保存到相冊中。

user avatar zzd41 頭像 peter-wilson 頭像 dujing_5b7edb9db0b1c 頭像 hightopo 頭像 flymon 頭像 shaochuancs 頭像 sunhengzhe 頭像 zhangxishuo 頭像 tingzhong666 頭像 niumingxin 頭像 b_a_r_a_n 頭像 user_p5fejtxs 頭像
46 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.