mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 11:54:00 +08:00
perf(tabs): perf multiple-tabs
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { withInstall } from '../util';
|
||||
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
export const Dropdown = createAsyncComponent(() => import('./src/Dropdown'));
|
||||
import Dropdown from './src/Dropdown';
|
||||
|
||||
withInstall(Dropdown);
|
||||
export * from './src/types';
|
||||
export { Dropdown };
|
||||
|
@@ -243,6 +243,7 @@ export default defineComponent({
|
||||
onOpenChange={handleOpenChange}
|
||||
class={unref(getMenuClass)}
|
||||
onClick={handleMenuClick}
|
||||
subMenuOpenDelay={0.2}
|
||||
{...unref(getInlineCollapseOptions)}
|
||||
>
|
||||
{{
|
||||
|
@@ -6,6 +6,7 @@ import { appStore } from '/@/store/modules/app';
|
||||
|
||||
import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
|
||||
import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { useFullContent } from '/@/hooks/web/useFullContent';
|
||||
|
||||
// Get menu configuration
|
||||
const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting);
|
||||
@@ -78,6 +79,15 @@ const getCalcContentWidth = computed(() => {
|
||||
return `calc(100% - ${unref(width)}px)`;
|
||||
});
|
||||
|
||||
const { getFullContent: fullContent } = useFullContent();
|
||||
|
||||
const getShowSidebar = computed(() => {
|
||||
return (
|
||||
unref(getSplit) ||
|
||||
(unref(getShowMenu) && unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && !unref(fullContent))
|
||||
);
|
||||
});
|
||||
|
||||
// Set menu configuration
|
||||
function setMenuSetting(menuSetting: Partial<MenuSetting>): void {
|
||||
appStore.commitProjectConfigState({ menuSetting });
|
||||
@@ -119,5 +129,6 @@ export function useMenuSetting() {
|
||||
getMenuHidden,
|
||||
getIsTopMenu,
|
||||
getMenuBgColor,
|
||||
getShowSidebar,
|
||||
};
|
||||
}
|
||||
|
@@ -1,42 +0,0 @@
|
||||
import type { FunctionalComponent } from 'vue';
|
||||
|
||||
import { defineComponent, unref } from 'vue';
|
||||
|
||||
import {
|
||||
DoubleRightOutlined,
|
||||
DoubleLeftOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
const SiderTrigger: FunctionalComponent = () => {
|
||||
const { getCollapsed } = useMenuSetting();
|
||||
return unref(getCollapsed) ? <DoubleRightOutlined /> : <DoubleLeftOutlined />;
|
||||
};
|
||||
|
||||
const HeaderTrigger: FunctionalComponent<{
|
||||
theme?: string;
|
||||
}> = (props) => {
|
||||
const { toggleCollapsed, getCollapsed } = useMenuSetting();
|
||||
return (
|
||||
<span class={['layout-trigger', props.theme]} onClick={toggleCollapsed}>
|
||||
{unref(getCollapsed) ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutTrigger',
|
||||
props: {
|
||||
sider: propTypes.bool.def(true),
|
||||
theme: propTypes.oneOf(['light', 'dark']),
|
||||
},
|
||||
setup(props) {
|
||||
return () => {
|
||||
return props.sider ? <SiderTrigger /> : <HeaderTrigger theme={props.theme} />;
|
||||
};
|
||||
},
|
||||
});
|
@@ -6,7 +6,7 @@
|
||||
:loading="getPageLoading"
|
||||
background="rgba(240, 242, 245, 0.6)"
|
||||
absolute
|
||||
:class="`${prefixCls}__loading`"
|
||||
:class="`${prefixCls}-loading`"
|
||||
/>
|
||||
</transition>
|
||||
<PageLayout />
|
||||
@@ -53,7 +53,7 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
&-loading {
|
||||
position: absolute;
|
||||
top: 200px;
|
||||
z-index: @page-loading-z-index;
|
||||
|
29
src/layouts/default/feature/index.vue
Normal file
29
src/layouts/default/feature/index.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<LayoutLockPage />
|
||||
<BackTop v-if="getUseOpenBackTop" :target="getTarget" />
|
||||
<SettingDrawer v-if="getShowSettingButton" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
import { BackTop } from 'ant-design-vue';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutFeatures',
|
||||
components: {
|
||||
BackTop,
|
||||
LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')),
|
||||
SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')),
|
||||
},
|
||||
setup() {
|
||||
const { getUseOpenBackTop, getShowSettingButton } = useRootSetting();
|
||||
|
||||
return {
|
||||
getTarget: () => document.body,
|
||||
getUseOpenBackTop,
|
||||
getShowSettingButton,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -1,28 +0,0 @@
|
||||
@normal-color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
@hover-color: rgba(0, 0, 0, 0.85);
|
||||
|
||||
.layout-footer {
|
||||
color: @normal-color;
|
||||
text-align: center;
|
||||
|
||||
&__links {
|
||||
margin-bottom: 8px;
|
||||
|
||||
a {
|
||||
color: @normal-color;
|
||||
|
||||
&:hover {
|
||||
color: @hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
.github {
|
||||
margin: 0 30px;
|
||||
|
||||
&:hover {
|
||||
color: @hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
import './index.less';
|
||||
|
||||
import { defineComponent } from 'vue';
|
||||
import { Layout } from 'ant-design-vue';
|
||||
|
||||
import { GithubFilled } from '@ant-design/icons-vue';
|
||||
|
||||
import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting';
|
||||
import { openWindow } from '/@/utils';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutContent',
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
return () => {
|
||||
return (
|
||||
<Layout.Footer class="layout-footer">
|
||||
{() => (
|
||||
<>
|
||||
<div class="layout-footer__links">
|
||||
<a onClick={() => openWindow(SITE_URL)}>{t('layout.footer.onlinePreview')}</a>
|
||||
<GithubFilled onClick={() => openWindow(GITHUB_URL)} class="github" />
|
||||
<a onClick={() => openWindow(DOC_URL)}>{t('layout.footer.onlineDocument')}</a>
|
||||
</div>
|
||||
<div>Copyright ©2020 Vben Admin</div>
|
||||
</>
|
||||
)}
|
||||
</Layout.Footer>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
74
src/layouts/default/footer/index.vue
Normal file
74
src/layouts/default/footer/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<Footer :class="prefixCls" v-if="getShowLayoutFooter">
|
||||
<div :class="`${prefixCls}__links`">
|
||||
<a @click="openWindow(SITE_URL)">{{ t('layout.footer.onlinePreview') }}</a>
|
||||
<GithubFilled @click="openWindow(GITHUB_URL)" :class="`${prefixCls}__github`" />
|
||||
<a @click="openWindow(DOC_URL)">{{ t('layout.footer.onlineDocument') }}</a>
|
||||
</div>
|
||||
<div>Copyright ©2020 Vben Admin</div>
|
||||
</Footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, unref } from 'vue';
|
||||
import { Layout } from 'ant-design-vue';
|
||||
|
||||
import { GithubFilled } from '@ant-design/icons-vue';
|
||||
|
||||
import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting';
|
||||
import { openWindow } from '/@/utils';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutFooter',
|
||||
components: { Footer: Layout.Footer, GithubFilled },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const { getShowFooter } = useRootSetting();
|
||||
const { currentRoute } = useRouter();
|
||||
const { prefixCls } = useDesign('layout-footer');
|
||||
|
||||
const getShowLayoutFooter = computed(() => {
|
||||
return unref(getShowFooter) && !unref(currentRoute).meta?.hiddenFooter;
|
||||
});
|
||||
return { getShowLayoutFooter, prefixCls, t, DOC_URL, GITHUB_URL, SITE_URL, openWindow };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import (reference) '../../../design/index.less';
|
||||
@prefix-cls: ~'@{namespace}-layout-footer';
|
||||
|
||||
@normal-color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
@hover-color: rgba(0, 0, 0, 0.85);
|
||||
|
||||
.@{prefix-cls} {
|
||||
color: @normal-color;
|
||||
text-align: center;
|
||||
|
||||
&__links {
|
||||
margin-bottom: 8px;
|
||||
|
||||
a {
|
||||
color: @normal-color;
|
||||
|
||||
&:hover {
|
||||
color: @hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__github {
|
||||
margin: 0 30px;
|
||||
|
||||
&:hover {
|
||||
color: @hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -19,7 +19,7 @@ import UserDropdown from './UserDropdown';
|
||||
import LayoutMenu from '../menu';
|
||||
import LayoutBreadcrumb from './LayoutBreadcrumb.vue';
|
||||
import LockAction from './actions/LockAction';
|
||||
import LayoutTrigger from '../LayoutTrigger';
|
||||
import LayoutTrigger from '../trigger/index.vue';
|
||||
import NoticeAction from './notice/NoticeActionItem.vue';
|
||||
import {
|
||||
RedoOutlined,
|
||||
|
@@ -3,7 +3,7 @@ import './LayoutMultipleHeader.less';
|
||||
import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue';
|
||||
|
||||
import LayoutHeader from './LayoutHeader';
|
||||
import MultipleTabs from '../multitabs/index';
|
||||
import MultipleTabs from '../tabs/index.vue';
|
||||
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
|
@@ -1,4 +1,5 @@
|
||||
@import (reference) '../../../design/index.less';
|
||||
@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger';
|
||||
|
||||
.layout-header {
|
||||
display: flex;
|
||||
@@ -24,7 +25,7 @@
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
|
||||
.layout-trigger {
|
||||
.@{header-trigger-prefix-cls} {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 1px 10px 0 16px;
|
||||
|
@@ -1,32 +1,28 @@
|
||||
import './index.less';
|
||||
|
||||
import { defineComponent, unref, computed, ref } from 'vue';
|
||||
import { Layout, BackTop } from 'ant-design-vue';
|
||||
import LayoutHeader from './header/LayoutHeader';
|
||||
import { defineComponent, unref, ref } from 'vue';
|
||||
import { Layout } from 'ant-design-vue';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
import LayoutHeader from './header/LayoutHeader';
|
||||
import LayoutContent from './content/index.vue';
|
||||
import LayoutFooter from './footer';
|
||||
import LayoutLockPage from '/@/views/sys/lock/index.vue';
|
||||
import LayoutSideBar from './sider';
|
||||
import SettingBtn from './setting/index.vue';
|
||||
import LayoutMultipleHeader from './header/LayoutMultipleHeader';
|
||||
|
||||
import { MenuModeEnum } from '/@/enums/menuEnum';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useFullContent } from '/@/hooks/web/useFullContent';
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { createLayoutContext } from './useLayoutContext';
|
||||
|
||||
import { registerGlobComp } from '/@/components/registerGlobComp';
|
||||
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
|
||||
import { isMobile } from '/@/utils/is';
|
||||
|
||||
const LayoutFeatures = createAsyncComponent(() => import('/@/layouts/default/feature/index.vue'));
|
||||
const LayoutFooter = createAsyncComponent(() => import('/@/layouts/default/footer/index.vue'));
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayout',
|
||||
setup() {
|
||||
const { currentRoute } = useRouter();
|
||||
const headerRef = ref<ComponentRef>(null);
|
||||
const isMobileRef = ref(false);
|
||||
|
||||
@@ -43,56 +39,27 @@ export default defineComponent({
|
||||
|
||||
const { getShowFullHeaderRef } = useHeaderSetting();
|
||||
|
||||
const { getUseOpenBackTop, getShowSettingButton, getShowFooter } = useRootSetting();
|
||||
|
||||
const { getShowMenu, getMenuMode, getSplit } = useMenuSetting();
|
||||
|
||||
const { getFullContent } = useFullContent();
|
||||
|
||||
const getShowLayoutFooter = computed(() => {
|
||||
return unref(getShowFooter) && !unref(currentRoute).meta?.hiddenFooter;
|
||||
});
|
||||
|
||||
const showSideBarRef = computed(() => {
|
||||
return (
|
||||
unref(getSplit) ||
|
||||
(unref(getShowMenu) &&
|
||||
unref(getMenuMode) !== MenuModeEnum.HORIZONTAL &&
|
||||
!unref(getFullContent))
|
||||
);
|
||||
});
|
||||
|
||||
function renderFeatures() {
|
||||
return (
|
||||
<>
|
||||
<LayoutLockPage />
|
||||
{/* back top */}
|
||||
{unref(getUseOpenBackTop) && <BackTop target={() => document.body} />}
|
||||
{/* open setting drawer */}
|
||||
{unref(getShowSettingButton) && <SettingBtn />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
const { getShowSidebar } = useMenuSetting();
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<Layout class="default-layout">
|
||||
{() => (
|
||||
<>
|
||||
{renderFeatures()}
|
||||
<LayoutFeatures />
|
||||
|
||||
{unref(getShowFullHeaderRef) && <LayoutHeader fixed={true} ref={headerRef} />}
|
||||
|
||||
<Layout>
|
||||
{() => (
|
||||
<>
|
||||
{unref(showSideBarRef) && <LayoutSideBar />}
|
||||
{unref(getShowSidebar) && <LayoutSideBar />}
|
||||
<Layout class="default-layout__main">
|
||||
{() => (
|
||||
<>
|
||||
<LayoutMultipleHeader />
|
||||
<LayoutContent />
|
||||
{unref(getShowLayoutFooter) && <LayoutFooter />}
|
||||
<LayoutFooter />
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
|
95
src/layouts/default/index.vue
Normal file
95
src/layouts/default/index.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<Layout :class="prefixCls">
|
||||
<LayoutFeatures />
|
||||
<LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" />
|
||||
<Layout>
|
||||
<LayoutSideBar v-if="getShowSidebar" />
|
||||
<Layout :class="`${prefixCls}__main`">
|
||||
<LayoutMultipleHeader />
|
||||
<LayoutContent />
|
||||
<LayoutFooter />
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { Layout } from 'ant-design-vue';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
import LayoutHeader from './header/LayoutHeader';
|
||||
import LayoutContent from './content/index.vue';
|
||||
import LayoutSideBar from './sider';
|
||||
import LayoutMultipleHeader from './header/LayoutMultipleHeader';
|
||||
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { createLayoutContext } from './useLayoutContext';
|
||||
|
||||
import { registerGlobComp } from '/@/components/registerGlobComp';
|
||||
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
|
||||
import { isMobile } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayout',
|
||||
components: {
|
||||
LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')),
|
||||
LayoutFooter: createAsyncComponent(() => import('/@/layouts/default/footer/index.vue')),
|
||||
LayoutHeader,
|
||||
LayoutContent,
|
||||
LayoutSideBar,
|
||||
LayoutMultipleHeader,
|
||||
Layout,
|
||||
},
|
||||
setup() {
|
||||
const headerRef = ref<ComponentRef>(null);
|
||||
const isMobileRef = ref(false);
|
||||
|
||||
const { prefixCls } = useDesign('default-layout');
|
||||
|
||||
createLayoutContext({ fullHeader: headerRef, isMobile: isMobileRef });
|
||||
|
||||
createBreakpointListen(() => {
|
||||
isMobileRef.value = isMobile();
|
||||
});
|
||||
|
||||
// ! Only register global components here
|
||||
// ! Can reduce the size of the first screen code
|
||||
// default layout It is loaded after login. So it won’t be packaged to the first screen
|
||||
registerGlobComp();
|
||||
|
||||
const { getShowFullHeaderRef } = useHeaderSetting();
|
||||
|
||||
const { getShowSidebar } = useMenuSetting();
|
||||
|
||||
return {
|
||||
getShowFullHeaderRef,
|
||||
getShowSidebar,
|
||||
headerRef,
|
||||
prefixCls,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import (reference) '../../design/index.less';
|
||||
@prefix-cls: ~'@{namespace}-default-layout';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: @content-bg;
|
||||
flex-direction: column;
|
||||
|
||||
> .ant-layout {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
&__main {
|
||||
margin-left: 1px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,78 +0,0 @@
|
||||
import type { PropType } from 'vue';
|
||||
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 './types';
|
||||
|
||||
import { useTabDropdown } from './useTabDropdown';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
const { t: titleT } = useI18n();
|
||||
|
||||
const ExtraContent: FunctionalComponent = () => {
|
||||
return (
|
||||
<span class={`multiple-tabs-content__extra `}>
|
||||
<RightOutlined />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const TabContent: FunctionalComponent<{ tabItem: RouteLocationNormalized; handler: Fn }> = (
|
||||
props
|
||||
) => {
|
||||
const { tabItem: { meta } = {} } = props;
|
||||
|
||||
return (
|
||||
<div class={`multiple-tabs-content__content `} onContextmenu={props.handler(props.tabItem)}>
|
||||
<span class="ml-1">{meta && titleT(meta.title)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TabContent',
|
||||
props: {
|
||||
tabItem: {
|
||||
type: Object as PropType<RouteLocationNormalized>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
type: {
|
||||
type: Number as PropType<TabContentEnum>,
|
||||
default: TabContentEnum.TAB_TYPE,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const {
|
||||
getDropMenuList,
|
||||
handleMenuEvent,
|
||||
handleContextMenu,
|
||||
getTrigger,
|
||||
isTabs,
|
||||
} = useTabDropdown(props as TabContentProps);
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<Dropdown
|
||||
dropMenuList={unref(getDropMenuList)}
|
||||
trigger={unref(getTrigger)}
|
||||
onMenuEvent={handleMenuEvent}
|
||||
>
|
||||
{() => {
|
||||
if (!unref(isTabs)) {
|
||||
return <ExtraContent />;
|
||||
}
|
||||
return <TabContent handler={handleContextMenu} tabItem={props.tabItem} />;
|
||||
}}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
@@ -1,114 +0,0 @@
|
||||
import './index.less';
|
||||
|
||||
import type { TabContentProps } from './types';
|
||||
|
||||
import { defineComponent, watch, computed, unref, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
import TabContent from './TabContent';
|
||||
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
|
||||
import { TabContentEnum } from './types';
|
||||
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
|
||||
import { initAffixTabs, useTabsDrag } from './useMultipleTabs';
|
||||
import { REDIRECT_NAME } from '/@/router/constant';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MultipleTabs',
|
||||
setup() {
|
||||
const activeKeyRef = ref('');
|
||||
|
||||
const affixTextList = initAffixTabs();
|
||||
|
||||
useTabsDrag(affixTextList);
|
||||
|
||||
const go = useGo();
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
const getTabsState = computed(() => tabStore.getTabsState);
|
||||
|
||||
watch(
|
||||
() => tabStore.getLastChangeRouteState?.path,
|
||||
() => {
|
||||
if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) {
|
||||
return;
|
||||
}
|
||||
const lastChangeRoute = unref(tabStore.getLastChangeRouteState);
|
||||
if (!lastChangeRoute || !userStore.getTokenState) return;
|
||||
const { path, fullPath } = lastChangeRoute;
|
||||
const p = fullPath || path;
|
||||
if (activeKeyRef.value !== p) {
|
||||
activeKeyRef.value = p;
|
||||
}
|
||||
tabStore.addTabAction(lastChangeRoute);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(activeKey: any) {
|
||||
activeKeyRef.value = activeKey;
|
||||
go(activeKey, false);
|
||||
}
|
||||
|
||||
// Close the current tab
|
||||
function handleEdit(targetKey: string) {
|
||||
// Added operation to hide, currently only use delete operation
|
||||
tabStore.closeTabByKeyAction(targetKey);
|
||||
}
|
||||
|
||||
function renderQuick() {
|
||||
const tabContentProps: TabContentProps = {
|
||||
tabItem: currentRoute.value,
|
||||
type: TabContentEnum.EXTRA_TYPE,
|
||||
};
|
||||
return <TabContent {...tabContentProps} />;
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
return unref(getTabsState).map((item) => {
|
||||
const key = item.query ? item.fullPath : item.path;
|
||||
const closable = !(item && item.meta && item.meta.affix);
|
||||
|
||||
const slots = {
|
||||
tab: () => <TabContent tabItem={item} />,
|
||||
};
|
||||
return (
|
||||
<Tabs.TabPane key={key} closable={closable}>
|
||||
{slots}
|
||||
</Tabs.TabPane>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
const slots = {
|
||||
default: () => renderTabs(),
|
||||
tabBarExtraContent: () => renderQuick(),
|
||||
};
|
||||
return (
|
||||
<div class="multiple-tabs">
|
||||
<Tabs
|
||||
type="editable-card"
|
||||
size="small"
|
||||
animated={false}
|
||||
hideAdd={true}
|
||||
tabBarGutter={3}
|
||||
activeKey={unref(activeKeyRef)}
|
||||
onChange={handleChange}
|
||||
onEdit={handleEdit}
|
||||
>
|
||||
{slots}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
@@ -13,7 +13,7 @@
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SettingBtn',
|
||||
name: 'SettingButton',
|
||||
components: { SettingOutlined, SettingDrawer },
|
||||
setup() {
|
||||
const [register, { openDrawer }] = useDrawer();
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, unref, onMounted, nextTick, ref } from 'vue';
|
||||
import LayoutTrigger from '/@/layouts/default/LayoutTrigger';
|
||||
import LayoutTrigger from '/@/layouts/default/trigger/index.vue';
|
||||
|
||||
import { TriggerEnum } from '/@/enums/menuEnum';
|
||||
|
||||
|
21
src/layouts/default/tabs/components/QuickButton.vue
Normal file
21
src/layouts/default/tabs/components/QuickButton.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<TabContent :type="TabContentEnum.EXTRA_TYPE" :tabItem="$route" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { TabContentEnum } from '../types';
|
||||
|
||||
import TabContent from './TabContent.vue';
|
||||
export default defineComponent({
|
||||
name: 'QuickButton',
|
||||
components: {
|
||||
TabContent,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
TabContentEnum,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
72
src/layouts/default/tabs/components/TabContent.vue
Normal file
72
src/layouts/default/tabs/components/TabContent.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<Dropdown :dropMenuList="getDropMenuList" :trigger="getTrigger" @menuEvent="handleMenuEvent">
|
||||
<div :class="`${prefixCls}__info`" @contextmenu="handleContext" v-if="isTabs">
|
||||
<span class="ml-1">{{ getTitle }}</span>
|
||||
</div>
|
||||
|
||||
<span :class="`${prefixCls}__extra`" v-else>
|
||||
<RightOutlined />
|
||||
</span>
|
||||
</Dropdown>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { Dropdown } from '/@/components/Dropdown/index';
|
||||
|
||||
import { TabContentProps, TabContentEnum } from '../types';
|
||||
|
||||
import { RightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useTabDropdown } from '../useTabDropdown';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
export default defineComponent({
|
||||
name: 'TabContent',
|
||||
components: { Dropdown, RightOutlined },
|
||||
props: {
|
||||
tabItem: {
|
||||
type: Object as PropType<RouteLocationNormalized>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
type: {
|
||||
type: Number as PropType<TabContentEnum>,
|
||||
default: TabContentEnum.TAB_TYPE,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('multiple-tabs-content');
|
||||
const { t } = useI18n();
|
||||
|
||||
const getTitle = computed(() => {
|
||||
const { tabItem: { meta } = {} } = props;
|
||||
return meta && t(meta.title);
|
||||
});
|
||||
|
||||
const {
|
||||
getDropMenuList,
|
||||
handleMenuEvent,
|
||||
handleContextMenu,
|
||||
getTrigger,
|
||||
isTabs,
|
||||
} = useTabDropdown(props as TabContentProps);
|
||||
|
||||
function handleContext(e: ChangeEvent) {
|
||||
props.tabItem && handleContextMenu(props.tabItem)(e);
|
||||
}
|
||||
return {
|
||||
prefixCls,
|
||||
getDropMenuList,
|
||||
handleMenuEvent,
|
||||
handleContext,
|
||||
getTrigger,
|
||||
isTabs,
|
||||
getTitle,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -1,10 +1,9 @@
|
||||
@import (reference) '../../../design/index.less';
|
||||
@prefix-cls: ~'@{namespace}-multiple-tabs';
|
||||
|
||||
.multiple-tabs {
|
||||
.@{prefix-cls} {
|
||||
z-index: 10;
|
||||
height: @multiple-height + 2;
|
||||
padding: 0 0 2px 0;
|
||||
margin-left: -1px;
|
||||
line-height: @multiple-height + 2;
|
||||
background: @white;
|
||||
box-shadow: 0 1px 2px 0 rgba(29, 35, 41, 0.05);
|
||||
@@ -32,13 +31,33 @@
|
||||
line-height: calc(@multiple-height - 2px);
|
||||
color: @text-color-call-out;
|
||||
background: @white;
|
||||
border: 1px solid darken(@border-color-light, 8%);
|
||||
border: 1px solid darken(@border-color-light, 6%);
|
||||
transition: none;
|
||||
|
||||
&:not(.ant-tabs-tab-active)::before {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: @primary-color;
|
||||
content: '';
|
||||
opacity: 0;
|
||||
transform: translate(-50%, 0) scaleX(0);
|
||||
transform-origin: center;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ant-tabs-close-x {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:not(.ant-tabs-tab-active)::before {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0) scaleX(1);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-close-x {
|
||||
@@ -51,7 +70,7 @@
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
width: 0.75em;
|
||||
width: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +92,7 @@
|
||||
color: @white;
|
||||
background: fade(@primary-color, 100%);
|
||||
border: 0;
|
||||
transition: none;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
@@ -98,7 +118,7 @@
|
||||
}
|
||||
|
||||
.ant-tabs-nav > div:nth-child(1) {
|
||||
padding: 0 10px;
|
||||
padding: 0 6px;
|
||||
|
||||
.ant-tabs-tab {
|
||||
margin-right: 3px !important;
|
||||
@@ -124,9 +144,14 @@
|
||||
.ant-dropdown-trigger {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.multiple-tabs-content {
|
||||
&--hide-close {
|
||||
.ant-tabs-close-x {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
&__extra {
|
||||
display: inline-block;
|
||||
width: @multiple-height;
|
||||
@@ -146,7 +171,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
&__info {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: @multiple-height - 2;
|
||||
@@ -156,4 +181,5 @@
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
123
src/layouts/default/tabs/index.vue
Normal file
123
src/layouts/default/tabs/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div :class="getWrapClass">
|
||||
<Tabs
|
||||
type="editable-card"
|
||||
size="small"
|
||||
:animated="false"
|
||||
:hideAdd="true"
|
||||
:tabBarGutter="3"
|
||||
:activeKey="activeKeyRef"
|
||||
@change="handleChange"
|
||||
@edit="handleEdit"
|
||||
>
|
||||
<template v-for="item in getTabsState" :key="item.query ? item.fullPath : item.path">
|
||||
<TabPane :closable="!(item && item.meta && item.meta.affix)">
|
||||
<template #tab>
|
||||
<TabContent :tabItem="item" />
|
||||
</template>
|
||||
</TabPane>
|
||||
</template>
|
||||
<template #tabBarExtraContent>
|
||||
<QuickButton />
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, computed, unref, ref } from 'vue';
|
||||
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
import TabContent from './components/TabContent.vue';
|
||||
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
|
||||
import { initAffixTabs, useTabsDrag } from './useMultipleTabs';
|
||||
import { REDIRECT_NAME } from '/@/router/constant';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MultipleTabs',
|
||||
components: {
|
||||
QuickButton: createAsyncComponent(() => import('./components/QuickButton.vue')),
|
||||
Tabs,
|
||||
TabPane: Tabs.TabPane,
|
||||
TabContent,
|
||||
},
|
||||
setup() {
|
||||
const affixTextList = initAffixTabs();
|
||||
|
||||
const activeKeyRef = ref('');
|
||||
|
||||
useTabsDrag(affixTextList);
|
||||
const { prefixCls } = useDesign('multiple-tabs');
|
||||
const go = useGo();
|
||||
|
||||
const getTabsState = computed(() => tabStore.getTabsState);
|
||||
|
||||
const unClose = computed(() => {
|
||||
return getTabsState.value.length === 1;
|
||||
});
|
||||
|
||||
const getWrapClass = computed(() => {
|
||||
return [
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}--hide-close`]: unClose,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => tabStore.getLastChangeRouteState?.path,
|
||||
() => {
|
||||
if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) {
|
||||
return;
|
||||
}
|
||||
const lastChangeRoute = unref(tabStore.getLastChangeRouteState);
|
||||
if (!lastChangeRoute || !userStore.getTokenState) return;
|
||||
|
||||
const { path, fullPath } = lastChangeRoute;
|
||||
const p = fullPath || path;
|
||||
|
||||
if (activeKeyRef.value !== p) {
|
||||
activeKeyRef.value = p;
|
||||
}
|
||||
|
||||
tabStore.addTabAction(lastChangeRoute);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function handleChange(activeKey: any) {
|
||||
activeKeyRef.value = activeKey;
|
||||
go(activeKey, false);
|
||||
}
|
||||
|
||||
// Close the current tab
|
||||
function handleEdit(targetKey: string) {
|
||||
// Added operation to hide, currently only use delete operation
|
||||
if (unref(unClose)) return;
|
||||
|
||||
tabStore.closeTabByKeyAction(targetKey);
|
||||
}
|
||||
return {
|
||||
prefixCls,
|
||||
unClose,
|
||||
getWrapClass,
|
||||
handleEdit,
|
||||
handleChange,
|
||||
activeKeyRef,
|
||||
getTabsState,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import './index.less';
|
||||
</style>
|
@@ -2,6 +2,7 @@ import Sortable from 'sortablejs';
|
||||
import { toRaw, ref, nextTick, onMounted } from 'vue';
|
||||
import { RouteLocationNormalized } from 'vue-router';
|
||||
import { useProjectSetting } from '/@/hooks/setting';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import router from '/@/router';
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
import { isNullAndUnDef } from '/@/utils/is';
|
||||
@@ -48,12 +49,12 @@ export function initAffixTabs(): string[] {
|
||||
export function useTabsDrag(affixTextList: string[]) {
|
||||
const { multiTabsSetting } = useProjectSetting();
|
||||
|
||||
const { prefixCls } = useDesign('multiple-tabs');
|
||||
|
||||
function initSortableTabs() {
|
||||
if (!multiTabsSetting.canDrag) return;
|
||||
nextTick(() => {
|
||||
const el = document.querySelectorAll(
|
||||
'.multiple-tabs .ant-tabs-nav > div'
|
||||
)?.[0] as HTMLElement;
|
||||
const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement;
|
||||
|
||||
if (!el) return;
|
||||
Sortable.create(el, {
|
25
src/layouts/default/trigger/HeaderTrigger.vue
Normal file
25
src/layouts/default/trigger/HeaderTrigger.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<span :class="[prefixCls, theme]" @click="toggleCollapsed">
|
||||
<MenuUnfoldOutlined v-if="getCollapsed" /> <MenuFoldOutlined v-else />
|
||||
</span>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SiderTrigger',
|
||||
components: { MenuUnfoldOutlined, MenuFoldOutlined },
|
||||
props: {
|
||||
theme: propTypes.oneOf(['light', 'dark']),
|
||||
},
|
||||
setup() {
|
||||
const { getCollapsed, toggleCollapsed } = useMenuSetting();
|
||||
const { prefixCls } = useDesign('layout-header-trigger');
|
||||
return { getCollapsed, toggleCollapsed, prefixCls };
|
||||
},
|
||||
});
|
||||
</script>
|
18
src/layouts/default/trigger/SiderTrigger.vue
Normal file
18
src/layouts/default/trigger/SiderTrigger.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<DoubleRightOutlined v-if="getCollapsed" />
|
||||
<DoubleLeftOutlined v-else />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { DoubleRightOutlined, DoubleLeftOutlined } from '@ant-design/icons-vue';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SiderTrigger',
|
||||
components: { DoubleRightOutlined, DoubleLeftOutlined },
|
||||
setup() {
|
||||
const { getCollapsed } = useMenuSetting();
|
||||
return { getCollapsed };
|
||||
},
|
||||
});
|
||||
</script>
|
21
src/layouts/default/trigger/index.vue
Normal file
21
src/layouts/default/trigger/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<SiderTrigger v-if="sider" />
|
||||
<HeaderTrigger v-else :theme="theme" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LayoutTrigger',
|
||||
components: {
|
||||
SiderTrigger: createAsyncComponent(() => import('./SiderTrigger.vue')),
|
||||
HeaderTrigger: createAsyncComponent(() => import('./HeaderTrigger.vue'), { loading: true }),
|
||||
},
|
||||
props: {
|
||||
sider: propTypes.bool.def(true),
|
||||
theme: propTypes.oneOf(['light', 'dark']),
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="frame in getFramePages" :key="frame.path">
|
||||
<FramePage
|
||||
v-if="frame.meta.frameSrc && hasRenderFrame(frame.name)"
|
||||
@@ -6,6 +7,7 @@
|
||||
:frameSrc="frame.meta.frameSrc"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import type { AppRouteRecordRaw } from '/@/router/types';
|
||||
|
||||
import { computed, toRaw, unref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import router from '/@/router';
|
||||
|
||||
import { tabStore } from '/@/store/modules/tab';
|
||||
|
||||
@@ -10,8 +8,10 @@ import { unique } from '/@/utils';
|
||||
|
||||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
|
||||
import router from '/@/router';
|
||||
|
||||
export function useFrameKeepAlive() {
|
||||
const { currentRoute } = useRouter();
|
||||
const { currentRoute } = router;
|
||||
const { getShowMultipleTab } = useMultipleTabSetting();
|
||||
|
||||
const getFramePages = computed(() => {
|
||||
|
@@ -10,12 +10,14 @@ import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting';
|
||||
import { useCache } from './useCache';
|
||||
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
|
||||
// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
interface DefaultContext {
|
||||
Component: FunctionalComponent & { type: { [key: string]: any } };
|
||||
route: RouteLocation;
|
||||
}
|
||||
|
||||
// const FrameLayout=createAsyncComponent(()=>'/@/layouts/iframe/index.vue')
|
||||
export default defineComponent({
|
||||
name: 'PageLayout',
|
||||
setup() {
|
||||
|
@@ -32,7 +32,6 @@ export function useCache(isPage: boolean) {
|
||||
|
||||
if (isPage) {
|
||||
// page Layout
|
||||
// not parent layout
|
||||
return cached.get(PAGE_LAYOUT_KEY) || [];
|
||||
}
|
||||
const cacheSet = new Set<string>();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
redo: 'Refresh',
|
||||
close: 'Close',
|
||||
redo: 'Refresh current',
|
||||
close: 'Close current',
|
||||
closeLeft: 'Close Left',
|
||||
closeRight: 'Close Right',
|
||||
closeOther: 'Close Other',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
redo: '刷新',
|
||||
close: '关闭',
|
||||
redo: '刷新当前',
|
||||
close: '关闭当前',
|
||||
closeLeft: '关闭左侧',
|
||||
closeRight: '关闭右侧',
|
||||
closeOther: '关闭其他',
|
||||
|
@@ -6,7 +6,7 @@ const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception');
|
||||
/**
|
||||
* @description: default layout
|
||||
*/
|
||||
export const LAYOUT = () => import('/@/layouts/default/index');
|
||||
export const LAYOUT = () => import('/@/layouts/default/index.vue');
|
||||
|
||||
/**
|
||||
* @description: page-layout
|
||||
|
Reference in New Issue
Block a user