mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-03-14 16:32:00 +08:00
Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -1,17 +1,15 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VNode } from 'vue';
|
||||
import type {
|
||||
RouteLocationNormalizedLoaded,
|
||||
RouteLocationNormalizedLoadedGeneric,
|
||||
} from 'vue-router';
|
||||
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { unref } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
|
||||
import { preferences, usePreferences } from '@vben/preferences';
|
||||
import { usePreferences } from '@vben/preferences';
|
||||
import { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';
|
||||
|
||||
import { transformComponent, useLayoutHook } from '../../hooks';
|
||||
import { IFrameRouterView } from '../../iframe';
|
||||
import { RouteCachedPage, RouteCachedView } from '../../route-cached';
|
||||
|
||||
defineOptions({ name: 'LayoutContent' });
|
||||
|
||||
@@ -21,85 +19,27 @@ const { keepAlive } = usePreferences();
|
||||
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
|
||||
storeToRefs(tabbarStore);
|
||||
|
||||
/**
|
||||
* 是否使用动画
|
||||
*/
|
||||
const getEnabledTransition = computed(() => {
|
||||
const { transition } = preferences;
|
||||
const transitionName = transition.name;
|
||||
return transitionName && transition.enable;
|
||||
});
|
||||
|
||||
// 页面切换动画
|
||||
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;
|
||||
}
|
||||
const { getEnabledTransition, getTransitionName } = useLayoutHook();
|
||||
|
||||
/**
|
||||
* 转换组件,自动添加 name
|
||||
* @param component
|
||||
* 是否显示component
|
||||
* @param route
|
||||
*/
|
||||
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;
|
||||
}
|
||||
const showComponent = (route: RouteLocationNormalizedLoadedGeneric) => {
|
||||
return !route.meta.domCached && unref(renderRouteView);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full">
|
||||
<IFrameRouterView />
|
||||
<RouteCachedView />
|
||||
<RouterView v-slot="{ Component, route }">
|
||||
<RouteCachedPage
|
||||
:component="Component"
|
||||
:route="route"
|
||||
v-if="route.meta.domCached"
|
||||
/>
|
||||
<Transition
|
||||
v-if="getEnabledTransition"
|
||||
:name="getTransitionName(route)"
|
||||
@@ -113,14 +53,14 @@ function transformComponent(
|
||||
>
|
||||
<component
|
||||
:is="transformComponent(Component, route)"
|
||||
v-if="renderRouteView"
|
||||
v-if="showComponent(route)"
|
||||
v-show="!route.meta.iframeSrc"
|
||||
:key="getTabKey(route)"
|
||||
/>
|
||||
</KeepAlive>
|
||||
<component
|
||||
:is="Component"
|
||||
v-else-if="renderRouteView"
|
||||
v-else-if="showComponent(route)"
|
||||
:key="getTabKey(route)"
|
||||
/>
|
||||
</Transition>
|
||||
@@ -132,14 +72,14 @@ function transformComponent(
|
||||
>
|
||||
<component
|
||||
:is="transformComponent(Component, route)"
|
||||
v-if="renderRouteView"
|
||||
v-if="showComponent(route)"
|
||||
v-show="!route.meta.iframeSrc"
|
||||
:key="getTabKey(route)"
|
||||
/>
|
||||
</KeepAlive>
|
||||
<component
|
||||
:is="Component"
|
||||
v-else-if="renderRouteView"
|
||||
v-else-if="showComponent(route)"
|
||||
:key="getTabKey(route)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -234,6 +234,7 @@ const headerSlots = computed(() => {
|
||||
:header-visible="preferences.header.enable"
|
||||
:is-mobile="preferences.app.isMobile"
|
||||
:layout="layout"
|
||||
:sidebar-draggable="preferences.sidebar.draggable"
|
||||
:sidebar-collapse="preferences.sidebar.collapsed"
|
||||
:sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle"
|
||||
:sidebar-enable="sidebarVisible"
|
||||
@@ -267,6 +268,9 @@ const headerSlots = computed(() => {
|
||||
(value: boolean) =>
|
||||
updatePreferences({ sidebar: { extraCollapse: value } })
|
||||
"
|
||||
@update:sidebar-width="
|
||||
(value: number) => updatePreferences({ sidebar: { width: value } })
|
||||
"
|
||||
>
|
||||
<!-- logo -->
|
||||
<template #logo>
|
||||
|
||||
98
packages/effects/layouts/src/hooks/index.ts
Normal file
98
packages/effects/layouts/src/hooks/index.ts
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
2
packages/effects/layouts/src/route-cached/index.ts
Normal file
2
packages/effects/layouts/src/route-cached/index.ts
Normal file
@@ -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>
|
||||
@@ -15,6 +15,7 @@ const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||
const appWatermark = defineModel<boolean>('appWatermark');
|
||||
const appWatermarkContent = defineModel<string>('appWatermarkContent');
|
||||
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
|
||||
const appEnableCopyPreferences = defineModel<boolean>('appEnableCopyPreferences');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,4 +45,7 @@ const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
|
||||
<SwitchItem v-model="appEnableCheckUpdates">
|
||||
{{ $t('preferences.checkUpdates') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="appEnableCopyPreferences">
|
||||
{{ $t('preferences.enableCopyPreferences') }}
|
||||
</SwitchItem>
|
||||
</template>
|
||||
|
||||
@@ -19,6 +19,7 @@ const sidebarCollapsedShowTitle = defineModel<boolean>(
|
||||
const sidebarAutoActivateChild = defineModel<boolean>(
|
||||
'sidebarAutoActivateChild',
|
||||
);
|
||||
const sidebarDraggable = defineModel<boolean>('sidebarDraggable');
|
||||
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
|
||||
const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
|
||||
|
||||
@@ -48,6 +49,9 @@ const handleCheckboxChange = () => {
|
||||
<SwitchItem v-model="sidebarEnable" :disabled="disabled">
|
||||
{{ $t('preferences.sidebar.visible') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="sidebarDraggable" :disabled="!sidebarEnable || disabled">
|
||||
{{ $t('preferences.sidebar.draggable') }}
|
||||
</SwitchItem>
|
||||
<SwitchItem v-model="sidebarCollapsed" :disabled="!sidebarEnable || disabled">
|
||||
{{ $t('preferences.sidebar.collapsed') }}
|
||||
</SwitchItem>
|
||||
|
||||
@@ -70,6 +70,7 @@ const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
|
||||
const appWatermark = defineModel<boolean>('appWatermark');
|
||||
const appWatermarkContent = defineModel<string>('appWatermarkContent');
|
||||
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
|
||||
const appEnableCopyPreferences = defineModel<boolean>('appEnableCopyPreferences');
|
||||
const appEnableStickyPreferencesNavigationBar = defineModel<boolean>(
|
||||
'appEnableStickyPreferencesNavigationBar',
|
||||
);
|
||||
@@ -93,6 +94,7 @@ const themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');
|
||||
|
||||
const sidebarEnable = defineModel<boolean>('sidebarEnable');
|
||||
const sidebarWidth = defineModel<number>('sidebarWidth');
|
||||
const sidebarDraggable = defineModel<boolean>('sidebarDraggable');
|
||||
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
|
||||
const sidebarCollapsedShowTitle = defineModel<boolean>(
|
||||
'sidebarCollapsedShowTitle',
|
||||
@@ -299,6 +301,7 @@ async function handleReset() {
|
||||
<General
|
||||
v-model:app-dynamic-title="appDynamicTitle"
|
||||
v-model:app-enable-check-updates="appEnableCheckUpdates"
|
||||
v-model:app-enable-copy-preferences="appEnableCopyPreferences"
|
||||
v-model:app-locale="appLocale"
|
||||
v-model:app-watermark="appWatermark"
|
||||
v-model:app-watermark-content="appWatermarkContent"
|
||||
@@ -354,6 +357,7 @@ async function handleReset() {
|
||||
<Block :title="$t('preferences.sidebar.title')">
|
||||
<Sidebar
|
||||
v-model:sidebar-auto-activate-child="sidebarAutoActivateChild"
|
||||
v-model:sidebar-draggable="sidebarDraggable"
|
||||
v-model:sidebar-collapsed="sidebarCollapsed"
|
||||
v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
|
||||
v-model:sidebar-enable="sidebarEnable"
|
||||
@@ -465,6 +469,7 @@ async function handleReset() {
|
||||
|
||||
<template #footer>
|
||||
<VbenButton
|
||||
v-if="appEnableCopyPreferences"
|
||||
:disabled="!diffPreference"
|
||||
class="mx-4 w-full"
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user