refactor: refactor route

This commit is contained in:
vben
2020-12-03 21:49:32 +08:00
parent 7bfe5f753d
commit c303ec1a23
84 changed files with 1575 additions and 1532 deletions

View File

@@ -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>
);
};

View File

@@ -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>
);
},
});

View 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>

View File

@@ -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';

View File

@@ -1,5 +1,6 @@
.multiple-tab-header {
flex: 0 0 auto;
margin-left: -1px;
&.fixed {
position: fixed;

View File

@@ -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);
}
}
}

View File

@@ -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>
);
};

View File

@@ -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,
};
}

View File

@@ -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(),

View 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,
}

View File

@@ -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();
});
}

View File

@@ -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 };
}