在 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 实例
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: {
// 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 拼接 Token
addQueryParam(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)}` });
},
// 上传图片
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);
}
},
// App 主动向 H5 传值
submit() {
const data = { name: 'wft', timestamp: Date.now() };
wv.evalJS(`window.msgFromUniApp(${JSON.stringify(data)})`);
}
}
};
</script>


