From 11c11994499e19c15447d9a71029e8056a3bed44 Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 14 Apr 2026 15:47:03 +0800 Subject: [PATCH] fix iOS back transition --- src/components/PageTransition.vue | 14 ++++++++++++++ src/layout/MainLayout.vue | 21 ++++++++------------- src/pages/setting/ConnectSetting.vue | 1 + src/router/index.ts | 23 +++++++++++++++++++++++ src/utils/backNavigationSignal.ts | 28 ++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 src/utils/backNavigationSignal.ts diff --git a/src/components/PageTransition.vue b/src/components/PageTransition.vue index 29d1273..ef9ba5f 100644 --- a/src/components/PageTransition.vue +++ b/src/components/PageTransition.vue @@ -14,6 +14,7 @@ import type { RouteLocationNormalized } from "vue-router"; import { viewDepthKey } from "vue-router"; import { useGlobalUIStore } from "@/store/globalUIStore"; +import { hasProgrammaticBackNavigation } from "@/utils/backNavigationSignal"; defineOptions({ name: "PageTransition" @@ -26,6 +27,8 @@ const viewDepth = inject(viewDepthKey, 0); const transitionName = ref(""); const hasTransition = ref(false); +// iOS 左滑返回由系统接管,页面侧不应叠加离场动画。 +const isIOS = /iP(ad|hone|od)/i.test(window.navigator.userAgent); const pageBackground = computed(() => globalUIStore.bodyBackground); const currentDepth = computed(() => Number(unref(viewDepth))); const pageKey = computed(() => { @@ -72,6 +75,17 @@ const unregisterGuard = router.beforeEach((to, from) => { const toDepth = calcDepth(to); const fromDepth = calcDepth(from); + const isBackwardNavigation = toDepth < fromDepth; + // 主动触发的返回(如导航栏返回)保留动画。 + const isProgrammaticBack = hasProgrammaticBackNavigation(); + // 仅在 iOS 系统返回手势下关闭动画。 + const shouldDisableTransition = isIOS && isBackwardNavigation && !isProgrammaticBack; + + if (shouldDisableTransition) { + transitionName.value = ""; + hasTransition.value = false; + return; + } if (fromDepth < toDepth) { transitionName.value = "push-left"; diff --git a/src/layout/MainLayout.vue b/src/layout/MainLayout.vue index 372fadf..e2777ee 100644 --- a/src/layout/MainLayout.vue +++ b/src/layout/MainLayout.vue @@ -129,33 +129,29 @@ function resolveTabValue(path: string): string { if (path === "/" || path.startsWith("/files/")) { return "/"; } - return path; } -watch( - () => route.path, - (newPath: string) => { - tabVal.value = resolveTabValue(newPath); - }, - { immediate: true } -); +watch(() => route.path, (newPath: string) => { + tabVal.value = resolveTabValue(newPath); +}, { immediate: true }); -function onChangeTab(value: string): void { +async function onChangeTab(value: string): Promise { if (value === "__music__") { musicPlayerStore.setPopupVisible(true); tabVal.value = resolveTabValue(route.path); return; } - - router.push(value); + if (resolveTabValue(route.path) === value) { + return; + } + await router.replace(value); } const isTabBarPaddingNeeded = computed(() => { if (!tabBarStore.isShowing) { return false; } - return route.meta.tabBarPadding !== false; }); @@ -163,7 +159,6 @@ const tabBarPadding = computed(() => { if (isTabBarPaddingNeeded.value) { return "6rem"; } - return "0rem"; }); diff --git a/src/pages/setting/ConnectSetting.vue b/src/pages/setting/ConnectSetting.vue index 498da7e..19eb8be 100644 --- a/src/pages/setting/ConnectSetting.vue +++ b/src/pages/setting/ConnectSetting.vue @@ -91,6 +91,7 @@ function saveConnect(): void { theme: "success", message: "连接配置已保存" }); + router.back(); } onMounted(() => { diff --git a/src/router/index.ts b/src/router/index.ts index 0f8095f..64e6bed 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -14,6 +14,10 @@ import ServerDetail from "@/pages/dashboard/ServerDashboard/ServerDetail.vue"; import ServerPerformanceDetail from "@/pages/dashboard/ServerDashboard/ServerPerformanceDetail.vue"; import ServerPartitionsDetail from "@/pages/dashboard/ServerDashboard/ServerPartitionsDetail.vue"; import DockerContainerDetail from "@/pages/dashboard/DockerDashboard/DockerContainerDetail.vue"; +import { + clearProgrammaticBackNavigation, + markProgrammaticBackNavigation +} from "@/utils/backNavigationSignal"; const router = createRouter({ history: createWebHistory("/"), @@ -180,6 +184,23 @@ const router = createRouter({ ] }); +const rawBack = router.back.bind(router); +const rawGo = router.go.bind(router); + +// 全局代理:所有代码触发的后退都自动标记为“主动返回”。 +router.back = () => { + markProgrammaticBackNavigation(); + rawBack(); +}; + +// 覆盖 go(-1 / -n) 场景,保证与 back() 行为一致。 +router.go = (delta) => { + if (delta < 0) { + markProgrammaticBackNavigation(); + } + rawGo(delta); +}; + router.beforeEach((to: RouteLocationNormalized) => { const settingStore = useSettingStore(); @@ -200,6 +221,8 @@ router.beforeEach((to: RouteLocationNormalized) => { }); router.afterEach((to: RouteLocationNormalized) => { + // 一次导航结束后清理信号,防止影响后续动画判定。 + clearProgrammaticBackNavigation(); const globalUIStore = useGlobalUIStore(); const targetBackground = typeof to.meta.bodyBackground === "string" ? to.meta.bodyBackground : DEFAULT_BODY_BACKGROUND; globalUIStore.setBodyBackground(targetBackground); diff --git a/src/utils/backNavigationSignal.ts b/src/utils/backNavigationSignal.ts new file mode 100644 index 0000000..61f99f5 --- /dev/null +++ b/src/utils/backNavigationSignal.ts @@ -0,0 +1,28 @@ +let programmaticBackAt = 0; + +// 主动返回信号有效期,避免历史信号污染后续导航 +const PROGRAMMATIC_BACK_SIGNAL_TTL_MS = 1200; + +// 由代码触发 router.back / router.go(-1) 时打标 +export function markProgrammaticBackNavigation(): void { + programmaticBackAt = Date.now(); +} + +// 读取主动返回信号,超时后自动清理 +export function hasProgrammaticBackNavigation(): boolean { + if (programmaticBackAt < 1) { + return false; + } + + const hasSignal = Date.now() - programmaticBackAt <= PROGRAMMATIC_BACK_SIGNAL_TTL_MS; + if (!hasSignal) { + programmaticBackAt = 0; + } + + return hasSignal; +} + +// 在导航结束后统一清理信号 +export function clearProgrammaticBackNavigation(): void { + programmaticBackAt = 0; +}