当前位置:   article > 正文

从实际营销活动案例看 H5 与 WebView 的交互

从实际营销活动案例看 H5 与 WebView 的交互

从实际案例看 H5 与 WebView 交互

现在运营需要设计一个活动页面,这个页面需要支持如下功能:

  • 支持分享到微信的好友和朋友圈
  • 支持长按保存图片
  • 支持打开小程序(一键获取干货)

同时,这个活动页面不仅能在微信浏览器打开,还希望能在 APP 内部打开,并且同样支持上述功能。

现在按终端把需求进行拆解:

  • 微信端
  • APP 端

当前专注于在微信中实现各项功能,接下来我们将一一实现这些功能。

1. 微信内

1.1 实现分享到朋友圈、会话功能

需要借助 JS-SDK 来完成相关的功能,大概步骤如下:

  • 先登录微信公众平台进入公众号设置功能设置里填写JS 接口安全域名

在这里插入图片描述

  • 引入 JS 文件

大部分情况下,都是使用单页应用模式,可以通过直接在入口文件中引入或者通过动态加载的方式来使用。在这里,直接在入口文件中引入的方式更为方便。

<script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
  • 1
  • 通过 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,
      })
    );
  }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
// 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();
    });
  },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 通过 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();
    });
  },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 通过 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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

上面完成微信内如何实现分享到朋友圈、分享到会话的功能。包括公众号配置、服务端生成签名、客户端请求对应签名信息以及完成相应的初始化。

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

目前已经实现了在微信浏览器内部的功能,包括微信分享、长按保存图片到相册、打开小程序等。接下来我们将优化这些功能,使其能够在 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 引入原生模块

在这里插入图片描述

说明下,我们的 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>
  • 1
  • 2
  • 3
  • 4
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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151

在 H5 页面上,我们已经成功实现了与 APP 的交互,并成功调用了 APP 提供的分享到朋友圈和会话的功能。

2.3 完成长按保存图片到相册

在微信浏览器内部,我们可以通过微信提供的长按图片功能来保存图片。然而,在 APP 中,我们需要自己来实现长按保存功能。现在我们来分析一下如何实现。

首先,在 H5 中,我们可以获取到图片对应的 base64 URL。同样,在APP 中,我们可以使用 saveImageToPhotosAlbum 函数来保存图片到相册。只要将图片数据发送给 APP,就可以成功将图片存储到相册中。

2.3.1 分析下 saveImageToPhotosAlbum 参数
uni.saveImageToPhotosAlbum({
  filePath:
    '图片文件路径,可以是临时文件路径也可以是永久文件路径,不支持网络图片路径',
  success: () => {},
  fail: () => {},
  complete: () => {},
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

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
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

到目前为止,我们已经成功在微信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 图片格式存储到本地系统目录中,并最终将图片保存到相册中。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号