uniapp 微信小程序webview 踩坑

这篇具有很好参考价值的文章主要介绍了uniapp 微信小程序webview 踩坑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

uniapp


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

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

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

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


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


webview样式

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

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

uniapp 微信小程序webview 踩坑,else,uni-app,webview,微信小程序

导航栏的标题会先加载小程序端的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备案了的服务器,也就是说本地的环境没办法使用,必须在线的服务器环境

uniapp 微信小程序webview 踩坑,else,uni-app,webview,微信小程序
如果webview部署所在的路径为www.abc.xx/page,那么设置域名也应该为www.abc.xx/page

业务域名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转换为Unicode
        c = (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 = ticket
            storage.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并注册jsApi
    fetToken().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的校验结果的

小程序

uniapp 微信小程序webview 踩坑,else,uni-app,webview,微信小程序
uniapp 微信小程序webview 踩坑,else,uni-app,webview,微信小程序

公众号

uniapp 微信小程序webview 踩坑,else,uni-app,webview,微信小程序
输入网址后跳转

uniapp 微信小程序webview 踩坑,else,uni-app,webview,微信小程序

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


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

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

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


: 这种方法仅支持线上调试,本地会显示localhost不在公众号校验的白名单中

小程序上线的时候会自动开启合法域名校验,此时需要在小程序的开放平台 - 开发管理 - 开发设置中把在公众号中的安全域名加上去(www.abc.xx),然后在体验版中才能访问




其他问题

webview中可以双指放大网页

使用meta标签禁止页面放大

<meta content='width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;' name='viewport' />
<meta name="viewport" content="width=device-width" />

小程序上webview缓存

每次更新webview时由于小程序的缓存会导致我们每次打开无法访问到最新的页面

  1. 可以在webview组件的src里面添加一个时间戳.
src = `https://XXX.com?timestamp=${new Date().getTime()}`
<web-view src='{{src}}'></web-view>
  1. 在index.html的head头部添加不缓存的配置
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
  1. 安卓端如果都不行就直接清除缓存
    在 微信 设定 -> 一般 -> 存储空间 中将缓存清除即可通过小程序访问最新的webview。

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].clientX
			    this.startPosition.Y = event.changedTouches[0].clientY
			},
			/**
			 * 通过当前手指的位置和开始滑动时点击的位置来判断手指滑动的方向
			 */
			chatExpandMove() {
			    this.touchingFlag = true //移动进行
			
			    this.moveingPosition.X = event.changedTouches[0].clientX
			    this.moveingPosition.Y = event.changedTouches[0].clientY
			
			    let 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 = true
			        let 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 = true
			        let direction = this.moveingPosition.Y - this.startPosition.Y > 0 ? "down" : "up"
			        
			        if (direction === "up") {
			            // 上
			        } else if (direction === 'down') {
			            // 下
			        }
			    }
			},
			/**
			 * 滑动结束时判断手指滑动的距离 如果时间过短或距离过短 则取消影响
			 */
			chatMoveEnd() {
			    //关闭手指移动的标识
			    this.touchingFlag = false
			    const endTime = Date.now()
			    if (endTime - this.startTime < 300) {
			        // 如果手指滑动的距离超过0.3s 就默认不合法
			        return;
			    }
			    //获取滑动结束后的坐标
			    this.endPosition.X = event.changedTouches[0].clientX
			    this.endPosition.Y = event.changedTouches[0].clientY
			    if (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 = false
			    this.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中的wx
     if (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上的开发体验非常糟糕,社区解决问题的效率堪忧,网上的资料也非常零散,浪费了非常多的时间。文章来源地址https://www.toymoban.com/news/detail-687363.html

到了这里,关于uniapp 微信小程序webview 踩坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • WebStorm开发uni-app ,用vue2实现手机APP(apk) + 微信小程序多端项目开发方案 webstorm开发的uniapp + hbuilderx进行app 小程序打包

    我们主要分析了如下小程序开发框架,主要包括: 框架 技术栈 案例 微信小程序 支付宝小程序 百度小程序 头条小程序 H5 App uni-app Vue 丰富 ⭕ ⭕️ ⭕️ ⭕ ⭕️ ⭕ Taro React 丰富 ⭕ ⭕ ⭕ ⭕ ⭕ ⭕ wepy Vue 丰富 ⭕ ❌ ❌ ❌ ❌ ❌ mpvue Vue 丰富 ⭕ ❌ ❌ ❌ ⭕️ ❌  首先,就要排

    2024年02月11日
    浏览(75)
  • 微信小程序uni-app

    小程序 是一种不需要下载、安装即可使用的应用,它实现了应用触手可及的梦想,用户扫一扫或者搜一下就能打开应用,也实现了用完即走的理念,用户不用安装太多应用,应用随处可用,但又无须安装卸载。 微信开发文档 1、工作原理 网页开发,渲染线程和脚本是互斥的

    2024年02月10日
    浏览(123)
  • 微信小程序授权(uni-app)

    概述 为了避免重复开发,自己封装了一个通用用户授权回调方法,只需要传入需要授权的scope,权限中文描述、回调函数,就可以实现一整套小程序是否授权、打开授权设置,调用后续操作函数的工作 功能 可以根据自己的实际应用进行微调 目前使用的uni-app版本,可以根据自

    2024年02月16日
    浏览(86)
  • uniapp webview H5跳转微信小程序

    第一步:manifest.json 第二步:index.html 第三步 H5跳转微信小程序代码

    2024年02月10日
    浏览(61)
  • uni-app(微信小程序)获取当前位置uni.getLocation

     1、微信公众平台  开发  开发管理   2、开通之后到项目文件    3、下载腾讯地图插件并引入到文件中    

    2024年02月11日
    浏览(67)
  • uni-app微信小程序使用echarts

    前言:本来是使用的ucharts,但因为无法监听图例点击交互,满足不了需求,所以只能放弃。 首先,下载echart组件。可以先随便建个文件夹,然后 npm init。接着下载依赖 然后找到 node_modulesmpvue-echarts下的文件,如图 只留下src,其他的删掉(没有用到)。然后复制 mpvue-echart

    2024年02月10日
    浏览(74)
  • uni-app 微信小程序 激励视频广告

    封装激励视频-Ad.js 调用上面写的方法:

    2024年02月12日
    浏览(83)
  • 【uni-app微信小程序】实现支付功能

    实现微信支付功能需要在小程序后台配置支付相关信息,并且在前端代码中调用微信支付API进行支付操作。好的, uni-app微信小程序实现支付功能整体流程 大致如下: 注册微信公众平台,并完成开发者资质认证; 在微信商户平台注册商户账号,并完成商户资质认证; 在商户

    2024年02月13日
    浏览(99)
  • 语法速通 uni-app随笔【uni-app】【微信小程序】【vue】

    其中, pages 目录/ index 目录【必有】: index.js 编写业务逻辑 【初始数据,生命周期函数】 index.json 编写配置 index.wxml 编写模板 【可理解为本页html】 index.wxss 【可理解为本页css】 直接输入敲回车,连尖括号都不需要就可以标签补全 1)初始数据写死 在 index.wxml 引入变

    2024年02月12日
    浏览(174)
  • 前端Uni-app开发微信小程序|微信小程序手机商城

    作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、Python项目、前端项目、人工智能与大数据、简历模板、学习资料、面试题库

    2024年02月12日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包