uniapp 微信小程序webview 踩坑

uniapp


微信小程序的存在许多功能上的限制和约束,有些情况不得不去使用webview进行开发实现需求,比如

原生无法满足(例如某团队维护SDK 只提供了WEB端jsSDK,且不维护小程序SDK)
H5可以同时适用多端(适用范围更广)
H5可以弥补小程序部分欠缺
微信生态有部分限制(包大小,设计规范等)

由于最近做的需求小程序不支持播放带有透明通道的视频,所以转到了webview

这里总结下完整的开发流程和现有的各种解决方案


: 开发阶段需要将不校验合法域名勾选上


webview样式

当小程序嵌入webview之后,会自动铺满一整个页面,设置宽高无效,并覆盖其他组件

并且会带有原生的导航栏,这个导航栏是无法消除的,即使在小程序端和webview端设置navigationStyle: custom都无效

在这里插入图片描述

导航栏的标题会先加载小程序端的pages.json中对应组件的navigationBarTitleText,如果没有则读globalStyle中的值

然后标题会刷新为webview端的对应组件的navigationBarTitleText,如果没有则读globalStyle中的值

如果webview端都没有navigationBarTitleText,则只会显示小程序端的值,不会刷新

如果都没有,则显示空白,可以改变其背景颜色,仅支持十六进制颜色码



微信小程序和webview通信

  • 小程序可以通过url拼接参数的方式向webview端传参

  • webview端向小程序传参现有只能通过wx.miniProgram.postMessage进行传参,但是这个api非常有局限性
    只有在特定时机(小程序后退、组件销毁、分享)触发组件的 message 事件,所以基本没用

  • 只能通过webSocket进行长连接发送接收消息,但是会增加服务器压力

小程序向web-view传参非常轻松,反过来则非常折磨

web-view 网页与小程序之间不支持除 JSSDK 提供的api之外的通信

如果调用JSSDK的api并不能解决问题,并且不想使用websocket,最好使用小程序单一实现

在 iOS 中,若存在 JSSDK 接口调用无响应的情况,可在 web-view 的 src 后面加个#wechat_redirect解决



JSSDK

想要在webview端调用小程序的接口,都需要jssdk 官方文档

绑定域名

如果想要在webview中使用jssdk,那么需要设置JS接口安全域名

由于我们使用的是webview(视为公众号),并不是小程序(小程序只是一个承载webview的壳子)

因此是在公众号设置的功能设置中填写JS接口安全域名,webview所在的域名设置进去后才允许鉴权

注意: 设置JS接口安全域名需要公网ICP备案了的服务器,也就是说本地的环境没办法使用,必须在线的服务器环境

在这里插入图片描述
如果webview部署所在的路径为www.abc.xx/page,那么设置域名也应该为www.abc.xx/page(没有测试过,最好直接www.abc.xx能直接访问webview为佳)

业务域名js接口安全域名网页授权域名可以一起设置为相同的,都为webview部署所在的域名即可

设置的时候需要保存一个配置文件到对应的目录,下载下来放进去即可,如果设置失败大概率是设置了代理跳转了没有读到文件

nginx的设置参考 nginx配置


引入JS文件

然后就是在webview中引入jssdk的js文件

然而如果通过<script>的方式引入,会出现wx.config is not a function

这是由于uniapp本身具有wx对象,因此会跟jssdk中的wx对象冲突


这里解决方案是在分别保存两个js文件然后通过import重命名进行引入

首先打开两个js文件的地址

https://res.wx.qq.com/open/js/jweixin-1.6.0.js
https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js

然后分别保存js到本地的js文件,通过import引入,然后将jssdk的wx变量重命名为jweixin

import jweixin from './jweixin.js'
import wxx from './jwxwork-1.0.0.js'

config注入权限

如果需要调用小程序的api则必须要通过授权并且注册需要使用的接口,才能进行调用

所以首先需要config进行权限校验

wx.config({debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId: '', // 必填,公众号的唯一标识timestamp: , // 必填,生成签名的时间戳nonceStr: '', // 必填,生成签名的随机串signature: '',// 必填,签名jsApiList: [] // 必填,需要使用的JS接口列表
});
  • debug: 设置为true时,通过或不通过校验,调用api的结果都会进行弹窗提示,调试阶段可以先打开
  • appid : 这个appid以及后面校验需要用到的AppSecret都必须是公众号的
    如果使用了小程序的appid、AppSecret也能拿到access_token,但是后续签名会校验不通过(没有提示,非常坑)
  • timestamp、nonceStr: 这是自己定的值,随便填即可,但是后续签名生成也会用到,需要保证这两个地方用的值是一样的
  • jsApiList: 数组中填的就是后续开发需要用的api,只有在这里注册了,才能进行调用,里面可以填的值在文档下面附录2
  • signature: 重点,需要进行验证的签名,这个签名是根据一系列参数生成的

signature

需获取access_token,根据access_token去获取jsapi_ticket, 再根据jsapi_ticket去和其他参数加密获取signature

// 流程如下
const appid = "xxxx"  // 注意appid 和 secret必须是公众号的!
const secret = "xxxx"
const noncestr = "Wm3WZYTPz0wzccnW" // noncestr 和 timestamp随便写,这里用了文档的
const timestamp = 1414587457
const url = 'https://www.abc.xx:5173/' // webview所在的网址,我这里部署在5173端口 
// 因为上面js安全域名设置的www.abc.xx 所以其他端口也是没问题的
// 另外需要注意 这里填写的网址需要跟<web-view src=''>的src的值一致,如果src需要携带参数 那么需要再src中用#隔开
// 如 <web-view :src="`https://www.abc.xx:5173/#/?time=${123}`"></web-view>
// 如果没用#隔开直接`?time=${123}`, 会由于这里加密用的url和webview src的url 不一致(???) 而签名失败// const wxApiUrl = 'https://api.weixin.qq.com/cgi-bin'
const wxApiUrl = 'https://www.abc.xx:5173/api' // 由于直接请求可能会有跨域问题, 因此还需要在nginx中设置代理(这里就省略了, 就是设置proxy_pass)const encodeUTF8 = (s) => {var i, r = [], c, x;for (i = 0; i < s.length; i++)if ((c = s.charCodeAt(i)) < 0x80) r.push(c);else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F));else {if ((x = c ^ 0xD800) >> 10 == 0) //对四字节UTF-16转换为Unicodec = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000,r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F));else r.push(0xE0 + (c >> 12 & 0xF));r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));};return r;
}// 字符串加密成 hex 字符串
const sha1 = (s) => {var data = new Uint8Array(encodeUTF8(s))var i, j, t;var l = ((data.length + 8) >>> 6 << 4) + 16, s = new Uint8Array(l << 2);s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer);for (t = new DataView(s.buffer), i = 0; i < l; i++)s[i] = t.getUint32(i << 2);s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8);s[l - 1] = data.length << 3;var w = [], f = [function () { return m[1] & m[2] | ~m[1] & m[3]; },function () { return m[1] ^ m[2] ^ m[3]; },function () { return m[1] & m[2] | m[1] & m[3] | m[2] & m[3]; },function () { return m[1] ^ m[2] ^ m[3]; }], rol = function (n, c) { return n << c | n >>> (32 - c); },k = [1518500249, 1859775393, -1894007588, -899497514],m = [1732584193, -271733879, null, null, -1009589776];m[2] = ~m[0], m[3] = ~m[1];for (i = 0; i < s.length; i += 16) {var o = m.slice(0);for (j = 0; j < 80; j++)w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1),t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0,m[1] = rol(m[1], 30), m.pop(), m.unshift(t);for (j = 0; j < 5; j++)m[j] = m[j] + o[j] | 0;};t = new DataView(new Uint32Array(m).buffer);for (var i = 0; i < 5; i++)m[i] = t.getUint32(i << 2);var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function (e) {return (e < 16 ? "0" : "") + e.toString(16);}).join("");return hex;
}class useStorage {/*** 额外设置一条 `key__expires__: 时间戳` 的storage来判断过期时间* @param {string} key* @param {any} value* @param {number} expired 过期时间 以分钟为单位* @returns {any}*/setItem(key, value, expired) {uni.setStorageSync(key, JSON.stringify(value))if (expired) {uni.setStorageSync(`${key}__expires__`, Date.now() + 1000 * 60 * expired)}return value;}/*** 获取storage时先获取`key__expires__`的值判断时间是否过期 * 过期则清空该两条storage 返回空* @param {string} key* @returns {any}*/getItem(key) {let expired = uni.getStorageSync(`${key}__expires__`) || Date.now + 1;const now = Date.now();if (now >= expired) {uni.removeStorageSync(key)uni.removeStorageSync(`${key}__expires__`)return;}return uni.getStorageSync(key) ? JSON.parse(uni.getStorageSync(key)) : uni.getStorageSync(key);}
}const storage = new useStorage();const fetchUrl = (url) => {return fetch(url).then((response) => {return response.json()})
}// 主要逻辑  为了便于理解把工具函数全部整合到一块代码了 实际上可以把工具函数进行抽离封装
const fetToken = async () => {let save_ticket = storage.getItem('TICKET')console.log('ticket', save_ticket)if(!save_ticket){try{const {access_token: token} = await fetchUrl(`${wxApiUrl}/token?grant_type=client_credential&appid=${appid}&secret=${secret}`)const {ticket} = await fetchUrl(`${wxApiUrl}/ticket/getticket?access_token=${token}&type=jsapi`)save_ticket = ticketstorage.setItem('TICKET', ticket, 100)}catch{uni.showToast({title: '授权异常',duration: 1000})}}const result = `jsapi_ticket=${save_ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`console.log('result', result)return sha1(result)
}

得到签名后可以在 微信校验网址 中对比自己生成的签名有没有问题

实际上校验加密这个步骤应该放在服务端,这里为了方便就放在了前端来实现

 onLoad() {// 获取ticket并注册jsApifetToken().then((res) => {if (jweixin) {jweixin.config({debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId: appid, // 必填,公众号的唯一标识timestamp: timestamp, // 必填,生成签名的时间戳nonceStr: noncestr, // 必填,生成签名的随机串signature: res, // 必填,签名jsApiList: ['startRecord', 'stopRecord', 'uploadVoice', 'downloadVoice'] // 必填,需要使用的JS接口列表});}})
},

进行到这一步权限如果获取成功基本就完成配置了,如果微信弹出config: ok就是成功了

小程序和小程序的公众号网页调试工具都是可以查看到config的校验结果的

小程序

在这里插入图片描述
在这里插入图片描述

公众号

在这里插入图片描述
输入网址后跳转

在这里插入图片描述

然后使用jweixin.startRecord调用api即可,如果配置失败则检查哪个字段有错误


jssdk比较脆弱, 且很多方法都是异步的,需要控制调用频率不能过快

比如startRecordstopRecord的调用,如果同时触发是可能 stopRecord先执行完的,导致回调不触发

具体详情可以看这篇博客,这里不过多赘述




其他问题

webview中长按图片会弹出系统菜单

这个会影响uniapp的touchstarttouchend事件,也不太美观

解决方案是在img标签全部添加上csspointer-events:none即可


webview中手指滑动判断方向

需求是手指往左划会弹出菜单,往右则收回菜单,但是uniapp只支持判断滑动和停止滑动,因此需要我们加个判断

<view @touchstart="touchMoveStart" @touchmove="chatExpandMove" @touchend="chatMoveEnd"></view>
export default {data() {return {touchingFlag: false, // 是否正在滑动moveingPosition: { // 正在滑动的手指位置X: 0,Y: 0},startPosition: { // 滑动开始时点击的位置X: 0,Y: 0},endPosition: { // 滑动结束时手指的位置X: 0,Y: 0},Xflag: false, // 是否在x轴滑动Yflag: false, // 是否在y轴滑动startMoveTime: 0, // 开始滑动的时间 用于判断手指滑动的时间 过短则不操作}},methods: {touchMoveStart() {this.startMoveTime = Date.now()this.startPosition.X = event.changedTouches[0].clientXthis.startPosition.Y = event.changedTouches[0].clientY},/*** 通过当前手指的位置和开始滑动时点击的位置来判断手指滑动的方向*/chatExpandMove() {this.touchingFlag = true //移动进行this.moveingPosition.X = event.changedTouches[0].clientXthis.moveingPosition.Y = event.changedTouches[0].clientYlet Xlength = parseInt(Math.abs(this.moveingPosition.X - this.startPosition.X))let Ylength = parseInt(Math.abs(this.moveingPosition.Y - this.startPosition.Y))if (Xlength > Ylength && Xlength > 10 && this.Yflag == false) { //x轴方向this.Xflag = truelet direction = this.moveingPosition.X - this.startPosition.X > 0 ? "right" : "left"if (direction === 'right') {// 右} else if (direction === 'left') {// 左}}if (Xlength < Ylength && Ylength > 10 && this.Xflag == false) { //Y轴方向this.Yflag = truelet direction = this.moveingPosition.Y - this.startPosition.Y > 0 ? "down" : "up"if (direction === "up") {// 上} else if (direction === 'down') {// 下}}},/*** 滑动结束时判断手指滑动的距离 如果时间过短或距离过短 则取消影响*/chatMoveEnd() {//关闭手指移动的标识this.touchingFlag = falseconst endTime = Date.now()if (endTime - this.startTime < 300) {// 如果手指滑动的距离超过0.3s 就默认不合法return;}//获取滑动结束后的坐标this.endPosition.X = event.changedTouches[0].clientXthis.endPosition.Y = event.changedTouches[0].clientYif (Math.abs(this.endPosition.X - this.startPosition.X) > 10 && this.Xflag) { //大于10个单位才有效//long的滑动长度绝对值作为视频s的值。let long = Math.abs(this.endPosition.X - this.startPosition.X)let height = Math.abs(this.endPosition.Y - this.startPosition.Y)//left向前 right向后 let elePosition = this.endPosition.X - this.startPosition.X > 0 ? "right" : "left"// 结束移动时与开始时的 距离 和 高度 }//复原互斥开关this.Xflag = falsethis.Yflag = false},}
}

webview上点击获得视频地址并播放

onload时获取到video对象,获取到地址时调用play即可

这个方法并不是100%有效,当我将video所在的组件迁移到一个子组件中时就失效了,具体原因未知

在页面初始化时直接播放视频仍然无法生效,会报错(DOMException: play() failed because the user didn't interact with the document first)

<video :src="videoPath" autoplay="true" :controls='false'  id='myVideo' @ended='videoEnd'></video>
onLoad() {// 获取video对象 存储起来 在ios中通过这种方法自动播放 // 这个wx不是jweixin 时uniapp中的wxif (wx) this.videoContext = wx.createVideoContext('myVideo', this);
}// 点击播放
this.videoContext && this.videoContext.play()

webview上播放视频

在webview中时无法实现v-show控制视频播放一段时间后再显示的,如果视频处于隐藏状态,那么视频将不会播放

v-show切换为true时也不会在显示时开始播放,会一直处于暂停状态

如果需要video播放一段时间后显示,只能使用css的opacity 为0或1控制其显示或隐藏




总而言之,在uniapp上的开发体验非常糟糕,社区解决问题的效率堪忧,网上的资料也非常零散,浪费了非常多的时间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/91818.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java当中的object介绍以及相关方法介绍

文章目录 前言 1. object介绍 1.1 简单介绍 1.2 object的相关方法 2. object相关方法的介绍 2.0 JAVA同步锁 synchronized(this)、synchronized(class)与synchronized(Object)的区别 2.1 Object之wait 2.1.1 另外两种wait方法 2.1.2 Object中notify&#xff0c;notify…

python中的文件操作

我们平常对文件的基本操作&#xff0c;大概可以分为三个步骤&#xff08;简称文件操作三步走&#xff09;&#xff1a; ① 打开文件 ② 读写文件 ③ 关闭文件 【注意事项】 注意&#xff1a;可以只打开和关闭文件&#xff0c;不进行任何读写 文件打开 open函数&#xff…

【Qt专栏】实现单例程序,禁止程序多开的几种方式

目录 一&#xff0c;简要介绍 二&#xff0c;实现示例&#xff08;Windows&#xff09; 1.使用系统级别的互斥机制 2.通过共享内存&#xff08;进程间通信-IPC&#xff09; 3.使用命名互斥锁&#xff08;不推荐&#xff09; 4.使用文件锁 5.通过网络端口检测 一&#xf…

基于RUM高效治理网站用户体验入门-价值篇

用户体验 用户体验基本包含访问网站的性能、可用性和正确性。通俗的讲&#xff0c;就是一把通过用户访问测量【设计者】意图的尺子。 本文目的 网站如何传递出设计者的意图&#xff0c;可能页面加载时间太长、或者页面在用户的浏览器中渲染时间太慢&#xff0c;或者第三方设备…

css3英文文字换行,超过两行...展示

需求&#xff1a;超过两行...展示 开发的过程中发现div内容中文可以换行英文不换行&#xff0c;导致长度会溢出。 是英文全英文的话浏览器会解析成一个单词&#xff0c; 加上这句就好了 word-break:break-all; 一开始不知道是会解析成一个单词&#xff0c;用字符串拼接处理…

PXE网络批量装机(centos7)

目录 前言 一、实验拓扑图 二、PXE的组件 三、配置PXE装机服务器 1、设置防火墙、selinux 2.安装、启动vsftp 3、拷贝系统文件到/var/ftp用于装机 4、配置tftp 5、准备pxelinx.0文件、引导文件、内核文件 6、配置本机IP 7、配置DHCP服务 8、创建default文件 四、配…

【ThingJS | 3D可视化】开发框架,一站式数字孪生

博主&#xff1a;_LJaXi Or 東方幻想郷 专栏&#xff1a; 数字孪生 | 3D可视化框架 开发工具&#xff1a;ThingJS在线开发工具 ThingJs 低代码开发 ThingJs 低代码开发注意点场景效果配置层级层级常用API实例化 Thing&#xff0c;加载场景load 加载函数ThingJs 层级关系图查找层…

基于MQTT协议的物联网关

随着工业领域的不断发展&#xff0c;数字化转型已经成为企业迈向未来的必由之路。在这个数字化浪潮中&#xff0c;HiWoo Box以其强大的功能和创新的设计&#xff0c;在工业物联网领域被越来越多的人所熟知。特别是其基于MQTT协议的物联网关能力&#xff0c;也为企业实现智能化数…

FBX SDK 开发环境配置 visual studio 2022

FBX | Adaptable File Formats for 3D Animation Software | Autodesk. 下载windows的sdk并安装. 创建一个c console 工程 设置include目录 添加预处理宏 FBX_SHARED1 添加fbx sdk lib 目录 添加依赖lib : libfbxsdk-md.lib libxml2-md.lib zlib-md.lib 配置完毕.

OPENCV实现ORB特征检测

# -*- coding:utf-8 -*- """ 作者:794919561 日期:2023/8/31 """ import cv2 import numpy as np# 读图像 img = cv2.imread(F:\\learnOpenCV\\openCVLearning\\pictures\\chess.jpg)

OS 死锁处理

如果P先申请mutex 则mutex从1置零&#xff0c;假设申请到的empty 0则empty变成-1阻塞态 同理C中mutex从0变为-1&#xff0c;那么如果想离开阻塞态&#xff0c;那么就需要执行V&#xff08;empty&#xff09;但是如果执行V&#xff08;empty&#xff09;就需要P&#xff08;mu…

说说Flink运行模式

分析&回答 1.开发者模式 在idea中运行Flink程序的方式就是开发模式。 2.local-cluster模式 Flink中的Local-cluster(本地集群)模式,单节点运行&#xff0c;主要用于测试, 学习。 3.Standalone模式 独立集群模式&#xff0c;由Flink自身提供计算资源。 4.Yarn模式 把Fl…