玩转 UniApp WebView:H5 与原生应用双向通信实战
在 UniApp 开发中,WebView 是连接原生应用与 H5 页面的重要桥梁,尤其在需要复用已有 Web 页面或集成复杂 Web 交互场景时不可或缺。本文将结合实际开发案例,详细讲解如何实现 UniApp(App/H5 端)与内嵌 H5 页面的双向通信,并封装常用的原生能力调用(扫码、拨打电话、蓝牙打印、文件上传等)。
一、核心需求与技术背景
你需要在 UniApp 中通过 WebView 嵌入 H5 页面,并实现:
- H5 页面触发原生能力(扫码、拨打电话、蓝牙打印、图片 / 视频上传);
- UniApp 原生端处理完逻辑后,将结果回传给 H5 页面;
- 兼容 App 端和 H5 端的通信逻辑;
- 给 H5 页面传递鉴权 Token 等参数。
二、整体实现思路
- 参数传递:UniApp 在打开 WebView 时,通过 URL 拼接方式给 H5 页面传递 Token 等参数;
- H5 → UniApp:H5 通过uni.webView.postMessage发送指令,UniApp 监听message事件接收并处理;
- UniApp → H5:UniApp 通过evalJS执行 H5 页面的全局函数,将处理结果回传;
- 端兼容:通过 UniApp 的条件编译区分 App 端和 H5 端的通信逻辑。
三、代码实现详解
3.1 UniApp 端:WebView 容器页面
<template><view style="position: relative;"><web-view :src="reportSrc":webview-styles="webviewStyles" @message="message"></web-view></view></template><script>import{ getToken }from'@/utils/auth'import cameraTool from'../mixins/camera-tool.vue'// 上传功能混入var wv;// WebView实例exportdefault{mixins:[cameraTool],data(){return{webviewStyles:{width:'100%',height:'100%'},reportSrc:''};},onLoad(option){// 接收打开页面时传递的参数const eventChannel =this.getOpenerEventChannel(); eventChannel.on('acceptDataFromOpenerPage',(data)=>{this.reportSrc =this.addQueryParam(data.url,`token=${getToken()}`); uni.setNavigationBarTitle({title: data.title });});// #ifdef APP-PLUSthis.initAppWebView();// #endif// #ifdef H5this.initH5Listener();// #endif},methods:{// App端WebView初始化initAppWebView(){ uni.showLoading({title:'加载中'});var currentWebview =this.$scope.$getAppWebview();setTimeout(()=>{ wv = currentWebview.children()[0]; uni.hideLoading();},1000);},// H5端监听initH5Listener(){ window.addEventListener('message',(e)=>{ console.log('接收H5消息', e.data);},false);},// URL拼接TokenaddQueryParam(url, param){return url.includes('?')?`${url}&${param}`:`${url}?${param}`;},// 接收H5发来的消息(核心)message(e){const data = e.detail.data[0];const{ type, info, options }= data;const actions ={'scanCode':()=>this.handleScan(info),'phoneCall':()=>this.handlePhoneCall(info),'bluetoothPrint':()=>this.handleBluetoothPrint(info),'chooseImage':()=>this.handleChooseImage(options),'chooseVideo':()=>this.handleChooseVideo(options),'logout':()=>this.handleLogout()};if(actions[type]){ actions[type]();}else{ console.warn('未知操作类型:', type);}},// 扫一扫handleScan(){ uni.scanCode({success:(res)=>{ wv.evalJS(`window.scanCodeFromUniApp('${res.result}')`);}});},// 拨打电话handlePhoneCall(phoneNumber){ uni.makePhoneCall({phoneNumber: phoneNumber ||'114'});},// 蓝牙打印(跳转蓝牙页面)handleBluetoothPrint(printData){ uni.navigateTo({url:`/pages/common/blueTooth/index?printData=${JSON.stringify(printData)}`});},// 上传图片asynchandleChooseImage(options){try{const imageUrl =awaitthis.uploadImage(options); wv.evalJS(`window.chooseImageFromUniApp(${JSON.stringify(imageUrl)})`);}catch(error){ console.error('图片上传失败', error);}},// 上传视频asynchandleChooseVideo(options){try{const videoUrl =awaitthis.uploadVideo(options); wv.evalJS(`window.chooseVideoFromUniApp(${JSON.stringify(videoUrl)})`);}catch(error){ console.error('视频上传失败', error);}},// App主动向H5传值submit(){const data ={name:'wft',timestamp: Date.now()}; wv.evalJS(`window.msgFromUniApp(${JSON.stringify(data)})`);}}};</script>3.2 H5 端:页面结构与通信逻辑
3.2.1 HTML 入口文件(index.html)
<!DOCTYPE html><html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/></head><body><div id="app"></div><!-- 关键:引入 uni.webView 桥接文件 --><script src="./uni.webview.js" type="text/javascript"></script><script type="text/javascript">// 等待桥接就绪 document.addEventListener("UniAppJSBridgeReady",function(){ uni.webView.getEnv(function(res){ console.log("当前环境:", res);});});// 声明供 App 调用的全局函数 window.msgFromUniApp=function(val){}; window.scanCodeFromUniApp=function(val){}; window.chooseImageFromUniApp=function(val){}; window.chooseVideoFromUniApp=function(val){};</script></body></html>3.2.2 H5 业务页面(A.vue)
<template><div class="hello"><h3>H5内嵌页面</h3><!-- 扫一扫 --><van-field v-model="scanCode" label="扫一扫" placeholder="扫码结果"><template #button><van-button type="primary" size="small" @click="handleScan"> 扫一扫 </van-button></template></van-field><!-- 拨打电话 --><van-field v-model="phone" label="拨打电话"><template #button><van-button type="primary" size="small" @click="makePhoneCall"> 拨打 </van-button></template></van-field><!-- 蓝牙打印 --><div class="section"><van-button type="primary" size="small" @click="handleBluetoothPrint"> 蓝牙打印 </van-button></div><!-- 上传图片 --><div class="section"><div class="preview"><img v-for="item in fileList":key="item.url":src="item.url"/></div><van-button type="primary" size="small" @click="handleUploadImage"> 上传图片 </van-button></div><!-- 上传视频 --><div class="section"><div class="preview"><video v-for="item in videoList":key="item.url":src="item.url" controls /></div><van-button type="primary" size="small" @click="handleUploadVideo"> 上传视频 </van-button></div></div></template><script>exportdefault{data(){return{scanCode:'',phone:'13800138000',fileList:[],videoList:[]};},mounted(){// 重写全局回调,接收App执行结果 window.scanCodeFromUniApp=(val)=>{this.scanCode = val;}; window.chooseImageFromUniApp=(val)=>{this.fileList =this.fileList.concat(val);}; window.chooseVideoFromUniApp=(val)=>{this.videoList =this.videoList.concat(val);}; window.msgFromUniApp=(val)=>{ console.log('App主动消息:', val);};},methods:{// 统一消息发送入口postMessage(type, payload ={}){if(!window.uni ||!window.uni.webView){ console.error('uni.webView 未就绪');return;} window.uni.webView.postMessage({data:{ type,...payload }});},handleScan(){this.postMessage('scanCode');},makePhoneCall(){this.postMessage('phoneCall',{info:this.phone });},handleBluetoothPrint(){this.postMessage('bluetoothPrint',{info:[11,22]});},handleUploadImage(){this.postMessage('chooseImage',{options:{maxNumber:2,sourceTypeIndex:3,// 1:相机 2:相册 3:两者sizeTypeIndex:2,// 1:压缩 2:原图 3:两者url:'http://your-server/upload',bucketName:'jingsheng'}});},handleUploadVideo(){this.postMessage('chooseVideo',{options:{maxDuration:60,sourceTypeIndex:3,url:'http://your-server/upload',bucketName:'jingsheng'}});}}};</script>四、关键注意事项
- 本地 H5 调试:本地运行的 H5 页面需用Live Server打开,且地址需替换成本机
IP(如192.168.1.4:5500),而非127.0.0.1,否则手机端无法访问; - App 端 webview 实例:必须通过this. s c o p e . scope. scope.getAppWebview()获取,且需延时 1
秒左右确保实例创建完成; - 参数传递格式:通过evalJS传递复杂数据时,需用JSON.stringify序列化,避免语法错误;
- 端兼容处理:通过#ifdef APP-PLUS/#ifdef H5区分不同端的通信逻辑,H5
端需用window.postMessage,App 端用evalJS; - 权限申请:调用扫码、拨打电话、文件上传等功能时,需在manifest.json中配置对应权限。
- App 端使用 uni.web-view.js 的最低版为 uni.webview.1.5.4.js
五、总结
- UniApp 与 H5 的双向通信核心是:H5 通过uni.webView.postMessage发消息,UniApp 通过evalJS执行 H5 全局函数回传结果;
- 需通过条件编译区分 App 端和 H5 端的通信逻辑,App 端依赖 webview 实例,H5 端依赖 window 对象;
- 实际开发中需注意本地调试的 IP 替换、参数序列化、权限配置等细节,确保跨端通信稳定可靠。
通过这套方案,你可以轻松实现 UniApp 原生能力与 H5 页面的深度融合,既复用了 Web 端的开发成果,又能充分利用原生应用的功能优势。