From 405e460ad65cf4e0c409482a9959a012f460f804 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 16 Jun 2026 14:20:12 +0800 Subject: [PATCH] fix: stabilize iOS Safari mobile navigation --- src/@layouts/components/VerticalNavLayout.vue | 13 ++++++++++--- src/@layouts/styles/_default-layout.scss | 2 +- src/composables/usePWA.ts | 12 ++++++++---- src/composables/usePullDownGesture.ts | 5 ++++- src/layouts/components/DefaultLayout.vue | 13 +++++++++---- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/@layouts/components/VerticalNavLayout.vue b/src/@layouts/components/VerticalNavLayout.vue index 0d412ac6..2b758b62 100644 --- a/src/@layouts/components/VerticalNavLayout.vue +++ b/src/@layouts/components/VerticalNavLayout.vue @@ -170,6 +170,10 @@ export default defineComponent({ } .layout-wrapper.layout-nav-type-vertical { + --layout-navbar-block-size: calc( + env(safe-area-inset-top, 0px) + #{variables.$layout-vertical-nav-navbar-height} + var(--navbar-tab-height) + ); + // TODO(v2): Check why we need height in vertical nav & min-height in horizontal nav min-block-size: 100%; @@ -185,13 +189,16 @@ export default defineComponent({ .layout-navbar { position: fixed; z-index: variables.$layout-vertical-nav-layout-navbar-z-index; + // iOS Safari 在地址栏收起和惯性滚动时可能把 fixed 顶栏和页面滚动层合成到一起, + // 单独提升顶栏图层可避免导航栏短暂上移到安全区下方。 + backface-visibility: hidden; + block-size: var(--layout-navbar-block-size); inline-size: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem); inset-block-start: 0; + transform: translate3d(0, 0, 0); .navbar-content-container { - block-size: calc( - env(safe-area-inset-top) + variables.$layout-vertical-nav-navbar-height + var(--navbar-tab-height) - ); + block-size: var(--layout-navbar-block-size); } @at-root { diff --git a/src/@layouts/styles/_default-layout.scss b/src/@layouts/styles/_default-layout.scss index b7dd3220..15d5fe16 100644 --- a/src/@layouts/styles/_default-layout.scss +++ b/src/@layouts/styles/_default-layout.scss @@ -15,7 +15,7 @@ body { background: rgb(var(--v-theme-background)); overscroll-behavior-y: contain; - --webkit-overflow-scrolling: touch; + -webkit-overflow-scrolling: touch; } body, diff --git a/src/composables/usePWA.ts b/src/composables/usePWA.ts index 7007cd7e..3d352fc4 100644 --- a/src/composables/usePWA.ts +++ b/src/composables/usePWA.ts @@ -1,6 +1,6 @@ import { ref, computed, onMounted } from 'vue' import { useDisplay } from 'vuetify' -import { checkPWAStatus, isPWADisplayMode } from '@/@core/utils/navigator' +import { checkPWAStatus, isMobileDevice, isPWADisplayMode } from '@/@core/utils/navigator' // 全局PWA状态,确保只初始化一次 const globalPwaStatus = ref<{ @@ -34,11 +34,14 @@ async function initializePWAGlobally() { globalPwaStatus.value = await checkPWAStatus() } catch (error) { console.error('Failed to detect PWA status', error) + const isStandaloneMode = isPWADisplayMode() + // 即使检测失败,也设置一个合理的默认值 globalPwaStatus.value = { hasPWAFeatures: false, - isStandaloneMode: isPWADisplayMode(), - isPWAEnvironment: isPWADisplayMode(), + isStandaloneMode, + // iOS Safari 浏览器模式可能取不到 Service Worker 注册信息,但移动端仍应使用 App 交互。 + isPWAEnvironment: isStandaloneMode || isMobileDevice(), isFullPWA: false, } } finally { @@ -56,7 +59,8 @@ export function usePWA() { // 基于新的PWA状态结构 const pwaMode = computed(() => { - return globalPwaStatus.value?.isPWAEnvironment ?? false + // PWA 状态异步恢复前先用移动端特征兜底,避免 Safari 浏览器首屏阶段缺少移动端交互。 + return globalPwaStatus.value?.isPWAEnvironment ?? isMobileDevice() }) const appMode = computed(() => { diff --git a/src/composables/usePullDownGesture.ts b/src/composables/usePullDownGesture.ts index 439b3947..ed8aae7a 100644 --- a/src/composables/usePullDownGesture.ts +++ b/src/composables/usePullDownGesture.ts @@ -85,7 +85,10 @@ export function usePullDownGesture(options: PullDownOptions = {}) { }) const indicatorTransform = computed(() => { - return `translate(-50%, ${Math.min(60 + pullDistance.value - config.SHOW_INDICATOR, 70)}px)` + // 顶部基准位置由布局 CSS 负责,这里只让指示器跟随下拉手势轻微移动。 + const followOffset = Math.min(Math.max(pullDistance.value - config.SHOW_INDICATOR, 0), 16) + + return `translate3d(-50%, ${followOffset}px, 0)` }) // 弹窗检测函数 diff --git a/src/layouts/components/DefaultLayout.vue b/src/layouts/components/DefaultLayout.vue index 4d794411..61a30cff 100644 --- a/src/layouts/components/DefaultLayout.vue +++ b/src/layouts/components/DefaultLayout.vue @@ -452,6 +452,7 @@ onMounted(async () => { v-if="appMode && showPullIndicator" class="pull-indicator" :style="{ + '--pull-indicator-navbar-extra-height': navbarExtraHeight, opacity: indicatorOpacity, transform: indicatorTransform, }" @@ -475,7 +476,7 @@ onMounted(async () => {