upper PageTransition
This commit is contained in:
@@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<route-page :background="background" :component="component" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Component } from "vue";
|
|
||||||
import RoutePage from "@/components/RoutePage.vue";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: "CachedRoutePage"
|
|
||||||
});
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
background: string;
|
|
||||||
component: Component;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-transition">
|
|
||||||
<router-view v-slot="{ Component }">
|
|
||||||
<transition :name="transitionName">
|
|
||||||
<keep-alive :include="keepAlivePageNames">
|
|
||||||
<component
|
|
||||||
:is="isCachedPage ? CachedRoutePage : RoutePage"
|
|
||||||
:key="route.path"
|
|
||||||
:background="pageBackground"
|
|
||||||
:component="Component"
|
|
||||||
/>
|
|
||||||
</keep-alive>
|
|
||||||
</transition>
|
|
||||||
</router-view>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { RouteLocationNormalized } from "vue-router";
|
|
||||||
import CachedRoutePage from "@/components/CachedRoutePage.vue";
|
|
||||||
import RoutePage from "@/components/RoutePage.vue";
|
|
||||||
import { useGlobalUIStore } from "@/store/globalUI";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: "PageTransition"
|
|
||||||
});
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const globalUIStore = useGlobalUIStore();
|
|
||||||
// KeepAlive 直接缓存的是单根路由边界,列表页面位于该边界子树中,仍会收到 activated / deactivated 生命周期。
|
|
||||||
const keepAlivePageNames = ["CachedRoutePage"];
|
|
||||||
const cachedPageRouteNameSet = new Set(["SalesOrderList", "ProductList", "ProductStockList"]);
|
|
||||||
const isCachedPage = computed(() => cachedPageRouteNameSet.has(String(route.name)));
|
|
||||||
|
|
||||||
const transitionName = ref("push-left");
|
|
||||||
const pageBackground = computed(() => globalUIStore.bodyBackground);
|
|
||||||
|
|
||||||
// ---------- 路由深度计算 ----------
|
|
||||||
|
|
||||||
const pathCache = new Map<string, number>();
|
|
||||||
|
|
||||||
function calcDepth(sourceRoute: RouteLocationNormalized): number {
|
|
||||||
const queryDepth = parseTransitionDepth(sourceRoute.query.transitionDepth);
|
|
||||||
if (queryDepth !== undefined) {
|
|
||||||
return queryDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sourceRoute.meta.dynamicDepth) {
|
|
||||||
return Number(sourceRoute.meta.depth ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullPath = sourceRoute.fullPath;
|
|
||||||
const cachedDepth = pathCache.get(fullPath);
|
|
||||||
if (cachedDepth !== undefined) {
|
|
||||||
return cachedDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseDepth = Number(sourceRoute.meta.baseDepth ?? 0);
|
|
||||||
const pathSegments = fullPath.split("?")[0].split("/").filter(pathSegment => pathSegment !== "");
|
|
||||||
const depth = baseDepth + (pathSegments.length - 1);
|
|
||||||
pathCache.set(fullPath, depth);
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTransitionDepth(value: unknown): number | undefined {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return parseTransitionDepth(value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value !== "string") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = value.trim();
|
|
||||||
if (!text) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const depth = Number(text);
|
|
||||||
if (!Number.isInteger(depth) || depth < 0 || 20 < depth) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unregisterGuard = router.beforeEach((to, from) => {
|
|
||||||
const toDepth = calcDepth(to);
|
|
||||||
const fromDepth = calcDepth(from);
|
|
||||||
|
|
||||||
if (fromDepth < toDepth) {
|
|
||||||
transitionName.value = "push-left";
|
|
||||||
} else if (toDepth < fromDepth) {
|
|
||||||
transitionName.value = "push-right";
|
|
||||||
} else {
|
|
||||||
transitionName.value = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
unregisterGuard();
|
|
||||||
pathCache.clear();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
@easing: cubic-bezier(.19, 1, .22, 1);
|
|
||||||
@duration: 520ms;
|
|
||||||
@brightness: .98;
|
|
||||||
@shadow-intensity: .15;
|
|
||||||
@page-offset: 25%;
|
|
||||||
|
|
||||||
.page-transition {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow-x: clip;
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
// ---------- push-left / push-right 公共激活状态 ----------
|
|
||||||
|
|
||||||
:deep(.push-left-enter-active),
|
|
||||||
:deep(.push-left-leave-active),
|
|
||||||
:deep(.push-right-enter-active),
|
|
||||||
:deep(.push-right-leave-active) {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: fixed;
|
|
||||||
transition: transform @duration @easing, opacity .2s linear;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- push-left:下一页从右滑入,当前页缩退到左侧 ----------
|
|
||||||
|
|
||||||
:deep(.push-left-enter-from) {
|
|
||||||
transform: translate3d(100%, 0, 0);
|
|
||||||
box-shadow: -4px 0 16px rgba(0, 0, 0, @shadow-intensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-left-enter-to) {
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-left-leave-active) {
|
|
||||||
transition: all @duration @easing, filter .3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-left-leave-to) {
|
|
||||||
filter: brightness(@brightness);
|
|
||||||
transform: translate3d(-@page-offset, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- push-right:当前页从右滑出,上一页从左侧复位 ----------
|
|
||||||
|
|
||||||
:deep(.push-right-enter-active) {
|
|
||||||
z-index: 0;
|
|
||||||
transition: transform @duration @easing, opacity .3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-right-enter-from) {
|
|
||||||
transform: translate3d(-@page-offset, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-right-enter-to) {
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-right-leave-active) {
|
|
||||||
z-index: 1;
|
|
||||||
transition: transform @duration @easing, box-shadow .3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-right-leave-from) {
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.push-right-leave-to) {
|
|
||||||
transform: translate3d(105%, 0, 0);
|
|
||||||
box-shadow: 4px 0 16px rgba(0, 0, 0, @shadow-intensity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page">
|
|
||||||
<component :is="component" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Component } from "vue";
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: "RoutePage"
|
|
||||||
});
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
component: Component;
|
|
||||||
background: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.page {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
overflow-x: clip;
|
|
||||||
background: v-bind(background);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -2,7 +2,12 @@
|
|||||||
<div class="main-layout">
|
<div class="main-layout">
|
||||||
<app-nav-bar @height-change="onChangeNavBarHeight" />
|
<app-nav-bar @height-change="onChangeNavBarHeight" />
|
||||||
<div class="router-view">
|
<div class="router-view">
|
||||||
<page-transition />
|
<page-transition
|
||||||
|
:keep-alive="shouldKeepAlive"
|
||||||
|
:page-background="globalUIStore.bodyBackground"
|
||||||
|
page-class="page"
|
||||||
|
:respect-reduced-motion="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<t-tab-bar
|
<t-tab-bar
|
||||||
v-model="tabVal"
|
v-model="tabVal"
|
||||||
@@ -33,16 +38,25 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AppNavBar from "@/components/navbar/index.vue";
|
import AppNavBar from "@/components/navbar/index.vue";
|
||||||
import PageTransition from "@/components/PageTransition.vue";
|
import { useGlobalUIStore } from "@/store/globalUI";
|
||||||
import { useTabBarStore } from "@/store/tabBarStore";
|
import { useTabBarStore } from "@/store/tabBarStore";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
import { PageTransition } from "timi-tdesign-mobile";
|
||||||
import { useUserStore } from "timi-web-api/store";
|
import { useUserStore } from "timi-web-api/store";
|
||||||
|
import type { RouteLocationNormalizedLoaded } from "vue-router";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const globalUIStore = useGlobalUIStore();
|
||||||
const tabBarStore = useTabBarStore();
|
const tabBarStore = useTabBarStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const cachedRouteNames = new Set(["SalesOrderList", "ProductList", "ProductStockList"]);
|
||||||
|
|
||||||
|
function shouldKeepAlive(sourceRoute: RouteLocationNormalizedLoaded): boolean {
|
||||||
|
return cachedRouteNames.has(String(sourceRoute.name));
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 导航边距 ----------
|
// ---------- 导航边距 ----------
|
||||||
|
|
||||||
const navBarHeight = ref("0px");
|
const navBarHeight = ref("0px");
|
||||||
|
|||||||
Reference in New Issue
Block a user