fix iOS back transition
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
import type { RouteLocationNormalized } from "vue-router";
|
import type { RouteLocationNormalized } from "vue-router";
|
||||||
import { viewDepthKey } from "vue-router";
|
import { viewDepthKey } from "vue-router";
|
||||||
import { useGlobalUIStore } from "@/store/globalUIStore";
|
import { useGlobalUIStore } from "@/store/globalUIStore";
|
||||||
|
import { hasProgrammaticBackNavigation } from "@/utils/backNavigationSignal";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "PageTransition"
|
name: "PageTransition"
|
||||||
@@ -26,6 +27,8 @@ const viewDepth = inject(viewDepthKey, 0);
|
|||||||
|
|
||||||
const transitionName = ref("");
|
const transitionName = ref("");
|
||||||
const hasTransition = ref(false);
|
const hasTransition = ref(false);
|
||||||
|
// iOS 左滑返回由系统接管,页面侧不应叠加离场动画。
|
||||||
|
const isIOS = /iP(ad|hone|od)/i.test(window.navigator.userAgent);
|
||||||
const pageBackground = computed(() => globalUIStore.bodyBackground);
|
const pageBackground = computed(() => globalUIStore.bodyBackground);
|
||||||
const currentDepth = computed(() => Number(unref(viewDepth)));
|
const currentDepth = computed(() => Number(unref(viewDepth)));
|
||||||
const pageKey = computed(() => {
|
const pageKey = computed(() => {
|
||||||
@@ -72,6 +75,17 @@ const unregisterGuard = router.beforeEach((to, from) => {
|
|||||||
|
|
||||||
const toDepth = calcDepth(to);
|
const toDepth = calcDepth(to);
|
||||||
const fromDepth = calcDepth(from);
|
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) {
|
if (fromDepth < toDepth) {
|
||||||
transitionName.value = "push-left";
|
transitionName.value = "push-left";
|
||||||
|
|||||||
@@ -129,33 +129,29 @@ function resolveTabValue(path: string): string {
|
|||||||
if (path === "/" || path.startsWith("/files/")) {
|
if (path === "/" || path.startsWith("/files/")) {
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(() => route.path, (newPath: string) => {
|
||||||
() => route.path,
|
|
||||||
(newPath: string) => {
|
|
||||||
tabVal.value = resolveTabValue(newPath);
|
tabVal.value = resolveTabValue(newPath);
|
||||||
},
|
}, { immediate: true });
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
function onChangeTab(value: string): void {
|
async function onChangeTab(value: string): Promise<void> {
|
||||||
if (value === "__music__") {
|
if (value === "__music__") {
|
||||||
musicPlayerStore.setPopupVisible(true);
|
musicPlayerStore.setPopupVisible(true);
|
||||||
tabVal.value = resolveTabValue(route.path);
|
tabVal.value = resolveTabValue(route.path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (resolveTabValue(route.path) === value) {
|
||||||
router.push(value);
|
return;
|
||||||
|
}
|
||||||
|
await router.replace(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTabBarPaddingNeeded = computed(() => {
|
const isTabBarPaddingNeeded = computed(() => {
|
||||||
if (!tabBarStore.isShowing) {
|
if (!tabBarStore.isShowing) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return route.meta.tabBarPadding !== false;
|
return route.meta.tabBarPadding !== false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,7 +159,6 @@ const tabBarPadding = computed(() => {
|
|||||||
if (isTabBarPaddingNeeded.value) {
|
if (isTabBarPaddingNeeded.value) {
|
||||||
return "6rem";
|
return "6rem";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "0rem";
|
return "0rem";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ function saveConnect(): void {
|
|||||||
theme: "success",
|
theme: "success",
|
||||||
message: "连接配置已保存"
|
message: "连接配置已保存"
|
||||||
});
|
});
|
||||||
|
router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import ServerDetail from "@/pages/dashboard/ServerDashboard/ServerDetail.vue";
|
|||||||
import ServerPerformanceDetail from "@/pages/dashboard/ServerDashboard/ServerPerformanceDetail.vue";
|
import ServerPerformanceDetail from "@/pages/dashboard/ServerDashboard/ServerPerformanceDetail.vue";
|
||||||
import ServerPartitionsDetail from "@/pages/dashboard/ServerDashboard/ServerPartitionsDetail.vue";
|
import ServerPartitionsDetail from "@/pages/dashboard/ServerDashboard/ServerPartitionsDetail.vue";
|
||||||
import DockerContainerDetail from "@/pages/dashboard/DockerDashboard/DockerContainerDetail.vue";
|
import DockerContainerDetail from "@/pages/dashboard/DockerDashboard/DockerContainerDetail.vue";
|
||||||
|
import {
|
||||||
|
clearProgrammaticBackNavigation,
|
||||||
|
markProgrammaticBackNavigation
|
||||||
|
} from "@/utils/backNavigationSignal";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory("/"),
|
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) => {
|
router.beforeEach((to: RouteLocationNormalized) => {
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
|
|
||||||
@@ -200,6 +221,8 @@ router.beforeEach((to: RouteLocationNormalized) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach((to: RouteLocationNormalized) => {
|
router.afterEach((to: RouteLocationNormalized) => {
|
||||||
|
// 一次导航结束后清理信号,防止影响后续动画判定。
|
||||||
|
clearProgrammaticBackNavigation();
|
||||||
const globalUIStore = useGlobalUIStore();
|
const globalUIStore = useGlobalUIStore();
|
||||||
const targetBackground = typeof to.meta.bodyBackground === "string" ? to.meta.bodyBackground : DEFAULT_BODY_BACKGROUND;
|
const targetBackground = typeof to.meta.bodyBackground === "string" ? to.meta.bodyBackground : DEFAULT_BODY_BACKGROUND;
|
||||||
globalUIStore.setBodyBackground(targetBackground);
|
globalUIStore.setBodyBackground(targetBackground);
|
||||||
|
|||||||
28
src/utils/backNavigationSignal.ts
Normal file
28
src/utils/backNavigationSignal.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user