Skip to content

Commit

Permalink
✨ feat(project): add transition router view virtural stack
Browse files Browse the repository at this point in the history
  • Loading branch information
ishareme committed Jan 11, 2023
1 parent 6bb437b commit ed2db00
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 7 deletions.
6 changes: 5 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<template>
<div class="h-screen w-screen fixed top-0 left-0">
<router-view />
<!-- <router-view /> -->
<TransitionRouterView
mainComponentName="home"
:routerType="$store.getters.routerType"
/>
</div>
</template>

Expand Down
167 changes: 167 additions & 0 deletions src/libs/TransitionRouterView/index.vue
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>
11 changes: 11 additions & 0 deletions src/libs/TriggerMenu/index.vue
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>
42 changes: 42 additions & 0 deletions src/libs/TriggerMenuItem/index.vue
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>
3 changes: 2 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ import './permission';
useRem();
useTheme();

createApp(App).use(router).use(store).use(libs).use(directives).mount('#app');
const app = createApp(App);
app.use(router).use(store).use(libs).use(directives).mount('#app');
11 changes: 11 additions & 0 deletions src/store/getters.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isMobileTerminal } from '@/utils/flexible';

export default {
categorys: (state) => state.category.categorys,
theme: (state) => state.theme.themeType,
Expand All @@ -13,4 +15,13 @@ export default {

token: (state) => state.user.token,
userInfo: (state) => state.user.userInfo,

// 路由跳转方式
routerType: (state) => {
// 在 PC 端下,永远为 none
if (!isMobileTerminal.value) {
return 'none';
}
return state.app.routerType;
},
};
10 changes: 10 additions & 0 deletions src/store/modules/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export default {

// 搜索文本
searchText: '',

// 路由跳转类型
routerType: 'none',
},
mutations: {
changeCurrentCategory(state, newCategory) {
Expand All @@ -16,6 +19,13 @@ export default {
changeSearchText(state, newText) {
state.searchText = newText;
},

/**
* 修改 routerType
*/
changeRouterType(state, newType) {
state.routerType = newType;
},
},
actions: {},
};
1 change: 0 additions & 1 deletion src/store/modules/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export default {
async profile(context) {
try {
const data = await getProfile();
console.log('[ data ]', data);
if (!data._id) throw new Error('');
context.commit('setUserInfo', data);
Message(
Expand Down
2 changes: 1 addition & 1 deletion src/views/loginRegister/login/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
name="username"
/>
<VeeField
type="text"
type="password"
class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:default:bg-zinc-900"
name="password"
placeholder="password"
Expand Down
2 changes: 1 addition & 1 deletion src/views/loginRegister/register/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
name="password"
/>
<VeeField
type="confirmPassword"
type="password"
class="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:default:bg-zinc-900"
name="confirmPassword"
placeholder="确认密码"
Expand Down
63 changes: 63 additions & 0 deletions src/views/main/index.vue
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>
Loading

0 comments on commit ed2db00

Please sign in to comment.