<template>
|
<view class="weizan-live-container">
|
<!-- 子 webview 由 plus.webview.create 命令式创建并浮在该 view 之上,无需在 template 中渲染 -->
|
</view>
|
</template>
|
|
<script setup>
|
import { ref } from 'vue'
|
import { onLoad, onUnload, onShow, onHide } from '@dcloudio/uni-app'
|
import { clearWebViewData } from '@/utils/webview-cache.js'
|
import { registerWeizanPayBridge, unregisterWeizanPayBridge } from '@/utils/weizan-pay-bridge.js'
|
|
const WEIZAN_WEBVIEW_ID = 'weizanLiveWebview'
|
// 微赞识别易宝支付通道用的 UA 后缀(接入方案文档约定)。
|
// 必须完整携带以下标识,微赞 H5 才会把当前环境判定为 isVzanApp / 成功注册 AppChannel,
|
// 进而回调 window.android.jumpToMiniProgramPay:
|
// webchatPay/wx... → 易宝支付通道
|
// InAppPay Vzan/miniAppPayment → 微赞 APP 内小程序支付
|
// vzanApp / zhiboApp → 微赞 APP / 直播 APP 标识
|
const WEIZAN_UA_SUFFIX = 'webchatPay/wx074c1f0cf5a0ecf4 InAppPay Vzan/miniAppPayment vzanApp navigator.userAgent zhiboApp'
|
|
let subWebview = null
|
let currentUrl = ''
|
let currentTitle = ''
|
const loadError = ref('')
|
|
/**
|
* 解码 query 参数。
|
*/
|
function decodeQueryValue(value) {
|
if (!value) {
|
return ''
|
}
|
try {
|
return decodeURIComponent(value)
|
} catch (e) {
|
return value || ''
|
}
|
}
|
|
/**
|
* 页面加载:读取 URL/Title,进入前清缓存,创建子 webview 并注册支付桥。
|
*/
|
onLoad((options) => {
|
currentUrl = decodeQueryValue(options?.url)
|
currentTitle = decodeQueryValue(options?.title)
|
console.log('[weizanLive] onLoad, url=', currentUrl, 'title=', currentTitle)
|
|
if (currentTitle) {
|
uni.setNavigationBarTitle({ title: currentTitle })
|
}
|
|
// #ifdef APP-PLUS
|
// 进入前先清理上次缓存、cookie、登录态
|
clearWebViewData()
|
// 创建子 webview(注入 UA + 注册支付桥 + 加载 URL)
|
createWeizanWebview(currentUrl)
|
// #endif
|
})
|
|
/**
|
* 页面卸载:反注册支付桥并关闭子 webview,避免残留。
|
*/
|
onUnload(() => {
|
console.log('[weizanLive] onUnload')
|
// #ifdef APP-PLUS
|
if (subWebview) {
|
try {
|
unregisterWeizanPayBridge(subWebview)
|
} catch (e) {
|
console.warn('[weizanLive] unregisterWeizanPayBridge fail:', e)
|
}
|
try {
|
subWebview.close()
|
} catch (e) {
|
console.warn('[weizanLive] subWebview.close fail:', e)
|
}
|
subWebview = null
|
}
|
// #endif
|
})
|
|
onShow(() => {
|
// #ifdef APP-PLUS
|
if (subWebview) {
|
try {
|
subWebview.show()
|
} catch (e) {}
|
}
|
// #endif
|
})
|
|
onHide(() => {
|
// #ifdef APP-PLUS
|
if (subWebview) {
|
try {
|
subWebview.hide()
|
} catch (e) {}
|
}
|
// #endif
|
})
|
|
/**
|
* 创建子 webview:先创建空白实例 → 注入 UA → 注册支付桥 → 加载真实 URL → show。
|
* 这个顺序保证 H5 加载完成时 UA 已带上微赞标识、window.android.jumpToMiniProgramPay 已可用。
|
*/
|
function createWeizanWebview(targetUrl) {
|
// #ifdef APP-PLUS
|
const platform = String(uni.getSystemInfoSync().platform || '').toLowerCase()
|
console.log('[weizanLive] createWeizanWebview, platform=', platform, 'url=', targetUrl)
|
|
try {
|
// 1. 创建空白 webview(不立即加载 URL)
|
subWebview = plus.webview.create('', WEIZAN_WEBVIEW_ID, {
|
top: '0px',
|
bottom: '0px',
|
background: '#FFFFFF',
|
// 让子 webview 的 progress 进度条颜色与 APP 一致
|
progress: {
|
color: '#FF3333'
|
}
|
})
|
console.log('[weizanLive] plus.webview.create 完成, subWebview=', subWebview ? '[Object]' : 'null')
|
|
// 2. 注入 UA(让微赞 H5 识别为易宝支付通道)
|
injectUserAgent(subWebview, platform)
|
|
// 3. 注册支付 JSBridge(在 loadURL 之前注册,保证 H5 加载完成时钩子已可用)
|
registerWeizanPayBridge(subWebview)
|
|
// 4. 加载真实 URL
|
if (targetUrl) {
|
subWebview.loadURL(targetUrl)
|
console.log('[weizanLive] loadURL 调用完成:', targetUrl)
|
} else {
|
loadError.value = '直播间地址为空'
|
}
|
|
// 5. 显示子 webview
|
subWebview.show()
|
console.log('[weizanLive] subWebview.show() 完成')
|
} catch (e) {
|
console.error('[weizanLive] createWeizanWebview 失败:', e)
|
loadError.value = '直播间加载失败'
|
}
|
// #endif
|
}
|
|
/**
|
* 给子 webview 注入微赞支付 UA 后缀。
|
* - Android:取底层原生 WebView 后反射 getSettings().setUserAgentString
|
* - iOS:反射 WKWebView setValue:forKey:@"customUserAgent"
|
*/
|
function injectUserAgent(webview, platform) {
|
// #ifdef APP-PLUS
|
if (platform === 'android') {
|
try {
|
// plus.webview.create 返回的是 JS 包装对象,不能直接反射,
|
// 必须先取底层原生 WebView(android.webkit.WebView)再取 settings。
|
const nativeWebView = getAndroidNativeWebView(webview)
|
const settings = safeAndroidInvoke(nativeWebView, 'getSettings')
|
if (settings) {
|
const originalUA = safeAndroidInvoke(settings, 'getUserAgentString')
|
const customUA = `${originalUA} ${WEIZAN_UA_SUFFIX}`
|
safeAndroidInvoke(settings, 'setUserAgentString', customUA)
|
console.log('[weizanLive] Android UA 注入完成:', customUA)
|
} else {
|
console.warn('[weizanLive] Android 未取到原生 WebView 或 getSettings 返回空,UA 未注入')
|
}
|
} catch (e) {
|
console.error('[weizanLive] Android UA 注入失败:', e)
|
}
|
} else if (platform === 'ios') {
|
try {
|
// plus.webview 实例 → valueForKey:@"webView" 拿底层 WKWebView
|
const wkWebView = safeIosInvoke(webview, 'valueForKey:', 'webView') || webview
|
// 读取原 UA
|
const originalUA = safeIosInvoke(wkWebView, 'valueForKey:', 'userAgent') || ''
|
const customUA = `${originalUA} ${WEIZAN_UA_SUFFIX}`
|
// WKWebView.customUserAgent 是可写属性,反射设置
|
safeIosInvoke(wkWebView, 'setValue:forKey:', customUA, 'customUserAgent')
|
console.log('[weizanLive] iOS UA 注入完成:', customUA)
|
} catch (e) {
|
console.error('[weizanLive] iOS UA 注入失败:', e)
|
}
|
}
|
// #endif
|
}
|
|
/**
|
* 取 plus.webview 实例底层的原生 android.webkit.WebView。
|
* 优先用官方 nativeInstanceObject(),失败再退回 obtainWebview() 等兼容方式。
|
*/
|
function getAndroidNativeWebView(webview) {
|
if (!webview) {
|
return null
|
}
|
// 1. 官方 API:WebviewObject.nativeInstanceObject() 返回原生 WebView
|
if (typeof webview.nativeInstanceObject === 'function') {
|
try {
|
const nativeObj = webview.nativeInstanceObject()
|
if (nativeObj) {
|
return nativeObj
|
}
|
} catch (e) {
|
console.warn('[weizanLive] nativeInstanceObject 调用失败:', e && e.message)
|
}
|
}
|
// 2. 兼容:部分运行时通过 obtainWebview 取原生 WebView
|
if (typeof webview.obtainWebview === 'function') {
|
try {
|
const obtained = webview.obtainWebview()
|
if (obtained) {
|
return obtained
|
}
|
} catch (e) {}
|
}
|
return null
|
}
|
|
/**
|
* 安全调用 plus.android.invoke,失败返回 null。
|
*/
|
function safeAndroidInvoke(obj, methodName, ...args) {
|
try {
|
return plus.android.invoke(obj, methodName, ...args)
|
} catch (e) {
|
console.warn('[weizanLive] android invoke fail:', methodName, e && e.message)
|
return null
|
}
|
}
|
|
/**
|
* 安全调用 plus.ios.invoke,失败返回 null。
|
*/
|
function safeIosInvoke(obj, selector, ...args) {
|
try {
|
return plus.ios.invoke(obj, selector, ...args)
|
} catch (e) {
|
console.warn('[weizanLive] ios invoke fail:', selector, e && e.message)
|
return null
|
}
|
}
|
</script>
|
|
<style>
|
.weizan-live-container {
|
width: 100%;
|
height: 100vh;
|
background-color: #FFFFFF;
|
}
|
</style>
|