从 UniApp 容器页 、到 H5 index.html 桥接初始化 、再到 H5 业务页收发消息,完整流程一次讲清楚。
1. 先说结论:到底怎么通信?
在 uni-app 的 <web-view> 场景里,建议用这套模型:
- UniApp -> H5 :通过子 webview 的
evalJS主动注入消息 - H5 -> UniApp :通过
uni.postMessage({ data })发消息给宿主页 - H5 端调用 uni 能力前 :等待
UniAppJSBridgeReady
UniAppJSBridgeReady的意思:
uni-app 注入到网页里的 JS Bridge 已经准备好了 。在这个事件之前调用
uni相关 API,容易出现uni 未定义或调用无效。
官方文档参考:
2. 通信协议先统一
先统一消息结构,避免后面越写越乱:
ts
type BridgeMessage = {
type: string
timestamp: number
requestId?: string
token?: string
payload?: Record<string, any>
}
建议至少有:
type:消息类型(必填)timestamp:时间戳(排查时序问题)requestId:链路追踪(可选但推荐)payload:业务数据
3. UniApp 容器页代码
这个页面负责加载 H5、处理双向通信、回传 token。
vue
<template>
<view class="webview-container">
<web-view
v-if="webUrl"
:src="webUrl"
ref="webviewRef"
:update-title="false"
@message="handleMessage"
@error="handleError"
/>
</view>
</template>
<script setup>
import { ref, getCurrentInstance } from 'vue'
import { GRAPH_H5_BASE_URL } from '@/utils/config'
import { onLoad, onReady } from '@dcloudio/uni-app'
const webUrl = ref('')
const token = ref('')
const webviewRef = ref(null)
const childWebview = ref(null)
// 读取 token(可按项目实际改造)
const getTokenFromStorage = () => {
try {
const tokenData = uni.getStorageSync('auth_token')
if (tokenData && typeof tokenData === 'object' && tokenData.access_token) {
return tokenData.access_token
}
if (typeof tokenData === 'string') return tokenData
return ''
} catch (e) {
console.error('获取 token 失败:', e)
return ''
}
}
const setNavigationTitle = (title) => {
if (!title) return
try {
uni.setNavigationBarTitle({ title })
} catch (e) {
console.error('设置导航栏标题失败:', e)
}
}
// UniApp -> H5
const sendMessageToH5 = (payload = {}) => {
// #ifdef APP-PLUS
const target = childWebview.value
if (!target) {
console.warn('子 webview 未就绪,消息发送失败:', payload)
return
}
const safePayload = JSON.stringify(payload)
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
const jsCode = `
(function () {
var payload = JSON.parse('${safePayload}');
if (typeof window.receiveData === 'function') {
window.receiveData(payload);
}
window.dispatchEvent(new CustomEvent('uniapp-message', { detail: payload }));
})();
`
target.evalJS(jsCode)
// #endif
}
// 页面参数解析:url + title
onLoad((options) => {
if (!options?.url) return
let h5Url = ''
try {
h5Url = decodeURIComponent(options.url)
} catch (e) {
h5Url = options.url
}
if (!h5Url.startsWith('http://') && !h5Url.startsWith('https://')) {
h5Url = `${GRAPH_H5_BASE_URL}${h5Url.startsWith('/') ? '' : '/'}${h5Url}`
}
if (options?.title) {
let title = ''
try {
title = decodeURIComponent(options.title)
} catch (e) {
title = options.title
}
setNavigationTitle(title)
}
webUrl.value = h5Url
})
// 获取子 webview,初始化下发 token
onReady(() => {
if (!token.value) token.value = getTokenFromStorage()
// #ifdef APP-PLUS
const instance = getCurrentInstance()
const currentWebview = instance?.proxy?.$scope?.$getAppWebview?.()
if (!currentWebview) return
let checkCount = 0
const maxChecks = 30
const checkInterval = 100
const waitWebviewReady = () => {
checkCount++
if (checkCount > maxChecks) {
console.warn('子 webview 等待超时')
return
}
try {
const children = currentWebview.children()
if (!children || !children.length || !children[0]) {
setTimeout(waitWebviewReady, checkInterval)
return
}
childWebview.value = children[0]
sendMessageToH5({
type: 'UNIAPP_INIT',
token: token.value || '',
timestamp: Date.now()
})
} catch (error) {
console.error('等待子 webview 失败:', error)
setTimeout(waitWebviewReady, checkInterval)
}
}
waitWebviewReady()
// #endif
})
// H5 -> UniApp
const handleMessage = (e) => {
const rawData = e.detail?.data
const message = Array.isArray(rawData) ? rawData[0] : rawData
if (!message || typeof message !== 'object') return
if (message.type === 'GET_TOKEN') {
sendMessageToH5({
type: 'TOKEN_RESULT',
token: token.value || '',
timestamp: Date.now()
})
}
}
const handleError = (e) => {
console.error('WebView 加载错误:', e)
uni.showToast({ title: '页面加载失败', icon: 'none' })
}
</script>
<style scoped>
.webview-container {
width: 100%;
min-height: 100svh;
}
</style>
4. H5 的 index.html
先加载 uni.webview.1.5.8.js,再等 UniAppJSBridgeReady。
建议下载后放到自己项目的静态资源目录 防止官方的网络波动
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0,user-scalable=no,maximum-scale=1.0, minimum-scale=1.0"
/>
<meta name="referrer" content="never" />
<meta http-equiv="Content-Security-Policy" content="img-src 'self' http: https: data:;" />
<title></title>
</head>
<body>
<div id="app"></div>
<!-- uni SDK:建议下载后部署到自己静态资源目录 -->
<script type="text/javascript" src="/uniapp_webview/uni.webview.1.5.8.js"></script>
<script>
document.addEventListener('UniAppJSBridgeReady', function () {
console.log('UniAppJSBridgeReady')
// 环境探测(可用于埋点或兼容逻辑)
if (window.uni?.webView?.getEnv) {
window.uni.webView.getEnv(function (res) {
console.log('当前环境:' + JSON.stringify(res))
})
}
})
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
5. H5 业务侧通信代码
ts
// 1) 接收 UniApp 主动消息
window.receiveData = function (payload: any) {
if (!payload || typeof payload !== 'object') return
switch (payload.type) {
case 'UNIAPP_INIT':
localStorage.setItem('app_token', payload.token || '')
break
case 'TOKEN_RESULT':
localStorage.setItem('app_token', payload.token || '')
break
default:
break
}
}
// 2) 监听自定义事件(与 receiveData 可并存)
window.addEventListener('uniapp-message', (e: any) => {
const payload = e.detail || {}
console.log('uniapp-message:', payload)
})
// 3) 主动请求 token(H5 -> UniApp)
export function requestTokenFromUniApp() {
if (window.uni && typeof window.uni.postMessage === 'function') {
window.uni.postMessage({
data: {
type: 'GET_TOKEN',
timestamp: Date.now()
}
})
} else {
console.warn('uni.postMessage 不可用,当前可能不是 web-view 场景')
}
}
6. 完整流程
- UniApp 打开
<web-view>加载 H5 地址 - H5 加载
uni.webview.1.5.8.js - 触发
UniAppJSBridgeReady,桥接就绪 - UniApp
onReady获取子 webview,发送UNIAPP_INIT(含 token) - H5 收到后缓存 token
- 业务中若 token 过期或缺失,H5 发
GET_TOKEN - UniApp 收到后回
TOKEN_RESULT - H5 更新 token,继续请求接口