UniApp WebView 与 H5 双向通信实战指南
在 UniApp 开发里,WebView 是连接原生应用和 H5 页面的桥梁。特别是需要复用现有 Web 页面或集成复杂交互时,这块能力必不可少。今天结合实际案例,聊聊怎么实现 UniApp(App/H5)和内嵌 H5 的双向通信,顺便封装几个常用的原生调用,比如扫码、打电话、蓝牙打印这些。
核心需求与技术背景
通常我们需要在 UniApp 里嵌入 H5,并搞定这几件事:
- H5 触发原生能力(扫码、拨号、蓝牙、上传)。
- 原生处理完把结果回传给 H5。
- 兼容 App 和 H5 端的逻辑差异。
- 给 H5 透传鉴权 Token 等参数。
整体思路
参数传递上,UniApp 打开 WebView 时直接拼接到 URL 里传 Token。
H5 到 UniApp 这边,用 uni.webView.postMessage 发指令,UniApp 监听 message 事件接收。
UniApp 到 H5 这边,通过 evalJS 执行 H5 的全局函数来回传结果。
至于端兼容,靠条件编译区分 App 和 H5 的处理逻辑。
代码实现详解
UniApp 端:WebView 容器
这里有个坑要注意,App 端获取 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 实例
export default {
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-PLUS
this.initAppWebView();
#endif
#ifdef H5
this.initH5Listener();
#endif
},
methods: {
initAppWebView() {
uni.showLoading({ title: '加载中' });
var currentWebview = this.$scope.$getAppWebview();
setTimeout(() => {
wv = currentWebview.children()[0];
uni.hideLoading();
}, 1000);
},
initH5Listener() {
window.addEventListener('message', (e) => {
console.log('接收 H5 消息', e.data);
}, false);
},
addQueryParam(url, param) {
return url.includes('?') ? `${url}&${param}` : `${url}?${param}`;
},
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)}` });
},
async handleChooseImage(options) {
try {
const imageUrl = await this.uploadImage(options);
wv.evalJS(`window.chooseImageFromUniApp(${JSON.stringify(imageUrl)})`);
} catch (error) {
console.error('图片上传失败', error);
}
},
async handleChooseVideo(options) {
try {
const videoUrl = await this.uploadVideo(options);
wv.evalJS(`window.chooseVideoFromUniApp(${JSON.stringify(videoUrl)})`);
} catch (error) {
console.error('视频上传失败', error);
}
},
submit() {
const data = { name: 'wft', timestamp: Date.now() };
wv.evalJS(`window.msgFromUniApp(${JSON.stringify(data)})`);
}
}
};
</script>


