init project
This commit is contained in:
156
src/components/PageTransition.vue
Normal file
156
src/components/PageTransition.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="page-transition">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition v-if="hasTransition" :name="transitionName">
|
||||
<div class="pages" :key="route.fullPath">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</transition>
|
||||
<div v-else class="pages" :key="route.fullPath">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { RouteLocationNormalized } from "vue-router";
|
||||
import { useGlobalUIStore } from "@/store/globalUIStore";
|
||||
|
||||
defineOptions({
|
||||
name: "PageTransition"
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const globalUIStore = useGlobalUIStore();
|
||||
|
||||
const transitionName = ref("push-left");
|
||||
const hasTransition = computed(() => transitionName.value !== "");
|
||||
const pageBackground = computed(() => globalUIStore.bodyBackground);
|
||||
|
||||
// ---------- 路由深度计算 ----------
|
||||
|
||||
const pathCache = new Map<string, number>();
|
||||
|
||||
function calcDepth(sourceRoute: RouteLocationNormalized): number {
|
||||
if (!sourceRoute.meta.dynamicDepth) {
|
||||
return Number(sourceRoute.meta.depth ?? 0);
|
||||
}
|
||||
|
||||
const fullPath = sourceRoute.fullPath;
|
||||
const cached = pathCache.get(fullPath);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const base = Number(sourceRoute.meta.baseDepth ?? 0);
|
||||
const pathSegments = fullPath.split("?")[0].split("/").filter((pathSegment) => pathSegment !== "");
|
||||
const depth = base + (pathSegments.length - 1);
|
||||
pathCache.set(fullPath, depth);
|
||||
return depth;
|
||||
}
|
||||
|
||||
// beforeEach 返回注销函数,组件卸载时必须调用,否则守卫会一直存在。
|
||||
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 lang="less" scoped>
|
||||
@easing: cubic-bezier(.19, 1, .22, 1);
|
||||
@duration: 750ms;
|
||||
@brightness: .98;
|
||||
@shadow-intensity: .15;
|
||||
@page-offset: 25%;
|
||||
|
||||
.page-transition {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
|
||||
// 这个 wrapper 必须独立,不能把 class=pages 直接挂在 <component> 上。
|
||||
// 否则页面根节点会和动画容器变成同一个 DOM 节点,容易导致 fixed 布局溢出视口。
|
||||
.pages {
|
||||
height: 100%;
|
||||
background: v-bind(pageBackground);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// ---------- push-left / push-right 公共过渡状态 ----------
|
||||
|
||||
.push-left-enter-active,
|
||||
.push-left-leave-active,
|
||||
.push-right-enter-active,
|
||||
.push-right-leave-active {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
transition: transform @duration @easing, opacity .2s linear;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
// ---------- 前进:下一页从右滑入,当前页退到左侧 ----------
|
||||
|
||||
.push-left-enter-from {
|
||||
transform: translate3d(100%, 0, 0);
|
||||
box-shadow: -4px 0 16px rgba(0, 0, 0, @shadow-intensity);
|
||||
}
|
||||
|
||||
.push-left-enter-to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.push-left-leave-active {
|
||||
transition: all @duration @easing, filter .3s;
|
||||
}
|
||||
|
||||
.push-left-leave-to {
|
||||
filter: brightness(@brightness);
|
||||
transform: translate3d(-@page-offset, 0, 0);
|
||||
}
|
||||
|
||||
// ---------- 返回:当前页从右滑出,上一页回到原位 ----------
|
||||
|
||||
.push-right-enter-active {
|
||||
z-index: -1;
|
||||
transition: transform @duration @easing, opacity .3s;
|
||||
}
|
||||
|
||||
.push-right-enter-from {
|
||||
transform: translate3d(-@page-offset, 0, 0);
|
||||
}
|
||||
|
||||
.push-right-enter-to {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.push-right-leave-active {
|
||||
transition: transform @duration @easing, box-shadow .3s;
|
||||
}
|
||||
|
||||
.push-right-leave-from {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.push-right-leave-to {
|
||||
transform: translate3d(105%, 0, 0);
|
||||
box-shadow: 4px 0 16px rgba(0, 0, 0, @shadow-intensity);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
src/components/RoutePlaceholder.vue
Normal file
63
src/components/RoutePlaceholder.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<section class="placeholder">
|
||||
<div class="box">
|
||||
<p class="tag">当前组件</p>
|
||||
<h1 class="title" v-text="title"></h1>
|
||||
<p class="desc" v-text="description"></p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title: string;
|
||||
description: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.placeholder {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 1.2rem;
|
||||
|
||||
.box {
|
||||
gap: .9rem;
|
||||
display: flex;
|
||||
min-height: 12rem;
|
||||
padding: 1.2rem;
|
||||
border-radius: 1rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--app-line);
|
||||
background: linear-gradient(160deg, rgba(255, 255, 255, .92), rgba(255, 255, 255, .72));
|
||||
box-shadow: 0 .4rem 1.2rem rgba(17, 32, 56, .08);
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin: 0;
|
||||
color: var(--app-primary);
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin: 0;
|
||||
color: var(--app-sub);
|
||||
font-size: .95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .placeholder {
|
||||
.box {
|
||||
background: linear-gradient(160deg, rgba(29, 37, 47, .96), rgba(22, 28, 36, .88));
|
||||
box-shadow: 0 .4rem 1.2rem rgba(0, 0, 0, .28);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user