Sfoglia il codice sorgente

修改重新加载浏览器

liuq 2 mesi fa
parent
commit
60cb71255e
1 ha cambiato i file con 124 aggiunte e 32 eliminazioni
  1. 124 32
      pages/webview/index.vue

+ 124 - 32
pages/webview/index.vue

@@ -2,7 +2,7 @@
 	<view class="webview-page">
 		<!-- #ifdef APP-PLUS -->
 		<!-- App:web-view 为原生层会挡住普通 view 的点击,须用 cover-view 盖在上面 -->
-		<web-view class="webview" :src="decodedUrl" @load="onEmbeddedWebviewLoad" />
+		<web-view class="webview" :key="webviewKey" :src="webviewSrc" @load="onEmbeddedWebviewLoad" />
 		<cover-view class="header header--overlay" :style="headerStyle">
 			<cover-view class="header-inner">
 				<cover-view class="capsule">
@@ -51,16 +51,29 @@
 				</view>
 			</view>
 		</view>
-		<web-view class="webview" :src="decodedUrl" />
+		<web-view class="webview" :key="webviewKey" :src="webviewSrc" />
 		<!-- #endif -->
 	</view>
 </template>
 
 <script setup>
-import { computed, ref } from 'vue'
+import { computed, nextTick, ref } from 'vue'
 import { onLoad, onReady, onShow } from '@dcloudio/uni-app'
 
 const decodedUrl = ref('')
+/** 传给 web-view 的地址;重新加载时会同步为原生子 webview 当前 getURL(含重定向后) */
+const webviewSrc = ref('')
+/** App 端每次 load 后刷新,供 getURL 暂不可用时回退 */
+const cachedCurrentUrl = ref('')
+/** 变更 key 以强制重建 web-view,实现整页重新加载 */
+const webviewKey = ref(0)
+
+function isUsableWebviewUrl(u) {
+	const s = String(u || '').trim()
+	if (!s) return false
+	if (/^about:blank$/i.test(s)) return false
+	return true
+}
 
 /** 用系统信息算顶栏 padding-top(部分端上 CSS env 不可靠) */
 function computeHeaderPaddingTopPx() {
@@ -86,7 +99,10 @@ const headerStyle = computed(() => ({
 onLoad((options) => {
 	headerPaddingTopPx.value = computeHeaderPaddingTopPx()
 	const rawUrl = options && options.url ? options.url : ''
-	decodedUrl.value = rawUrl ? decodeURIComponent(rawUrl) : ''
+	const initial = rawUrl ? decodeURIComponent(rawUrl) : ''
+	decodedUrl.value = initial
+	webviewSrc.value = initial
+	cachedCurrentUrl.value = initial
 })
 
 /**
@@ -95,16 +111,46 @@ onLoad((options) => {
  * 不可假定 children()[0] 为内嵌页,须遍历带 getURL 的子窗体。
  */
 // #ifdef APP-PLUS
-function findEmbeddedWebviewNative(currentWebview) {
+function urlsRoughlyMatch(a, b) {
+	const strip = (s) =>
+		String(s || '')
+			.trim()
+			.split('#')[0]
+			.replace(/\/$/, '')
+	const x = strip(a)
+	const y = strip(b)
+	if (!x || !y) return false
+	if (x === y) return true
+	try {
+		return new URL(x).origin === new URL(y).origin
+	} catch (e) {
+		return x.includes(y) || y.includes(x)
+	}
+}
+
+function findEmbeddedWebviewNative(currentWebview, preferredUrlHint) {
 	try {
 		const list = currentWebview.children && currentWebview.children()
 		if (!list || !list.length) return null
+		const candidates = []
 		for (let i = 0; i < list.length; i++) {
 			const w = list[i]
 			if (w && typeof w.setStyle === 'function' && typeof w.getURL === 'function') {
-				return w
+				candidates.push(w)
 			}
 		}
+		const hint = String(preferredUrlHint || '').trim()
+		if (candidates.length === 1) return candidates[0]
+		if (hint && candidates.length > 1) {
+			for (let i = candidates.length - 1; i >= 0; i--) {
+				try {
+					const u = candidates[i].getURL()
+					if (u && urlsRoughlyMatch(u, hint)) return candidates[i]
+				} catch (e) {}
+			}
+			return candidates[candidates.length - 1]
+		}
+		if (candidates.length) return candidates[candidates.length - 1]
 		for (let i = 0; i < list.length; i++) {
 			const w = list[i]
 			if (w && typeof w.setStyle === 'function') return w
@@ -114,14 +160,39 @@ function findEmbeddedWebviewNative(currentWebview) {
 }
 // #endif
 
-function positionEmbeddedWebview() {
+/** App 端返回子 webview 当前地址;其它端恒为空(无原生 getURL) */
+function getEmbeddedWebviewUrl() {
+	// #ifdef APP-PLUS
+	try {
+		const pages = getCurrentPages()
+		const page = pages[pages.length - 1]
+		if (!page || typeof page.$getAppWebview !== 'function') return ''
+		const wv = findEmbeddedWebviewNative(page.$getAppWebview(), webviewSrc.value)
+		if (wv && typeof wv.getURL === 'function') {
+			const u = wv.getURL()
+			return u && String(u).trim() ? String(u).trim() : ''
+		}
+	} catch (e) {}
+	// #endif
+	return ''
+}
+
+function syncCachedUrlFromEmbeddedNative() {
+	// #ifdef APP-PLUS
+	const u = getEmbeddedWebviewUrl()
+	if (isUsableWebviewUrl(u)) cachedCurrentUrl.value = u
+	// #endif
+}
+
+function positionEmbeddedWebview(extraDelays) {
 	// #ifdef APP-PLUS
 	const pages = getCurrentPages()
 	const page = pages[pages.length - 1]
 	if (!page || typeof page.$getAppWebview !== 'function') return
 	const currentWebview = page.$getAppWebview()
+	const hint = webviewSrc.value
 	const apply = () => {
-		const wv = findEmbeddedWebviewNative(currentWebview)
+		const wv = findEmbeddedWebviewNative(currentWebview, hint)
 		if (!wv) return false
 		uni.createSelectorQuery()
 			.select('.header')
@@ -140,7 +211,11 @@ function positionEmbeddedWebview() {
 			.exec()
 		return true
 	}
-	const delays = [0, 50, 100, 200, 400, 800, 1200, 2000]
+	const baseDelays = [0, 50, 100, 200, 400, 800, 1200, 2000]
+	const delays =
+		Array.isArray(extraDelays) && extraDelays.length
+			? [...new Set([...baseDelays, ...extraDelays])].sort((a, b) => a - b)
+			: baseDelays
 	delays.forEach((ms) => {
 		setTimeout(() => {
 			if (!apply()) {
@@ -151,54 +226,71 @@ function positionEmbeddedWebview() {
 	// #endif
 }
 
-/** 内嵌页 load 完成后再缩窗,避免子 webview 尚未就绪时仍全屏挡在胶囊下 */
+/** 内嵌页 load 完成后再缩窗;并同步子 webview 真实 URL(302 / 前端路由后 getURL 才是当前页) */
 function onEmbeddedWebviewLoad() {
+	// #ifdef APP-PLUS
+	syncCachedUrlFromEmbeddedNative()
+	;[200, 600, 1500].forEach((ms) => setTimeout(syncCachedUrlFromEmbeddedNative, ms))
+	// #endif
 	positionEmbeddedWebview()
 }
 
+/** 子 webview 因 key 重建后需多轮缩窗,否则易保持全屏被顶栏遮挡 */
+function schedulePositionAfterWebviewRemount() {
+	// #ifdef APP-PLUS
+	const extra = [
+		10, 30, 80, 150, 300, 500, 900, 1500, 2500, 3500, 5000
+	]
+	nextTick(() => {
+		positionEmbeddedWebview(extra)
+		setTimeout(() => positionEmbeddedWebview(extra), 0)
+	})
+	// #endif
+}
+
 onReady(() => {
 	positionEmbeddedWebview()
 })
 
 onShow(() => {
+	// #ifdef APP-PLUS
+	syncCachedUrlFromEmbeddedNative()
+	// #endif
 	// 从后台返回时子 webview 可能重排,再对齐一次
 	positionEmbeddedWebview()
 })
 
-/** 更多:底部菜单(当前含「用系统浏览器打开」);关闭:清栈并回到应用中心 Tab */
+/** 更多:重新加载当前内嵌页;关闭:回到应用中心 Tab */
 function onCapsuleMoreMenu() {
 	const url = decodedUrl.value
-	console.log('[webview/capsule] more', { url })
+	console.log('[webview/capsule] reload menu', { url })
 	uni.showActionSheet({
-		itemList: ['用系统浏览器打开'],
+		itemList: ['重新加载'],
 		success: (res) => {
 			if (res.tapIndex !== 0) return
-			openUrlInSystemBrowser(url)
+			reloadEmbeddedPage()
 		}
 	})
 }
 
-function openUrlInSystemBrowser(url) {
-	const u = String(url || '').trim()
-	if (!u) {
-		uni.showToast({ title: '链接无效', icon: 'none' })
-		return
-	}
+function reloadEmbeddedPage() {
 	// #ifdef APP-PLUS
-	try {
-		plus.runtime.openURL(u)
-	} catch (e) {
-		uni.showToast({ title: '无法打开', icon: 'none' })
-	}
+	syncCachedUrlFromEmbeddedNative()
 	// #endif
-	// #ifdef H5
-	if (typeof window !== 'undefined' && window.open) {
-		window.open(u, '_blank')
+	const target =
+		getEmbeddedWebviewUrl() ||
+		cachedCurrentUrl.value ||
+		webviewSrc.value ||
+		decodedUrl.value
+	if (!isUsableWebviewUrl(target)) {
+		uni.showToast({ title: '链接无效', icon: 'none' })
+		return
 	}
-	// #endif
-	// #ifndef APP-PLUS || H5
-	uni.showToast({ title: '当前环境不支持', icon: 'none' })
-	// #endif
+	const url = String(target).trim()
+	webviewSrc.value = url
+	cachedCurrentUrl.value = url
+	webviewKey.value += 1
+	schedulePositionAfterWebviewRemount()
 }
 
 function onCapsuleClose() {