在 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'
// 上传功能混入
let 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>


