forked from ishareme/vue3-front-common
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ feat(project): add transition router view virtural stack
- Loading branch information
Showing
12 changed files
with
316 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
<template> | ||
<!-- 路由出口 --> | ||
<router-view v-slot="{ Component }"> | ||
<!-- 动画组件 --> | ||
<transition | ||
:name="transitionName" | ||
@before-enter="beforeEnter" | ||
@after-leave="afterLeave" | ||
> | ||
<!-- 缓存组件 --> | ||
<keep-alive :include="virtualTaskStack"> | ||
<component | ||
:is="Component" | ||
:class="{ 'fixed top-0 left-0 w-screen z-50': isAnimation }" | ||
:key="$route.fullPath" | ||
/> | ||
</keep-alive> | ||
</transition> | ||
</router-view> | ||
</template> | ||
|
||
<script> | ||
import { ref } from 'vue'; | ||
import { useRouter } from 'vue-router'; | ||
// 无需监听路由的各种状态(在 PC 端下) | ||
const NONE = 'none'; | ||
// 路由进入 | ||
const PUSH = 'push'; | ||
// 路由退出 | ||
const BACK = 'back'; | ||
// 路由跳转的 enum | ||
const ROUTER_TYPE_ENUM = [NONE, PUSH, BACK]; | ||
</script> | ||
|
||
<script setup> | ||
const props = defineProps({ | ||
// 路由跳转的类型,对应 ROUTER_TYPE_ENUM | ||
routerType: { | ||
type: String, | ||
default: NONE, | ||
validator(val) { | ||
const result = ROUTER_TYPE_ENUM.includes(val); | ||
if (!result) { | ||
throw new Error( | ||
`你的 routerType 必须是 ${ROUTER_TYPE_ENUM.join( | ||
'、' | ||
)} 中的一个` | ||
); | ||
} | ||
return result; | ||
}, | ||
}, | ||
// 首页的组件名称,对应任务栈中的第一个组件 | ||
mainComponentName: { | ||
type: String, | ||
required: true, | ||
}, | ||
}); | ||
// 任务栈 | ||
const virtualTaskStack = ref([props.mainComponentName]); | ||
const router = useRouter(); | ||
// 跳转动画 | ||
const transitionName = ref(''); | ||
/** | ||
* 监听路由变化 | ||
*/ | ||
router.beforeEach((to, from) => { | ||
// 定义当前动画名称 | ||
transitionName.value = props.routerType; | ||
console.log('[ transitionName.value ]', transitionName.value); | ||
if (props.routerType === PUSH) { | ||
// 入栈 | ||
virtualTaskStack.value.push(to.name); | ||
} else if (props.routerType === BACK) { | ||
// 出栈 | ||
virtualTaskStack.value.pop(); | ||
} | ||
// 进入首页默认清空栈 | ||
if (to.name === props.mainComponentName) { | ||
clearTask(); | ||
} | ||
}); | ||
// 处理动画状态变化 | ||
const isAnimation = ref(false); | ||
const beforeEnter = () => { | ||
isAnimation.value = true; | ||
}; | ||
const afterLeave = () => { | ||
isAnimation.value = false; | ||
}; | ||
/** | ||
* 清空栈 | ||
*/ | ||
const clearTask = () => { | ||
virtualTaskStack.value = [props.mainComponentName]; | ||
}; | ||
</script> | ||
|
||
<style lang="scss" scoped> | ||
// push页面时:新页面的进入动画 | ||
.push-enter-active { | ||
animation-name: push-in; | ||
animation-duration: 0.6s; | ||
} | ||
// push页面时:老页面的退出动画 | ||
.push-leave-active { | ||
animation-name: push-out; | ||
animation-duration: 0.6s; | ||
} | ||
// push页面时:新页面的进入动画 | ||
@keyframes push-in { | ||
0% { | ||
transform: translate(100%, 0); | ||
} | ||
100% { | ||
transform: translate(0, 0); | ||
} | ||
} | ||
// push页面时:老页面的退出动画 | ||
@keyframes push-out { | ||
0% { | ||
transform: translate(0, 0); | ||
} | ||
100% { | ||
transform: translate(-50%, 0); | ||
} | ||
} | ||
// 后退页面时:即将展示的页面动画 | ||
.back-enter-active { | ||
animation-name: back-in; | ||
animation-duration: 0.6s; | ||
} | ||
// 后退页面时:后退的页面执行的动画 | ||
.back-leave-active { | ||
animation-name: back-out; | ||
animation-duration: 0.6s; | ||
} | ||
// 后退页面时:即将展示的页面动画 | ||
@keyframes back-in { | ||
0% { | ||
width: 100%; | ||
transform: translate(-100%, 0); | ||
} | ||
100% { | ||
width: 100%; | ||
transform: translate(0, 0); | ||
} | ||
} | ||
// 后退页面时:后退的页面执行的动画 | ||
@keyframes back-out { | ||
0% { | ||
width: 100%; | ||
transform: translate(0, 0); | ||
} | ||
100% { | ||
width: 100%; | ||
transform: translate(50%, 0); | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<template> | ||
<div | ||
class="min-w-[180px] bg-white dark:bg-zinc-800 rounded-full shadow flex items-center justify-between px-2 py-1" | ||
> | ||
<slot></slot> | ||
</div> | ||
</template> | ||
|
||
<script setup></script> | ||
|
||
<style lang="scss" scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<template> | ||
<div | ||
class="w-5 flex flex-col items-center justify-center mx-0.5" | ||
@click="onItemClick" | ||
> | ||
<SvgIcon class="w-2 h-2" :name="icon" :fillClass="iconClass" /> | ||
<p class="text-sm mt-0.5" :class="textClass"> | ||
<slot></slot> | ||
</p> | ||
</div> | ||
</template> | ||
|
||
<script setup> | ||
import { useRouter } from 'vue-router'; | ||
const props = defineProps({ | ||
icon: { | ||
type: String, | ||
required: true, | ||
}, | ||
iconClass: { | ||
type: String, | ||
}, | ||
textClass: { | ||
type: String, | ||
default: 'text-zinc-900 dark:text-zinc-200', | ||
}, | ||
to: { | ||
type: String, | ||
}, | ||
}); | ||
const router = useRouter(); | ||
const onItemClick = () => { | ||
if (!props.to) { | ||
return; | ||
} | ||
router.push(props.to); | ||
}; | ||
</script> | ||
|
||
<style lang="scss" scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,80 @@ | ||
<template> | ||
<div | ||
class="h-full overflow-auto bg-white dark:bg-zinc-800 duration-500 scrollbar-thin scrollbar-thumb-transparent xl:scrollbar-thumb-zinc-200 xl:dark:scrollbar-thumb-zinc-900 scrollbar-track-transparent" | ||
ref="containerTarget" | ||
> | ||
<Navigation /> | ||
<div class="max-w-xl mx-auto relative m-1 xl:mt-4"> | ||
<List /> | ||
</div> | ||
|
||
<!-- 移动端下 tabbar --> | ||
<TriggerMenu | ||
v-if="isMobileTerminal" | ||
class="fixed bottom-6 left-0 right-0 mx-auto w-[220px]" | ||
> | ||
<TriggerMenuItem | ||
icon="home" | ||
iconClass="fill-zinc-900 dark:fill-zinc-200" | ||
>首页</TriggerMenuItem | ||
> | ||
<TriggerMenuItem | ||
v-if="$store.getters.token" | ||
icon="vip" | ||
iconClass="fill-zinc-400 dark:fill-zinc-500" | ||
textClass="text-zinc-400 dark:text-zinc-500" | ||
@click="onVipClick" | ||
>VIP</TriggerMenuItem | ||
> | ||
<TriggerMenuItem | ||
icon="profile" | ||
iconClass="fill-zinc-400 dark:fill-zinc-500" | ||
textClass="text-zinc-400 dark:text-zinc-500" | ||
@click="onMyClick" | ||
>{{ $store.getters.token ? '我的' : '去登录' }}</TriggerMenuItem | ||
> | ||
</TriggerMenu> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
// 组件名字 组件缓存 | ||
export default { | ||
name: 'home', | ||
}; | ||
</script> | ||
<script setup> | ||
import Navigation from './components/navigation'; | ||
import List from './components/list'; | ||
import { isMobileTerminal } from '@/utils/flexible.js'; | ||
import { useStore } from 'vuex'; | ||
import { useRouter } from 'vue-router'; | ||
import { useScroll } from '@vueuse/core'; | ||
import { ref, onActivated } from 'vue'; | ||
const store = useStore(); | ||
const router = useRouter(); | ||
const onVipClick = () => { | ||
store.commit('app/changeRouterType', 'push'); | ||
}; | ||
const onMyClick = () => { | ||
store.commit('app/changeRouterType', 'push'); | ||
if (store.getters.token) { | ||
router.push('/profile'); | ||
} else { | ||
router.push('/login'); | ||
} | ||
}; | ||
// 记录滚动 | ||
const containerTarget = ref(null); | ||
const { y: containerTargetY } = useScroll(containerTarget); | ||
// 被缓存的组件 再次回调 | ||
onActivated(() => { | ||
if (!containerTarget.value) return; | ||
containerTarget.value.scrollTop = containerTargetY.value; | ||
}); | ||
</script> | ||
|
||
<style lang="scss" scoped></style> |
Oops, something went wrong.