mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-28 05:39:34 +08:00
refactor: refactor route
This commit is contained in:
@@ -3,11 +3,9 @@ import './index.less';
|
||||
import { defineComponent, unref } from 'vue';
|
||||
import { Loading } from '/@/components/Loading';
|
||||
|
||||
import { RouterView } from 'vue-router';
|
||||
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
|
||||
|
||||
import PageLayout from '/@/layouts/page/index.vue';
|
||||
export default defineComponent({
|
||||
name: 'LayoutContent',
|
||||
setup() {
|
||||
@@ -20,7 +18,7 @@ export default defineComponent({
|
||||
{unref(getOpenPageLoading) && (
|
||||
<Loading loading={unref(getPageLoading)} absolute class="layout-content__loading" />
|
||||
)}
|
||||
<RouterView />
|
||||
<PageLayout />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -1,128 +0,0 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { RouteLocationMatched } from 'vue-router';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { defineComponent, TransitionGroup, unref, watch, ref } from 'vue';
|
||||
import Icon from '/@/components/Icon';
|
||||
|
||||
import { Breadcrumb, BreadcrumbItem } from '/@/components/Breadcrumb';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { isBoolean } from '/@/utils/is';
|
||||
import { compile } from 'path-to-regexp';
|
||||
|
||||
import router from '/@/router';
|
||||
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicBreadcrumb',
|
||||
props: {
|
||||
showIcon: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const itemList = ref<AppRouteRecordRaw[]>([]);
|
||||
|
||||
const { currentRoute, push } = useRouter();
|
||||
const { t } = useI18n();
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
if (unref(currentRoute).name === 'Redirect') return;
|
||||
getBreadcrumb();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function getBreadcrumb() {
|
||||
const { matched } = unref(currentRoute);
|
||||
const matchedList = matched.filter((item) => item.meta && item.meta.title).slice(1);
|
||||
const firstItem = matchedList[0];
|
||||
const ret = getHomeRoute(firstItem);
|
||||
if (!isBoolean(ret)) {
|
||||
matchedList.unshift(ret);
|
||||
}
|
||||
itemList.value = ((matchedList as any) as AppRouteRecordRaw[]).filter(
|
||||
(item) => item.meta && item.meta.title && !item.meta.hideBreadcrumb
|
||||
);
|
||||
}
|
||||
|
||||
function getHomeRoute(firstItem: RouteLocationMatched) {
|
||||
if (!firstItem || !firstItem.name) return false;
|
||||
const routes = router.getRoutes();
|
||||
const homeRoute = routes.find((item) => item.path === PageEnum.BASE_HOME);
|
||||
if (!homeRoute) return false;
|
||||
if (homeRoute.name === firstItem.name) return false;
|
||||
return homeRoute;
|
||||
}
|
||||
|
||||
function pathCompile(path: string) {
|
||||
const { params } = unref(currentRoute);
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
}
|
||||
|
||||
function handleItemClick(item: AppRouteRecordRaw) {
|
||||
const { redirect, path, meta } = item;
|
||||
if (meta.disabledRedirect) return;
|
||||
if (redirect) {
|
||||
push(redirect as string);
|
||||
return;
|
||||
}
|
||||
return push(pathCompile(path));
|
||||
}
|
||||
|
||||
function renderItemContent(item: AppRouteRecordRaw) {
|
||||
return (
|
||||
<>
|
||||
{props.showIcon && item.meta.icon && item.meta.icon.trim() !== '' && (
|
||||
<Icon
|
||||
icon={item.meta.icon}
|
||||
class="icon mr-1 "
|
||||
style={{
|
||||
marginBottom: '2px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{t(item.meta.title)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderBreadcrumbItemList() {
|
||||
return unref(itemList).map((item) => {
|
||||
const isLink =
|
||||
(!!item.redirect && !item.meta.disabledRedirect) ||
|
||||
!item.children ||
|
||||
item.children.length === 0;
|
||||
|
||||
return (
|
||||
<BreadcrumbItem
|
||||
key={item.path}
|
||||
isLink={isLink}
|
||||
onClick={handleItemClick.bind(null, item)}
|
||||
>
|
||||
{() => renderItemContent(item as AppRouteRecordRaw)}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function renderBreadcrumbDefault() {
|
||||
return (
|
||||
<TransitionGroup name="breadcrumb">{() => renderBreadcrumbItemList()}</TransitionGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return () => (
|
||||
<Breadcrumb class={['layout-breadcrumb', unref(itemList).length === 0 ? 'hidden' : '']}>
|
||||
{() => renderBreadcrumbDefault()}
|
||||
</Breadcrumb>
|
||||
);
|
||||
},
|
||||
});
|
79
src/layouts/default/header/LayoutBreadcrumb.vue
Normal file
79
src/layouts/default/header/LayoutBreadcrumb.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="layout-breadcrumb">
|
||||
<a-breadcrumb :routes="routes">
|
||||
<template #itemRender="{ route, routes }">
|
||||
<Icon :icon="route.meta.icon" v-if="showIcon && route.meta.icon" />
|
||||
<span v-if="routes.indexOf(route) === routes.length - 1">
|
||||
{{ t(route.meta.title) }}
|
||||
</span>
|
||||
<router-link v-else :to="route.path">
|
||||
{{ t(route.meta.title) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { defineComponent, ref, toRaw, watchEffect } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import type { RouteLocationMatched } from 'vue-router';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { filter } from '/@/utils/helper/treeHelper';
|
||||
import { REDIRECT_NAME } from '/@/router/constant';
|
||||
import Icon from '/@/components/Icon';
|
||||
|
||||
import { HomeOutlined } from '@ant-design/icons-vue';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
export default defineComponent({
|
||||
name: 'LayoutBreadcrumb',
|
||||
components: { HomeOutlined, Icon },
|
||||
props: {
|
||||
showIcon: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const routes = ref<RouteLocationMatched[]>([]);
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
const { t } = useI18n();
|
||||
watchEffect(() => {
|
||||
if (currentRoute.value.name === REDIRECT_NAME) {
|
||||
return;
|
||||
}
|
||||
const matched = currentRoute.value.matched;
|
||||
if (!matched || matched.length === 0) return;
|
||||
|
||||
let breadcrumbList = filter(toRaw(matched), (item) => {
|
||||
if (!item.meta) {
|
||||
return false;
|
||||
}
|
||||
const { title, hideBreadcrumb } = item.meta;
|
||||
if (!title || hideBreadcrumb) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const filterBreadcrumbList = breadcrumbList.filter(
|
||||
(item) => item.path !== PageEnum.BASE_HOME
|
||||
);
|
||||
|
||||
if (filterBreadcrumbList.length === breadcrumbList.length) {
|
||||
filterBreadcrumbList.unshift({
|
||||
path: PageEnum.BASE_HOME,
|
||||
meta: {
|
||||
title: t('layout.header.home'),
|
||||
},
|
||||
});
|
||||
}
|
||||
routes.value = filterBreadcrumbList;
|
||||
});
|
||||
|
||||
return { routes, t };
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -9,7 +9,7 @@ import { Layout, Tooltip, Badge } from 'ant-design-vue';
|
||||
import { AppLogo } from '/@/components/Application';
|
||||
import UserDropdown from './UserDropdown';
|
||||
import LayoutMenu from '../menu';
|
||||
import LayoutBreadcrumb from './LayoutBreadcrumb';
|
||||
import LayoutBreadcrumb from './LayoutBreadcrumb.vue';
|
||||
import LockAction from '../lock/LockAction';
|
||||
import LayoutTrigger from '../LayoutTrigger';
|
||||
import NoticeAction from './notice/NoticeActionItem.vue';
|
||||
|
@@ -1,5 +1,6 @@
|
||||
.multiple-tab-header {
|
||||
flex: 0 0 auto;
|
||||
margin-left: -1px;
|
||||
|
||||
&.fixed {
|
||||
position: fixed;
|
||||
|
@@ -21,11 +21,15 @@
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
|
||||
.layout-trigger {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 1px 10px 0 16px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
|
||||
.anticon {
|
||||
font-size: 17px;
|
||||
@@ -49,12 +53,22 @@
|
||||
}
|
||||
|
||||
.layout-breadcrumb {
|
||||
display: flex;
|
||||
padding: 0 8px;
|
||||
align-items: center;
|
||||
|
||||
.ant-breadcrumb-link {
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -72,6 +86,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
.layout-breadcrumb {
|
||||
.ant-breadcrumb-link {
|
||||
color: @breadcrumb-item-normal-color;
|
||||
|
||||
a {
|
||||
color: @text-color-base;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-breadcrumb-separator {
|
||||
color: @breadcrumb-item-normal-color;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header__logo {
|
||||
height: @header-height;
|
||||
color: @text-color-base;
|
||||
@@ -152,20 +184,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
&__item:last-child .breadcrumb__inner,
|
||||
&__item:last-child &__inner a,
|
||||
&__item:last-child &__inner a:hover,
|
||||
&__item:last-child &__inner:hover {
|
||||
font-weight: 400;
|
||||
.layout-breadcrumb {
|
||||
.ant-breadcrumb-link {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: text;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
&:hover {
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__inner,
|
||||
&__inner.is-link,
|
||||
&__separator {
|
||||
color: @white;
|
||||
.ant-breadcrumb-separator,
|
||||
.anticon {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,20 +1,19 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { defineComponent, unref, computed, FunctionalComponent } from 'vue';
|
||||
|
||||
import { TabItem, tabStore } from '/@/store/modules/tab';
|
||||
import { getScaleAction, TabContentProps } from './data';
|
||||
|
||||
import { Dropdown } from '/@/components/Dropdown/index';
|
||||
|
||||
import { defineComponent, unref, FunctionalComponent } from 'vue';
|
||||
|
||||
import { TabContentProps } from './types';
|
||||
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { TabContentEnum } from './data';
|
||||
import { TabContentEnum } from './types';
|
||||
|
||||
import { useTabDropdown } from './useTabDropdown';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
const { t: titleT } = useI18n();
|
||||
|
||||
const ExtraContent: FunctionalComponent = () => {
|
||||
@@ -25,21 +24,13 @@ const ExtraContent: FunctionalComponent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TabContent: FunctionalComponent<{ tabItem: TabItem }> = (props) => {
|
||||
const TabContent: FunctionalComponent<{ tabItem: RouteLocationNormalized; handler: Fn }> = (
|
||||
props
|
||||
) => {
|
||||
const { tabItem: { meta } = {} } = props;
|
||||
|
||||
function handleContextMenu(e: Event) {
|
||||
if (!props.tabItem) return;
|
||||
const tableItem = props.tabItem;
|
||||
e?.preventDefault();
|
||||
const index = unref(tabStore.getTabsState).findIndex((tab) => tab.path === tableItem.path);
|
||||
|
||||
tabStore.commitCurrentContextMenuIndexState(index);
|
||||
tabStore.commitCurrentContextMenuState(props.tabItem);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={`multiple-tabs-content__content `} onContextmenu={handleContextMenu}>
|
||||
<div class={`multiple-tabs-content__content `} onContextmenu={props.handler(props.tabItem)}>
|
||||
<span class="ml-1">{meta && titleT(meta.title)}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -49,7 +40,7 @@ export default defineComponent({
|
||||
name: 'TabContent',
|
||||
props: {
|
||||
tabItem: {
|
||||
type: Object as PropType<TabItem>,
|
||||
type: Object as PropType<RouteLocationNormalized>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
@@ -59,36 +50,27 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
const { getShowMenu } = useMenuSetting();
|
||||
const { getShowHeader } = useHeaderSetting();
|
||||
const { getShowQuick } = useMultipleTabSetting();
|
||||
|
||||
const getIsScale = computed(() => {
|
||||
return !unref(getShowMenu) && !unref(getShowHeader);
|
||||
});
|
||||
|
||||
const getIsTab = computed(() => {
|
||||
return !unref(getShowQuick) ? true : props.type === TabContentEnum.TAB_TYPE;
|
||||
});
|
||||
|
||||
const { getDropMenuList, handleMenuEvent } = useTabDropdown(props as TabContentProps);
|
||||
const {
|
||||
getDropMenuList,
|
||||
handleMenuEvent,
|
||||
handleContextMenu,
|
||||
getTrigger,
|
||||
isTabs,
|
||||
} = useTabDropdown(props as TabContentProps);
|
||||
|
||||
return () => {
|
||||
const scaleAction = getScaleAction(
|
||||
unref(getIsScale) ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'),
|
||||
unref(getIsScale)
|
||||
);
|
||||
const dropMenuList = unref(getDropMenuList) || [];
|
||||
|
||||
const isTab = unref(getIsTab);
|
||||
return (
|
||||
<Dropdown
|
||||
dropMenuList={!isTab ? [scaleAction, ...dropMenuList] : dropMenuList}
|
||||
trigger={isTab ? ['contextmenu'] : ['click']}
|
||||
dropMenuList={unref(getDropMenuList)}
|
||||
trigger={unref(getTrigger)}
|
||||
onMenuEvent={handleMenuEvent}
|
||||
>
|
||||
{() => (isTab ? <TabContent tabItem={props.tabItem} /> : <ExtraContent />)}
|
||||
{() => {
|
||||
if (!unref(isTabs)) {
|
||||
return <ExtraContent />;
|
||||
}
|
||||
return <TabContent handler={handleContextMenu} tabItem={props.tabItem} />;
|
||||
}}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
@@ -1,90 +0,0 @@
|
||||
import { DropMenu } from '/@/components/Dropdown/index';
|
||||
import { AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { TabItem } from '/@/store/modules/tab';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
export enum TabContentEnum {
|
||||
TAB_TYPE,
|
||||
EXTRA_TYPE,
|
||||
}
|
||||
|
||||
export interface TabContentProps {
|
||||
tabItem: TabItem | AppRouteRecordRaw;
|
||||
type?: TabContentEnum;
|
||||
trigger?: Array<'click' | 'hover' | 'contextmenu'>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 右键:下拉菜单文字
|
||||
*/
|
||||
export enum MenuEventEnum {
|
||||
// 刷新
|
||||
REFRESH_PAGE,
|
||||
// 关闭当前
|
||||
CLOSE_CURRENT,
|
||||
// 关闭左侧
|
||||
CLOSE_LEFT,
|
||||
// 关闭右侧
|
||||
CLOSE_RIGHT,
|
||||
// 关闭其他
|
||||
CLOSE_OTHER,
|
||||
// 关闭所有
|
||||
CLOSE_ALL,
|
||||
// 放大
|
||||
SCALE,
|
||||
}
|
||||
|
||||
export function getActions() {
|
||||
const REFRESH_PAGE: DropMenu = {
|
||||
icon: 'ant-design:reload-outlined',
|
||||
event: MenuEventEnum.REFRESH_PAGE,
|
||||
text: t('layout.multipleTab.redo'),
|
||||
disabled: false,
|
||||
};
|
||||
const CLOSE_CURRENT: DropMenu = {
|
||||
icon: 'ant-design:close-outlined',
|
||||
event: MenuEventEnum.CLOSE_CURRENT,
|
||||
text: t('layout.multipleTab.close'),
|
||||
disabled: false,
|
||||
divider: true,
|
||||
};
|
||||
const CLOSE_LEFT: DropMenu = {
|
||||
icon: 'ant-design:pic-left-outlined',
|
||||
event: MenuEventEnum.CLOSE_LEFT,
|
||||
text: t('layout.multipleTab.closeLeft'),
|
||||
disabled: false,
|
||||
divider: false,
|
||||
};
|
||||
const CLOSE_RIGHT: DropMenu = {
|
||||
icon: 'ant-design:pic-right-outlined',
|
||||
event: MenuEventEnum.CLOSE_RIGHT,
|
||||
text: t('layout.multipleTab.closeRight'),
|
||||
disabled: false,
|
||||
divider: true,
|
||||
};
|
||||
const CLOSE_OTHER: DropMenu = {
|
||||
icon: 'ant-design:pic-center-outlined',
|
||||
event: MenuEventEnum.CLOSE_OTHER,
|
||||
text: t('layout.multipleTab.closeOther'),
|
||||
disabled: false,
|
||||
};
|
||||
const CLOSE_ALL: DropMenu = {
|
||||
icon: 'ant-design:line-outlined',
|
||||
event: MenuEventEnum.CLOSE_ALL,
|
||||
text: t('layout.multipleTab.closeAll'),
|
||||
disabled: false,
|
||||
};
|
||||
return [REFRESH_PAGE, CLOSE_CURRENT, CLOSE_LEFT, CLOSE_RIGHT, CLOSE_OTHER, CLOSE_ALL];
|
||||
}
|
||||
|
||||
export function getScaleAction(text: string, isZoom = false) {
|
||||
return {
|
||||
icon: isZoom ? 'codicon:screen-normal' : 'codicon:screen-full',
|
||||
event: MenuEventEnum.SCALE,
|
||||
text: text,
|
||||
disabled: false,
|
||||
};
|
||||
}
|
@@ -1,12 +1,8 @@
|
||||
import './index.less';
|
||||
|
||||
import type { TabContentProps } from './data';
|
||||
import type { TabItem } from '/@/store/modules/tab';
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
|
||||
import { defineComponent, watch, computed, unref, ref, onMounted, nextTick } from 'vue';
|
||||
import Sortable from 'sortablejs';
|
||||
import type { TabContentProps } from './types';
|
||||
|
||||
import { defineComponent, watch, computed, unref, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
@@ -14,15 +10,12 @@ import TabContent from './TabContent';
|
||||
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
|
||||
import { TabContentEnum } from './data';
|
||||
import { TabContentEnum } from './types';
|
||||
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
|
||||
import { closeTab } from './useTabDropdown';
|
||||
import { initAffixTabs } from './useMultipleTabs';
|
||||
import { isNullAndUnDef } from '/@/utils/is';
|
||||
import { useProjectSetting } from '/@/hooks/setting';
|
||||
import { initAffixTabs, useTabsDrag } from './useMultipleTabs';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MultipleTabs',
|
||||
@@ -31,28 +24,25 @@ export default defineComponent({
|
||||
|
||||
const affixTextList = initAffixTabs();
|
||||
|
||||
const go = useGo();
|
||||
useTabsDrag(affixTextList);
|
||||
|
||||
const { multiTabsSetting } = useProjectSetting();
|
||||
const go = useGo();
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
const getTabsState = computed(() => tabStore.getTabsState);
|
||||
|
||||
// If you monitor routing changes, tab switching will be stuck. So setting this method
|
||||
watch(
|
||||
() => tabStore.getLastChangeRouteState,
|
||||
() => tabStore.getLastChangeRouteState?.path,
|
||||
() => {
|
||||
const lastChangeRoute = unref(tabStore.getLastChangeRouteState);
|
||||
|
||||
if (!lastChangeRoute || !userStore.getTokenState) return;
|
||||
|
||||
const { path, fullPath } = lastChangeRoute as AppRouteRecordRaw;
|
||||
const { path, fullPath } = lastChangeRoute;
|
||||
const p = fullPath || path;
|
||||
if (activeKeyRef.value !== p) {
|
||||
activeKeyRef.value = p;
|
||||
}
|
||||
tabStore.commitAddTab(lastChangeRoute);
|
||||
tabStore.addTabAction(lastChangeRoute);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@@ -67,22 +57,19 @@ export default defineComponent({
|
||||
// Close the current tab
|
||||
function handleEdit(targetKey: string) {
|
||||
// Added operation to hide, currently only use delete operation
|
||||
const index = unref(getTabsState).findIndex(
|
||||
(item) => (item.fullPath || item.path) === targetKey
|
||||
);
|
||||
index !== -1 && closeTab(unref(getTabsState)[index]);
|
||||
tabStore.closeTabByKeyAction(targetKey);
|
||||
}
|
||||
|
||||
function renderQuick() {
|
||||
const tabContentProps: TabContentProps = {
|
||||
tabItem: (currentRoute as unknown) as AppRouteRecordRaw,
|
||||
tabItem: currentRoute.value,
|
||||
type: TabContentEnum.EXTRA_TYPE,
|
||||
};
|
||||
return <TabContent {...(tabContentProps as any)} />;
|
||||
return <TabContent {...tabContentProps} />;
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
return unref(getTabsState).map((item: TabItem) => {
|
||||
return unref(getTabsState).map((item) => {
|
||||
const key = item.query ? item.fullPath : item.path;
|
||||
const closable = !(item && item.meta && item.meta.affix);
|
||||
|
||||
@@ -97,40 +84,6 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
function initSortableTabs() {
|
||||
if (!multiTabsSetting.canDrag) return;
|
||||
nextTick(() => {
|
||||
const el = document.querySelectorAll(
|
||||
'.multiple-tabs .ant-tabs-nav > div'
|
||||
)?.[0] as HTMLElement;
|
||||
|
||||
if (!el) return;
|
||||
Sortable.create(el, {
|
||||
animation: 500,
|
||||
delay: 400,
|
||||
delayOnTouchOnly: true,
|
||||
filter: (e: ChangeEvent) => {
|
||||
const text = e?.target?.innerText;
|
||||
if (!text) return false;
|
||||
return affixTextList.includes(text);
|
||||
},
|
||||
onEnd: (evt) => {
|
||||
const { oldIndex, newIndex } = evt;
|
||||
|
||||
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
tabStore.commitSortTabs({ oldIndex, newIndex });
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initSortableTabs();
|
||||
});
|
||||
|
||||
return () => {
|
||||
const slots = {
|
||||
default: () => renderTabs(),
|
||||
|
35
src/layouts/default/multitabs/types.ts
Normal file
35
src/layouts/default/multitabs/types.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { DropMenu } from '/@/components/Dropdown/index';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
export enum TabContentEnum {
|
||||
TAB_TYPE,
|
||||
EXTRA_TYPE,
|
||||
}
|
||||
|
||||
export type { DropMenu };
|
||||
|
||||
export interface TabContentProps {
|
||||
tabItem: RouteLocationNormalized;
|
||||
type?: TabContentEnum;
|
||||
trigger?: ('click' | 'hover' | 'contextmenu')[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 右键:下拉菜单文字
|
||||
*/
|
||||
export enum MenuEventEnum {
|
||||
// 刷新
|
||||
REFRESH_PAGE,
|
||||
// 关闭当前
|
||||
CLOSE_CURRENT,
|
||||
// 关闭左侧
|
||||
CLOSE_LEFT,
|
||||
// 关闭右侧
|
||||
CLOSE_RIGHT,
|
||||
// 关闭其他
|
||||
CLOSE_OTHER,
|
||||
// 关闭所有
|
||||
CLOSE_ALL,
|
||||
// 放大
|
||||
SCALE,
|
||||
}
|
@@ -1,19 +1,22 @@
|
||||
import { toRaw, ref } from 'vue';
|
||||
import Sortable from 'sortablejs';
|
||||
import { toRaw, ref, nextTick, onMounted } from 'vue';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
import { useProjectSetting } from '/@/hooks/setting';
|
||||
import router from '/@/router';
|
||||
import { AppRouteRecordRaw } from '/@/router/types';
|
||||
import { TabItem, tabStore } from '/@/store/modules/tab';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { isNullAndUnDef } from '/@/utils/is';
|
||||
|
||||
export function initAffixTabs() {
|
||||
const affixList = ref<TabItem[]>([]);
|
||||
export function initAffixTabs(): string[] {
|
||||
const affixList = ref<RouteLocationNormalized[]>([]);
|
||||
/**
|
||||
* @description: Filter all fixed routes
|
||||
*/
|
||||
function filterAffixTabs(routes: AppRouteRecordRaw[]) {
|
||||
const tabs: TabItem[] = [];
|
||||
function filterAffixTabs(routes: RouteLocationNormalized[]) {
|
||||
const tabs: RouteLocationNormalized[] = [];
|
||||
routes &&
|
||||
routes.forEach((route) => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
tabs.push(toRaw(route) as TabItem);
|
||||
tabs.push(toRaw(route));
|
||||
}
|
||||
});
|
||||
return tabs;
|
||||
@@ -23,10 +26,14 @@ export function initAffixTabs() {
|
||||
* @description: Set fixed tabs
|
||||
*/
|
||||
function addAffixTabs(): void {
|
||||
const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as AppRouteRecordRaw[]);
|
||||
const affixTabs = filterAffixTabs((router.getRoutes() as unknown) as RouteLocationNormalized[]);
|
||||
affixList.value = affixTabs;
|
||||
for (const tab of affixTabs) {
|
||||
tabStore.commitAddTab(tab);
|
||||
tabStore.addTabAction(({
|
||||
meta: tab.meta,
|
||||
name: tab.name,
|
||||
path: tab.path,
|
||||
} as unknown) as RouteLocationNormalized);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,3 +44,41 @@ export function initAffixTabs() {
|
||||
}
|
||||
return affixList.value.map((item) => item.meta?.title).filter(Boolean);
|
||||
}
|
||||
|
||||
export function useTabsDrag(affixTextList: string[]) {
|
||||
const { multiTabsSetting } = useProjectSetting();
|
||||
|
||||
function initSortableTabs() {
|
||||
if (!multiTabsSetting.canDrag) return;
|
||||
nextTick(() => {
|
||||
const el = document.querySelectorAll(
|
||||
'.multiple-tabs .ant-tabs-nav > div'
|
||||
)?.[0] as HTMLElement;
|
||||
|
||||
if (!el) return;
|
||||
Sortable.create(el, {
|
||||
animation: 500,
|
||||
delay: 400,
|
||||
delayOnTouchOnly: true,
|
||||
filter: (e: ChangeEvent) => {
|
||||
const text = e?.target?.innerText;
|
||||
if (!text) return false;
|
||||
return affixTextList.includes(text);
|
||||
},
|
||||
onEnd: (evt) => {
|
||||
const { oldIndex, newIndex } = evt;
|
||||
|
||||
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
tabStore.commitSortTabs({ oldIndex, newIndex });
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initSortableTabs();
|
||||
});
|
||||
}
|
||||
|
@@ -1,168 +1,148 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
import type { TabContentProps } from './data';
|
||||
import type { Ref } from 'vue';
|
||||
import type { TabItem } from '/@/store/modules/tab';
|
||||
import type { TabContentProps } from './types';
|
||||
import type { DropMenu } from '/@/components/Dropdown';
|
||||
|
||||
import { computed, unref } from 'vue';
|
||||
import { TabContentEnum, MenuEventEnum, getActions } from './data';
|
||||
import { computed, unref, reactive } from 'vue';
|
||||
import { TabContentEnum, MenuEventEnum } from './types';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { useGo, useRedo } from '/@/hooks/web/usePage';
|
||||
import router from '/@/router';
|
||||
import { useTabs, isInitUseTab } from '/@/hooks/web/useTabs';
|
||||
import { RouteLocationRaw } from 'vue-router';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
import { useTabs } from '/@/hooks/web/useTabs';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
|
||||
const { initTabFn } = useTabs();
|
||||
const { t } = useI18n();
|
||||
|
||||
export function useTabDropdown(tabContentProps: TabContentProps) {
|
||||
const { currentRoute } = router;
|
||||
const redo = useRedo();
|
||||
const go = useGo();
|
||||
|
||||
const isTabsRef = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE);
|
||||
const getCurrentTab: Ref<TabItem | AppRouteRecordRaw> = computed(() => {
|
||||
return unref(isTabsRef)
|
||||
? tabContentProps.tabItem
|
||||
: ((unref(currentRoute) as any) as AppRouteRecordRaw);
|
||||
const state = reactive({
|
||||
current: null as Nullable<RouteLocationNormalized>,
|
||||
currentIndex: 0,
|
||||
});
|
||||
|
||||
// Current tab list
|
||||
const getTabsState = computed(() => tabStore.getTabsState);
|
||||
const { currentRoute } = router;
|
||||
|
||||
const { getShowMenu, setMenuSetting } = useMenuSetting();
|
||||
const { getShowHeader, setHeaderSetting } = useHeaderSetting();
|
||||
const { getShowQuick } = useMultipleTabSetting();
|
||||
|
||||
const isTabs = computed(() =>
|
||||
!unref(getShowQuick) ? true : tabContentProps.type === TabContentEnum.TAB_TYPE
|
||||
);
|
||||
|
||||
const getCurrentTab = computed(
|
||||
(): RouteLocationNormalized => {
|
||||
return unref(isTabs) ? tabContentProps.tabItem : unref(currentRoute);
|
||||
}
|
||||
);
|
||||
|
||||
const getIsScale = computed(() => {
|
||||
return !unref(getShowMenu) && !unref(getShowHeader);
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: drop-down list
|
||||
*/
|
||||
const getDropMenuList = computed(() => {
|
||||
const dropMenuList = getActions();
|
||||
// Reset to initial state
|
||||
for (const item of dropMenuList) {
|
||||
item.disabled = false;
|
||||
}
|
||||
|
||||
// No tab
|
||||
if (!unref(getTabsState) || unref(getTabsState).length <= 0) {
|
||||
return dropMenuList;
|
||||
} else if (unref(getTabsState).length === 1) {
|
||||
// Only one tab
|
||||
for (const item of dropMenuList) {
|
||||
if (item.event !== MenuEventEnum.REFRESH_PAGE) {
|
||||
item.disabled = true;
|
||||
}
|
||||
}
|
||||
return dropMenuList;
|
||||
}
|
||||
if (!unref(getCurrentTab)) return;
|
||||
const { meta, path } = unref(getCurrentTab);
|
||||
const { meta } = unref(getCurrentTab);
|
||||
const { path } = unref(currentRoute);
|
||||
|
||||
// Refresh button
|
||||
const curItem = tabStore.getCurrentContextMenuState;
|
||||
const index = tabStore.getCurrentContextMenuIndexState;
|
||||
const curItem = state.current;
|
||||
const index = state.currentIndex;
|
||||
const refreshDisabled = curItem ? curItem.path !== path : true;
|
||||
// Close left
|
||||
const closeLeftDisabled = index === 0;
|
||||
|
||||
const disabled = tabStore.getTabsState.length === 1;
|
||||
|
||||
// Close right
|
||||
const closeRightDisabled = index === unref(getTabsState).length - 1;
|
||||
// Currently fixed tab
|
||||
// TODO PERf
|
||||
dropMenuList[0].disabled = unref(isTabsRef) ? refreshDisabled : false;
|
||||
if (meta && meta.affix) {
|
||||
dropMenuList[1].disabled = true;
|
||||
const closeRightDisabled =
|
||||
index === tabStore.getTabsState.length - 1 && tabStore.getLastDragEndIndexState >= 0;
|
||||
const dropMenuList: DropMenu[] = [
|
||||
{
|
||||
icon: 'ant-design:reload-outlined',
|
||||
event: MenuEventEnum.REFRESH_PAGE,
|
||||
text: t('layout.multipleTab.redo'),
|
||||
disabled: refreshDisabled,
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:close-outlined',
|
||||
event: MenuEventEnum.CLOSE_CURRENT,
|
||||
text: t('layout.multipleTab.close'),
|
||||
disabled: meta?.affix || disabled,
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:pic-left-outlined',
|
||||
event: MenuEventEnum.CLOSE_LEFT,
|
||||
text: t('layout.multipleTab.closeLeft'),
|
||||
disabled: closeLeftDisabled,
|
||||
divider: false,
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:pic-right-outlined',
|
||||
event: MenuEventEnum.CLOSE_RIGHT,
|
||||
text: t('layout.multipleTab.closeRight'),
|
||||
disabled: closeRightDisabled,
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:pic-center-outlined',
|
||||
event: MenuEventEnum.CLOSE_OTHER,
|
||||
text: t('layout.multipleTab.closeOther'),
|
||||
disabled: disabled,
|
||||
},
|
||||
{
|
||||
icon: 'ant-design:line-outlined',
|
||||
event: MenuEventEnum.CLOSE_ALL,
|
||||
text: t('layout.multipleTab.closeAll'),
|
||||
disabled: disabled,
|
||||
},
|
||||
];
|
||||
|
||||
if (!unref(isTabs)) {
|
||||
const isScale = unref(getIsScale);
|
||||
dropMenuList.unshift({
|
||||
icon: isScale ? 'codicon:screen-normal' : 'codicon:screen-full',
|
||||
event: MenuEventEnum.SCALE,
|
||||
text: isScale ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'),
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
dropMenuList[2].disabled = closeLeftDisabled;
|
||||
dropMenuList[3].disabled = closeRightDisabled;
|
||||
|
||||
return dropMenuList;
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: Jump to page when closing all pages
|
||||
*/
|
||||
function gotoPage() {
|
||||
const len = unref(getTabsState).length;
|
||||
const { path } = unref(currentRoute);
|
||||
const getTrigger = computed(() => {
|
||||
return unref(isTabs) ? ['contextmenu'] : ['click'];
|
||||
});
|
||||
|
||||
let toPath: PageEnum | string = PageEnum.BASE_HOME;
|
||||
|
||||
if (len > 0) {
|
||||
const page = unref(getTabsState)[len - 1];
|
||||
const p = page.fullPath || page.path;
|
||||
if (p) {
|
||||
toPath = p;
|
||||
}
|
||||
}
|
||||
// Jump to the current page and report an error
|
||||
path !== toPath && go(toPath as PageEnum, true);
|
||||
}
|
||||
|
||||
function isGotoPage(currentTab?: TabItem) {
|
||||
const { path } = unref(currentRoute);
|
||||
const currentPath = (currentTab || unref(getCurrentTab)).path;
|
||||
// Not the current tab, when you close the left/right side, you need to jump to the page
|
||||
if (path !== currentPath) {
|
||||
go(currentPath as PageEnum, true);
|
||||
}
|
||||
}
|
||||
function refreshPage(tabItem?: TabItem) {
|
||||
try {
|
||||
tabStore.commitCloseTabKeepAlive(tabItem || unref(getCurrentTab));
|
||||
} catch (error) {}
|
||||
redo();
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
tabStore.commitCloseAllTab();
|
||||
gotoPage();
|
||||
}
|
||||
|
||||
function closeLeft(tabItem?: TabItem) {
|
||||
tabStore.closeLeftTabAction(tabItem || unref(getCurrentTab));
|
||||
isGotoPage(tabItem);
|
||||
}
|
||||
|
||||
function closeRight(tabItem?: TabItem) {
|
||||
tabStore.closeRightTabAction(tabItem || unref(getCurrentTab));
|
||||
isGotoPage(tabItem);
|
||||
}
|
||||
|
||||
function closeOther(tabItem?: TabItem) {
|
||||
tabStore.closeOtherTabAction(tabItem || unref(getCurrentTab));
|
||||
isGotoPage(tabItem);
|
||||
}
|
||||
|
||||
function closeCurrent(tabItem?: TabItem) {
|
||||
closeTab(unref(tabItem || unref(getCurrentTab)));
|
||||
function handleContextMenu(tabItem: RouteLocationNormalized) {
|
||||
return (e: Event) => {
|
||||
if (!tabItem) return;
|
||||
e?.preventDefault();
|
||||
const index = tabStore.getTabsState.findIndex((tab) => tab.path === tabItem.path);
|
||||
state.current = tabItem;
|
||||
state.currentIndex = index;
|
||||
};
|
||||
}
|
||||
|
||||
function scaleScreen() {
|
||||
const {
|
||||
headerSetting: { show: showHeader },
|
||||
menuSetting: { show: showMenu },
|
||||
} = appStore.getProjectConfig;
|
||||
const isScale = !showHeader && !showMenu;
|
||||
appStore.commitProjectConfigState({
|
||||
headerSetting: { show: isScale },
|
||||
menuSetting: { show: isScale },
|
||||
const isScale = !unref(getShowMenu) && !unref(getShowHeader);
|
||||
setMenuSetting({
|
||||
show: isScale,
|
||||
});
|
||||
}
|
||||
|
||||
if (!isInitUseTab) {
|
||||
initTabFn({
|
||||
refreshPageFn: refreshPage,
|
||||
closeAllFn: closeAll,
|
||||
closeCurrentFn: closeCurrent,
|
||||
closeLeftFn: closeLeft,
|
||||
closeOtherFn: closeOther,
|
||||
closeRightFn: closeRight,
|
||||
setHeaderSetting({
|
||||
show: isScale,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle right click event
|
||||
function handleMenuEvent(menu: DropMenu): void {
|
||||
const { refreshPage, closeAll, closeCurrent, closeLeft, closeOther, closeRight } = useTabs();
|
||||
const { event } = menu;
|
||||
|
||||
switch (event) {
|
||||
case MenuEventEnum.SCALE:
|
||||
scaleScreen();
|
||||
@@ -193,51 +173,5 @@ export function useTabDropdown(tabContentProps: TabContentProps) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { getDropMenuList, handleMenuEvent };
|
||||
}
|
||||
|
||||
export function getObj(tabItem: TabItem) {
|
||||
const { params, path, query } = tabItem;
|
||||
return {
|
||||
params: params || {},
|
||||
path,
|
||||
query: query || {},
|
||||
};
|
||||
}
|
||||
|
||||
export function closeTab(closedTab: TabItem | AppRouteRecordRaw) {
|
||||
const { currentRoute, replace } = router;
|
||||
// Current tab list
|
||||
const getTabsState = computed(() => tabStore.getTabsState);
|
||||
|
||||
const { path } = unref(currentRoute);
|
||||
if (path !== closedTab.path) {
|
||||
// Closed is not the activation tab
|
||||
tabStore.commitCloseTab(closedTab);
|
||||
return;
|
||||
}
|
||||
|
||||
// Closed is activated atb
|
||||
let toObj: RouteLocationRaw = {};
|
||||
|
||||
const index = unref(getTabsState).findIndex((item) => item.path === path);
|
||||
|
||||
// If the current is the leftmost tab
|
||||
if (index === 0) {
|
||||
// There is only one tab, then jump to the homepage, otherwise jump to the right tab
|
||||
if (unref(getTabsState).length === 1) {
|
||||
toObj = PageEnum.BASE_HOME;
|
||||
} else {
|
||||
// Jump to the right tab
|
||||
const page = unref(getTabsState)[index + 1];
|
||||
toObj = getObj(page);
|
||||
}
|
||||
} else {
|
||||
// Close the current tab
|
||||
const page = unref(getTabsState)[index - 1];
|
||||
toObj = getObj(page);
|
||||
}
|
||||
const route = (unref(currentRoute) as unknown) as AppRouteRecordRaw;
|
||||
tabStore.commitCloseTab(route);
|
||||
replace(toObj);
|
||||
return { getDropMenuList, handleMenuEvent, handleContextMenu, getTrigger, isTabs };
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<template v-for="frame in getFramePages" :key="frame.path">
|
||||
<FramePage
|
||||
v-if="frame.meta.frameSrc && hasRenderFrame(frame.path)"
|
||||
v-if="frame.meta.frameSrc && hasRenderFrame(frame.name)"
|
||||
v-show="showIframe(frame)"
|
||||
:frameSrc="frame.meta.frameSrc"
|
||||
/>
|
||||
|
@@ -23,7 +23,7 @@ export function useFrameKeepAlive() {
|
||||
const getOpenTabList = computed((): string[] => {
|
||||
return tabStore.getTabsState.reduce((prev: string[], next) => {
|
||||
if (next.meta && Reflect.has(next.meta, 'frameSrc')) {
|
||||
prev.push(next.path!);
|
||||
prev.push(next.name as string);
|
||||
}
|
||||
return prev;
|
||||
}, []);
|
||||
@@ -45,11 +45,14 @@ export function useFrameKeepAlive() {
|
||||
}
|
||||
|
||||
function showIframe(item: AppRouteRecordRaw) {
|
||||
return item.path === unref(currentRoute).path;
|
||||
return item.name === unref(currentRoute).name;
|
||||
}
|
||||
|
||||
function hasRenderFrame(path: string) {
|
||||
return unref(getShowMultipleTab) ? unref(getOpenTabList).includes(path) : true;
|
||||
function hasRenderFrame(name: string) {
|
||||
if (!unref(getShowMultipleTab)) {
|
||||
return true;
|
||||
}
|
||||
return unref(getOpenTabList).includes(name);
|
||||
}
|
||||
return { hasRenderFrame, getFramePages, showIframe, getAllFramePages };
|
||||
}
|
||||
|
@@ -1,79 +0,0 @@
|
||||
import type { FunctionalComponent } from 'vue';
|
||||
|
||||
import { computed, defineComponent, unref, Transition, KeepAlive } from 'vue';
|
||||
import { RouterView, RouteLocation } from 'vue-router';
|
||||
|
||||
import FrameLayout from '/@/layouts/iframe/index.vue';
|
||||
|
||||
import { useTransition } from './useTransition';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
|
||||
|
||||
interface DefaultContext {
|
||||
Component: FunctionalComponent;
|
||||
route: RouteLocation;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageLayout',
|
||||
setup() {
|
||||
const { getShowMenu } = useMenuSetting();
|
||||
|
||||
const { getOpenKeepAlive, getCanEmbedIFramePage } = useRootSetting();
|
||||
|
||||
const { getBasicTransition, getEnableTransition } = useTransitionSetting();
|
||||
|
||||
const { getMax } = useMultipleTabSetting();
|
||||
|
||||
const transitionEvent = useTransition();
|
||||
|
||||
const openCacheRef = computed(() => unref(getOpenKeepAlive) && unref(getShowMenu));
|
||||
|
||||
const getCacheTabsRef = computed(() => tabStore.getKeepAliveTabsState as string[]);
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div>
|
||||
<RouterView>
|
||||
{{
|
||||
default: ({ Component, route }: DefaultContext) => {
|
||||
// No longer show animations that are already in the tab
|
||||
const cacheTabs = unref(getCacheTabsRef);
|
||||
const isInCache = cacheTabs.includes(route.name as string);
|
||||
const name = isInCache && route.meta.inTab ? 'fade-slide' : null;
|
||||
|
||||
const renderComp = () => <Component key={route.fullPath} />;
|
||||
|
||||
const PageContent = unref(openCacheRef) ? (
|
||||
<KeepAlive max={unref(getMax)} include={cacheTabs}>
|
||||
{renderComp()}
|
||||
</KeepAlive>
|
||||
) : (
|
||||
renderComp()
|
||||
);
|
||||
|
||||
return unref(getEnableTransition) ? (
|
||||
<Transition
|
||||
{...transitionEvent}
|
||||
name={name || route.meta.transitionName || unref(getBasicTransition)}
|
||||
mode="out-in"
|
||||
appear={true}
|
||||
>
|
||||
{() => PageContent}
|
||||
</Transition>
|
||||
) : (
|
||||
PageContent
|
||||
);
|
||||
},
|
||||
}}
|
||||
</RouterView>
|
||||
{unref(getCanEmbedIFramePage) && <FrameLayout />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
21
src/layouts/page/index.vue
Normal file
21
src/layouts/page/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<ParentLayout :isPage="true" />
|
||||
<FrameLayout v-if="getCanEmbedIFramePage" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import FrameLayout from '/@/layouts/iframe/index.vue';
|
||||
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
|
||||
import ParentLayout from '/@/layouts/parent/index.vue';
|
||||
export default defineComponent({
|
||||
components: { ParentLayout, FrameLayout },
|
||||
setup() {
|
||||
const { getCanEmbedIFramePage } = useRootSetting();
|
||||
|
||||
return { getCanEmbedIFramePage };
|
||||
},
|
||||
});
|
||||
</script>
|
73
src/layouts/parent/index.vue
Normal file
73
src/layouts/parent/index.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<!--
|
||||
* @Description: The reason is that tsx will report warnings under multi-level nesting.
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<transition v-bind="transitionEvent" :name="getName(route)" mode="out-in" appear>
|
||||
<keep-alive v-if="openCache" :include="getCaches">
|
||||
<component :max="getMax" :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component v-else :max="getMax" :is="Component" :key="route.fullPath" />
|
||||
</transition>
|
||||
</template>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, unref } from 'vue';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
import { useTransition } from './useTransition';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
|
||||
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
|
||||
import { useCache } from './useCache';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isPage: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { getCaches } = useCache(props.isPage);
|
||||
|
||||
const { getShowMenu } = useMenuSetting();
|
||||
|
||||
const { getOpenKeepAlive } = useRootSetting();
|
||||
|
||||
const { getBasicTransition, getEnableTransition } = useTransitionSetting();
|
||||
|
||||
const { getMax } = useMultipleTabSetting();
|
||||
|
||||
const transitionEvent = useTransition();
|
||||
|
||||
const openCache = computed(() => unref(getOpenKeepAlive) && unref(getShowMenu));
|
||||
|
||||
function getName(route: RouteLocationNormalized) {
|
||||
if (!unref(getEnableTransition)) {
|
||||
return null;
|
||||
}
|
||||
const cacheTabs = unref(getCaches);
|
||||
const isInCache = cacheTabs.includes(route.name as string);
|
||||
const name = isInCache && route.meta.inTab ? 'fade-slide' : null;
|
||||
|
||||
return name || route.meta.transitionName || unref(getBasicTransition);
|
||||
}
|
||||
|
||||
return {
|
||||
getCaches,
|
||||
getMax,
|
||||
transitionEvent,
|
||||
getBasicTransition,
|
||||
getName,
|
||||
openCache,
|
||||
getEnableTransition,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
52
src/layouts/parent/useCache.ts
Normal file
52
src/layouts/parent/useCache.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { computed, ref, unref } from 'vue';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { tryTsxEmit } from '/@/utils/helper/vueHelper';
|
||||
import { tabStore, PAGE_LAYOUT_KEY } from '/@/store/modules/tab';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const ParentLayoutName = 'ParentLayout';
|
||||
export function useCache(isPage: boolean) {
|
||||
const name = ref('');
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
tryTsxEmit((instance: any) => {
|
||||
const routeName = instance.ctx.$options.name;
|
||||
|
||||
if (routeName && ![ParentLayoutName].includes(routeName)) {
|
||||
name.value = routeName;
|
||||
} else {
|
||||
const matched = currentRoute.value.matched;
|
||||
const len = matched.length;
|
||||
if (len < 2) return;
|
||||
name.value = matched[len - 2].name as string;
|
||||
}
|
||||
});
|
||||
const { getOpenKeepAlive } = useRootSetting();
|
||||
|
||||
const getCaches = computed((): string[] => {
|
||||
if (!unref(getOpenKeepAlive)) {
|
||||
return [];
|
||||
}
|
||||
const cached = tabStore.getCachedMapState;
|
||||
|
||||
if (isPage) {
|
||||
// page Layout
|
||||
// not parent layout
|
||||
return cached.get(PAGE_LAYOUT_KEY) || [];
|
||||
}
|
||||
|
||||
const cacheSet = new Set<string>();
|
||||
cacheSet.add(unref(name));
|
||||
|
||||
const list = cached.get(unref(name));
|
||||
if (!list) {
|
||||
return Array.from(cacheSet);
|
||||
}
|
||||
list.forEach((item) => {
|
||||
cacheSet.add(item);
|
||||
});
|
||||
return Array.from(cacheSet);
|
||||
});
|
||||
return { getCaches };
|
||||
}
|
Reference in New Issue
Block a user