9 changed files with 301 additions and 83 deletions
@ -0,0 +1,98 @@ |
|||||
|
import type { VNode } from 'vue'; |
||||
|
import type { |
||||
|
RouteLocationNormalizedLoaded, |
||||
|
RouteLocationNormalizedLoadedGeneric, |
||||
|
} from 'vue-router'; |
||||
|
|
||||
|
import { computed } from 'vue'; |
||||
|
|
||||
|
import { preferences, usePreferences } from '@vben/preferences'; |
||||
|
|
||||
|
/** |
||||
|
* 转换组件,自动添加 name |
||||
|
* @param component |
||||
|
* @param route |
||||
|
*/ |
||||
|
export function transformComponent( |
||||
|
component: VNode, |
||||
|
route: RouteLocationNormalizedLoadedGeneric, |
||||
|
) { |
||||
|
// 组件视图未找到,如果有设置后备视图,则返回后备视图,如果没有,则抛出错误
|
||||
|
if (!component) { |
||||
|
console.error( |
||||
|
'Component view not found,please check the route configuration', |
||||
|
); |
||||
|
return undefined; |
||||
|
} |
||||
|
|
||||
|
const routeName = route.name as string; |
||||
|
// 如果组件没有 name,则直接返回
|
||||
|
if (!routeName) { |
||||
|
return component; |
||||
|
} |
||||
|
const componentName = (component?.type as any)?.name; |
||||
|
|
||||
|
// 已经设置过 name,则直接返回
|
||||
|
if (componentName) { |
||||
|
return component; |
||||
|
} |
||||
|
|
||||
|
// componentName 与 routeName 一致,则直接返回
|
||||
|
if (componentName === routeName) { |
||||
|
return component; |
||||
|
} |
||||
|
|
||||
|
// 设置 name
|
||||
|
component.type ||= {}; |
||||
|
(component.type as any).name = routeName; |
||||
|
|
||||
|
return component; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Layout相关hook |
||||
|
*/ |
||||
|
export function useLayoutHook() { |
||||
|
const { keepAlive } = usePreferences(); |
||||
|
/** |
||||
|
* 是否使用动画 |
||||
|
*/ |
||||
|
const getEnabledTransition = computed(() => { |
||||
|
const { transition } = preferences; |
||||
|
const transitionName = transition.name; |
||||
|
return transitionName && transition.enable; |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 获取路由过渡动画 |
||||
|
* @param _route |
||||
|
*/ |
||||
|
function getTransitionName(_route: RouteLocationNormalizedLoaded) { |
||||
|
// 如果偏好设置未设置,则不使用动画
|
||||
|
const { tabbar, transition } = preferences; |
||||
|
const transitionName = transition.name; |
||||
|
if (!transitionName || !transition.enable) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 标签页未启用或者未开启缓存,则使用全局配置动画
|
||||
|
if (!tabbar.enable || !keepAlive) { |
||||
|
return transitionName; |
||||
|
} |
||||
|
|
||||
|
// 如果页面已经加载过,则不使用动画
|
||||
|
// if (route.meta.loaded) {
|
||||
|
// return;
|
||||
|
// }
|
||||
|
// 已经打开且已经加载过的页面不使用动画
|
||||
|
// const inTabs = getCachedTabs.value.includes(route.name as string);
|
||||
|
|
||||
|
// return inTabs && route.meta.loaded ? undefined : transitionName;
|
||||
|
return transitionName; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
getEnabledTransition, |
||||
|
getTransitionName, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as RouteCachedPage } from './route-cached-page.vue'; |
||||
|
export { default as RouteCachedView } from './route-cached-view.vue'; |
||||
@ -0,0 +1,36 @@ |
|||||
|
<!-- 本组件用于获取缓存的route并保存到pinia --> |
||||
|
<script setup lang="ts"> |
||||
|
import type { VNode } from 'vue'; |
||||
|
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'; |
||||
|
|
||||
|
import { watch } from 'vue'; |
||||
|
|
||||
|
import { useTabbarStore } from '@vben/stores'; |
||||
|
|
||||
|
interface Props { |
||||
|
component?: VNode; |
||||
|
route: RouteLocationNormalizedLoadedGeneric; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 这是页面缓存组件,不做任何的的实际渲染 |
||||
|
*/ |
||||
|
defineOptions({ |
||||
|
render() { |
||||
|
return null; |
||||
|
}, |
||||
|
}); |
||||
|
const props = defineProps<Props>(); |
||||
|
|
||||
|
const { addCachedRoute } = useTabbarStore(); |
||||
|
|
||||
|
watch( |
||||
|
() => props.route, |
||||
|
() => { |
||||
|
if (props.component && props.route.meta.domCached) { |
||||
|
addCachedRoute(props.component, props.route); |
||||
|
} |
||||
|
}, |
||||
|
{ immediate: true }, |
||||
|
); |
||||
|
</script> |
||||
@ -0,0 +1,98 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed, unref, watch } from 'vue'; |
||||
|
import { useRoute } from 'vue-router'; |
||||
|
|
||||
|
import { preferences } from '@vben/preferences'; |
||||
|
import { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores'; |
||||
|
|
||||
|
import { transformComponent, useLayoutHook } from '../hooks'; |
||||
|
|
||||
|
const route = useRoute(); |
||||
|
|
||||
|
const tabbarStore = useTabbarStore(); |
||||
|
|
||||
|
const { getTabs, getCachedRoutes, getExcludeCachedTabs } = |
||||
|
storeToRefs(tabbarStore); |
||||
|
const { removeCachedRoute } = tabbarStore; |
||||
|
|
||||
|
const { getEnabledTransition, getTransitionName } = useLayoutHook(); |
||||
|
|
||||
|
/** |
||||
|
* 是否启用tab |
||||
|
*/ |
||||
|
const enableTabbar = computed(() => preferences.tabbar.enable); |
||||
|
|
||||
|
const computedCachedRouteKeys = computed(() => { |
||||
|
if (!unref(enableTabbar)) { |
||||
|
return []; |
||||
|
} |
||||
|
return unref(getTabs) |
||||
|
.filter((item) => item.meta.domCached) |
||||
|
.map((item) => getTabKey(item)); |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 监听缓存路由变化,删除不存在的缓存路由 |
||||
|
*/ |
||||
|
watch(computedCachedRouteKeys, (keys) => { |
||||
|
unref(getCachedRoutes).forEach((item) => { |
||||
|
if (!keys.includes(item.key)) { |
||||
|
removeCachedRoute(item.key); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 所有缓存的route |
||||
|
*/ |
||||
|
const computedCachedRoutes = computed(() => { |
||||
|
if (!unref(enableTabbar)) { |
||||
|
return []; |
||||
|
} |
||||
|
// 刷新路由可刷新缓存 |
||||
|
const excludeCachedTabKeys = unref(getExcludeCachedTabs); |
||||
|
return [...unref(getCachedRoutes).values()].filter((item) => { |
||||
|
const componentType: any = item.component.type || {}; |
||||
|
let componentName = componentType.name; |
||||
|
if (!componentName) { |
||||
|
componentName = item.route.name; |
||||
|
} |
||||
|
return !excludeCachedTabKeys.includes(componentName); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 是否显示 |
||||
|
*/ |
||||
|
const computedShowView = computed(() => unref(computedCachedRoutes).length > 0); |
||||
|
|
||||
|
const computedCurrentRouteKey = computed(() => { |
||||
|
return getTabKey(route); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<template v-if="computedShowView"> |
||||
|
<template v-for="item in computedCachedRoutes" :key="item.key"> |
||||
|
<Transition |
||||
|
v-if="getEnabledTransition" |
||||
|
appear |
||||
|
mode="out-in" |
||||
|
:name="getTransitionName(item.route)" |
||||
|
> |
||||
|
<component |
||||
|
v-show="item.key === computedCurrentRouteKey" |
||||
|
:is="transformComponent(item.component, item.route)" |
||||
|
/> |
||||
|
</Transition> |
||||
|
<template v-else> |
||||
|
<component |
||||
|
v-show="item.key === computedCurrentRouteKey" |
||||
|
:is="transformComponent(item.component, item.route)" |
||||
|
/> |
||||
|
</template> |
||||
|
</template> |
||||
|
</template> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
Loading…
Reference in new issue