Commit 27e50b47479af8eaeb4be020aeb0fcbdb4308295
1 parent
ed41e508
perf(tabs): perf multiple-tabs
Showing
33 changed files
with
575 additions
and
383 deletions
src/components/Dropdown/index.ts
1 | 1 | import { withInstall } from '../util'; |
2 | 2 | |
3 | -import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
4 | -export const Dropdown = createAsyncComponent(() => import('./src/Dropdown')); | |
3 | +import Dropdown from './src/Dropdown'; | |
5 | 4 | |
6 | 5 | withInstall(Dropdown); |
7 | 6 | export * from './src/types'; |
7 | +export { Dropdown }; | ... | ... |
src/components/Menu/src/BasicMenu.tsx
src/hooks/setting/useMenuSetting.ts
... | ... | @@ -6,6 +6,7 @@ import { appStore } from '/@/store/modules/app'; |
6 | 6 | |
7 | 7 | import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; |
8 | 8 | import { MenuModeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
9 | +import { useFullContent } from '/@/hooks/web/useFullContent'; | |
9 | 10 | |
10 | 11 | // Get menu configuration |
11 | 12 | const getMenuSetting = computed(() => appStore.getProjectConfig.menuSetting); |
... | ... | @@ -78,6 +79,15 @@ const getCalcContentWidth = computed(() => { |
78 | 79 | return `calc(100% - ${unref(width)}px)`; |
79 | 80 | }); |
80 | 81 | |
82 | +const { getFullContent: fullContent } = useFullContent(); | |
83 | + | |
84 | +const getShowSidebar = computed(() => { | |
85 | + return ( | |
86 | + unref(getSplit) || | |
87 | + (unref(getShowMenu) && unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && !unref(fullContent)) | |
88 | + ); | |
89 | +}); | |
90 | + | |
81 | 91 | // Set menu configuration |
82 | 92 | function setMenuSetting(menuSetting: Partial<MenuSetting>): void { |
83 | 93 | appStore.commitProjectConfigState({ menuSetting }); |
... | ... | @@ -119,5 +129,6 @@ export function useMenuSetting() { |
119 | 129 | getMenuHidden, |
120 | 130 | getIsTopMenu, |
121 | 131 | getMenuBgColor, |
132 | + getShowSidebar, | |
122 | 133 | }; |
123 | 134 | } | ... | ... |
src/layouts/default/LayoutTrigger.tsx deleted
100644 → 0
1 | -import type { FunctionalComponent } from 'vue'; | |
2 | - | |
3 | -import { defineComponent, unref } from 'vue'; | |
4 | - | |
5 | -import { | |
6 | - DoubleRightOutlined, | |
7 | - DoubleLeftOutlined, | |
8 | - MenuUnfoldOutlined, | |
9 | - MenuFoldOutlined, | |
10 | -} from '@ant-design/icons-vue'; | |
11 | - | |
12 | -import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | |
13 | -import { propTypes } from '/@/utils/propTypes'; | |
14 | - | |
15 | -const SiderTrigger: FunctionalComponent = () => { | |
16 | - const { getCollapsed } = useMenuSetting(); | |
17 | - return unref(getCollapsed) ? <DoubleRightOutlined /> : <DoubleLeftOutlined />; | |
18 | -}; | |
19 | - | |
20 | -const HeaderTrigger: FunctionalComponent<{ | |
21 | - theme?: string; | |
22 | -}> = (props) => { | |
23 | - const { toggleCollapsed, getCollapsed } = useMenuSetting(); | |
24 | - return ( | |
25 | - <span class={['layout-trigger', props.theme]} onClick={toggleCollapsed}> | |
26 | - {unref(getCollapsed) ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} | |
27 | - </span> | |
28 | - ); | |
29 | -}; | |
30 | - | |
31 | -export default defineComponent({ | |
32 | - name: 'LayoutTrigger', | |
33 | - props: { | |
34 | - sider: propTypes.bool.def(true), | |
35 | - theme: propTypes.oneOf(['light', 'dark']), | |
36 | - }, | |
37 | - setup(props) { | |
38 | - return () => { | |
39 | - return props.sider ? <SiderTrigger /> : <HeaderTrigger theme={props.theme} />; | |
40 | - }; | |
41 | - }, | |
42 | -}); |
src/layouts/default/content/index.vue
... | ... | @@ -6,7 +6,7 @@ |
6 | 6 | :loading="getPageLoading" |
7 | 7 | background="rgba(240, 242, 245, 0.6)" |
8 | 8 | absolute |
9 | - :class="`${prefixCls}__loading`" | |
9 | + :class="`${prefixCls}-loading`" | |
10 | 10 | /> |
11 | 11 | </transition> |
12 | 12 | <PageLayout /> |
... | ... | @@ -53,7 +53,7 @@ |
53 | 53 | margin: 0 auto; |
54 | 54 | } |
55 | 55 | |
56 | - &__loading { | |
56 | + &-loading { | |
57 | 57 | position: absolute; |
58 | 58 | top: 200px; |
59 | 59 | z-index: @page-loading-z-index; | ... | ... |
src/layouts/default/feature/index.vue
0 → 100644
1 | +<template> | |
2 | + <LayoutLockPage /> | |
3 | + <BackTop v-if="getUseOpenBackTop" :target="getTarget" /> | |
4 | + <SettingDrawer v-if="getShowSettingButton" /> | |
5 | +</template> | |
6 | +<script lang="ts"> | |
7 | + import { defineComponent } from 'vue'; | |
8 | + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
9 | + import { BackTop } from 'ant-design-vue'; | |
10 | + import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | |
11 | + | |
12 | + export default defineComponent({ | |
13 | + name: 'LayoutFeatures', | |
14 | + components: { | |
15 | + BackTop, | |
16 | + LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')), | |
17 | + SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')), | |
18 | + }, | |
19 | + setup() { | |
20 | + const { getUseOpenBackTop, getShowSettingButton } = useRootSetting(); | |
21 | + | |
22 | + return { | |
23 | + getTarget: () => document.body, | |
24 | + getUseOpenBackTop, | |
25 | + getShowSettingButton, | |
26 | + }; | |
27 | + }, | |
28 | + }); | |
29 | +</script> | ... | ... |
src/layouts/default/footer/index.less deleted
100644 → 0
1 | -@normal-color: rgba(0, 0, 0, 0.45); | |
2 | - | |
3 | -@hover-color: rgba(0, 0, 0, 0.85); | |
4 | - | |
5 | -.layout-footer { | |
6 | - color: @normal-color; | |
7 | - text-align: center; | |
8 | - | |
9 | - &__links { | |
10 | - margin-bottom: 8px; | |
11 | - | |
12 | - a { | |
13 | - color: @normal-color; | |
14 | - | |
15 | - &:hover { | |
16 | - color: @hover-color; | |
17 | - } | |
18 | - } | |
19 | - | |
20 | - .github { | |
21 | - margin: 0 30px; | |
22 | - | |
23 | - &:hover { | |
24 | - color: @hover-color; | |
25 | - } | |
26 | - } | |
27 | - } | |
28 | -} |
src/layouts/default/footer/index.tsx deleted
100644 → 0
1 | -import './index.less'; | |
2 | - | |
3 | -import { defineComponent } from 'vue'; | |
4 | -import { Layout } from 'ant-design-vue'; | |
5 | - | |
6 | -import { GithubFilled } from '@ant-design/icons-vue'; | |
7 | - | |
8 | -import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting'; | |
9 | -import { openWindow } from '/@/utils'; | |
10 | - | |
11 | -import { useI18n } from '/@/hooks/web/useI18n'; | |
12 | - | |
13 | -export default defineComponent({ | |
14 | - name: 'LayoutContent', | |
15 | - setup() { | |
16 | - const { t } = useI18n(); | |
17 | - return () => { | |
18 | - return ( | |
19 | - <Layout.Footer class="layout-footer"> | |
20 | - {() => ( | |
21 | - <> | |
22 | - <div class="layout-footer__links"> | |
23 | - <a onClick={() => openWindow(SITE_URL)}>{t('layout.footer.onlinePreview')}</a> | |
24 | - <GithubFilled onClick={() => openWindow(GITHUB_URL)} class="github" /> | |
25 | - <a onClick={() => openWindow(DOC_URL)}>{t('layout.footer.onlineDocument')}</a> | |
26 | - </div> | |
27 | - <div>Copyright ©2020 Vben Admin</div> | |
28 | - </> | |
29 | - )} | |
30 | - </Layout.Footer> | |
31 | - ); | |
32 | - }; | |
33 | - }, | |
34 | -}); |
src/layouts/default/footer/index.vue
0 → 100644
1 | +<template> | |
2 | + <Footer :class="prefixCls" v-if="getShowLayoutFooter"> | |
3 | + <div :class="`${prefixCls}__links`"> | |
4 | + <a @click="openWindow(SITE_URL)">{{ t('layout.footer.onlinePreview') }}</a> | |
5 | + <GithubFilled @click="openWindow(GITHUB_URL)" :class="`${prefixCls}__github`" /> | |
6 | + <a @click="openWindow(DOC_URL)">{{ t('layout.footer.onlineDocument') }}</a> | |
7 | + </div> | |
8 | + <div>Copyright ©2020 Vben Admin</div> | |
9 | + </Footer> | |
10 | +</template> | |
11 | + | |
12 | +<script lang="ts"> | |
13 | + import { computed, defineComponent, unref } from 'vue'; | |
14 | + import { Layout } from 'ant-design-vue'; | |
15 | + | |
16 | + import { GithubFilled } from '@ant-design/icons-vue'; | |
17 | + | |
18 | + import { DOC_URL, GITHUB_URL, SITE_URL } from '/@/settings/siteSetting'; | |
19 | + import { openWindow } from '/@/utils'; | |
20 | + | |
21 | + import { useI18n } from '/@/hooks/web/useI18n'; | |
22 | + import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | |
23 | + import { useRouter } from 'vue-router'; | |
24 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
25 | + | |
26 | + export default defineComponent({ | |
27 | + name: 'LayoutFooter', | |
28 | + components: { Footer: Layout.Footer, GithubFilled }, | |
29 | + setup() { | |
30 | + const { t } = useI18n(); | |
31 | + const { getShowFooter } = useRootSetting(); | |
32 | + const { currentRoute } = useRouter(); | |
33 | + const { prefixCls } = useDesign('layout-footer'); | |
34 | + | |
35 | + const getShowLayoutFooter = computed(() => { | |
36 | + return unref(getShowFooter) && !unref(currentRoute).meta?.hiddenFooter; | |
37 | + }); | |
38 | + return { getShowLayoutFooter, prefixCls, t, DOC_URL, GITHUB_URL, SITE_URL, openWindow }; | |
39 | + }, | |
40 | + }); | |
41 | +</script> | |
42 | +<style lang="less" scoped> | |
43 | + @import (reference) '../../../design/index.less'; | |
44 | + @prefix-cls: ~'@{namespace}-layout-footer'; | |
45 | + | |
46 | + @normal-color: rgba(0, 0, 0, 0.45); | |
47 | + | |
48 | + @hover-color: rgba(0, 0, 0, 0.85); | |
49 | + | |
50 | + .@{prefix-cls} { | |
51 | + color: @normal-color; | |
52 | + text-align: center; | |
53 | + | |
54 | + &__links { | |
55 | + margin-bottom: 8px; | |
56 | + | |
57 | + a { | |
58 | + color: @normal-color; | |
59 | + | |
60 | + &:hover { | |
61 | + color: @hover-color; | |
62 | + } | |
63 | + } | |
64 | + } | |
65 | + | |
66 | + &__github { | |
67 | + margin: 0 30px; | |
68 | + | |
69 | + &:hover { | |
70 | + color: @hover-color; | |
71 | + } | |
72 | + } | |
73 | + } | |
74 | +</style> | ... | ... |
src/layouts/default/header/LayoutHeader.tsx
... | ... | @@ -19,7 +19,7 @@ import UserDropdown from './UserDropdown'; |
19 | 19 | import LayoutMenu from '../menu'; |
20 | 20 | import LayoutBreadcrumb from './LayoutBreadcrumb.vue'; |
21 | 21 | import LockAction from './actions/LockAction'; |
22 | -import LayoutTrigger from '../LayoutTrigger'; | |
22 | +import LayoutTrigger from '../trigger/index.vue'; | |
23 | 23 | import NoticeAction from './notice/NoticeActionItem.vue'; |
24 | 24 | import { |
25 | 25 | RedoOutlined, | ... | ... |
src/layouts/default/header/LayoutMultipleHeader.tsx
... | ... | @@ -3,7 +3,7 @@ import './LayoutMultipleHeader.less'; |
3 | 3 | import { defineComponent, unref, computed, ref, watch, nextTick, CSSProperties } from 'vue'; |
4 | 4 | |
5 | 5 | import LayoutHeader from './LayoutHeader'; |
6 | -import MultipleTabs from '../multitabs/index'; | |
6 | +import MultipleTabs from '../tabs/index.vue'; | |
7 | 7 | |
8 | 8 | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
9 | 9 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | ... | ... |
src/layouts/default/header/index.less
1 | 1 | @import (reference) '../../../design/index.less'; |
2 | +@header-trigger-prefix-cls: ~'@{namespace}-layout-header-trigger'; | |
2 | 3 | |
3 | 4 | .layout-header { |
4 | 5 | display: flex; |
... | ... | @@ -24,7 +25,7 @@ |
24 | 25 | height: 100%; |
25 | 26 | align-items: center; |
26 | 27 | |
27 | - .layout-trigger { | |
28 | + .@{header-trigger-prefix-cls} { | |
28 | 29 | display: flex; |
29 | 30 | height: 100%; |
30 | 31 | padding: 1px 10px 0 16px; | ... | ... |
src/layouts/default/index.tsx
1 | 1 | import './index.less'; |
2 | 2 | |
3 | -import { defineComponent, unref, computed, ref } from 'vue'; | |
4 | -import { Layout, BackTop } from 'ant-design-vue'; | |
5 | -import LayoutHeader from './header/LayoutHeader'; | |
3 | +import { defineComponent, unref, ref } from 'vue'; | |
4 | +import { Layout } from 'ant-design-vue'; | |
5 | +import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
6 | 6 | |
7 | +import LayoutHeader from './header/LayoutHeader'; | |
7 | 8 | import LayoutContent from './content/index.vue'; |
8 | -import LayoutFooter from './footer'; | |
9 | -import LayoutLockPage from '/@/views/sys/lock/index.vue'; | |
10 | 9 | import LayoutSideBar from './sider'; |
11 | -import SettingBtn from './setting/index.vue'; | |
12 | 10 | import LayoutMultipleHeader from './header/LayoutMultipleHeader'; |
13 | 11 | |
14 | -import { MenuModeEnum } from '/@/enums/menuEnum'; | |
15 | - | |
16 | -import { useRouter } from 'vue-router'; | |
17 | -import { useFullContent } from '/@/hooks/web/useFullContent'; | |
18 | 12 | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
19 | 13 | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
20 | -import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | |
21 | 14 | import { createLayoutContext } from './useLayoutContext'; |
22 | 15 | |
23 | 16 | import { registerGlobComp } from '/@/components/registerGlobComp'; |
24 | 17 | import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'; |
25 | 18 | import { isMobile } from '/@/utils/is'; |
19 | + | |
20 | +const LayoutFeatures = createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')); | |
21 | +const LayoutFooter = createAsyncComponent(() => import('/@/layouts/default/footer/index.vue')); | |
22 | + | |
26 | 23 | export default defineComponent({ |
27 | 24 | name: 'DefaultLayout', |
28 | 25 | setup() { |
29 | - const { currentRoute } = useRouter(); | |
30 | 26 | const headerRef = ref<ComponentRef>(null); |
31 | 27 | const isMobileRef = ref(false); |
32 | 28 | |
... | ... | @@ -43,56 +39,27 @@ export default defineComponent({ |
43 | 39 | |
44 | 40 | const { getShowFullHeaderRef } = useHeaderSetting(); |
45 | 41 | |
46 | - const { getUseOpenBackTop, getShowSettingButton, getShowFooter } = useRootSetting(); | |
47 | - | |
48 | - const { getShowMenu, getMenuMode, getSplit } = useMenuSetting(); | |
49 | - | |
50 | - const { getFullContent } = useFullContent(); | |
51 | - | |
52 | - const getShowLayoutFooter = computed(() => { | |
53 | - return unref(getShowFooter) && !unref(currentRoute).meta?.hiddenFooter; | |
54 | - }); | |
55 | - | |
56 | - const showSideBarRef = computed(() => { | |
57 | - return ( | |
58 | - unref(getSplit) || | |
59 | - (unref(getShowMenu) && | |
60 | - unref(getMenuMode) !== MenuModeEnum.HORIZONTAL && | |
61 | - !unref(getFullContent)) | |
62 | - ); | |
63 | - }); | |
64 | - | |
65 | - function renderFeatures() { | |
66 | - return ( | |
67 | - <> | |
68 | - <LayoutLockPage /> | |
69 | - {/* back top */} | |
70 | - {unref(getUseOpenBackTop) && <BackTop target={() => document.body} />} | |
71 | - {/* open setting drawer */} | |
72 | - {unref(getShowSettingButton) && <SettingBtn />} | |
73 | - </> | |
74 | - ); | |
75 | - } | |
42 | + const { getShowSidebar } = useMenuSetting(); | |
76 | 43 | |
77 | 44 | return () => { |
78 | 45 | return ( |
79 | 46 | <Layout class="default-layout"> |
80 | 47 | {() => ( |
81 | 48 | <> |
82 | - {renderFeatures()} | |
49 | + <LayoutFeatures /> | |
83 | 50 | |
84 | 51 | {unref(getShowFullHeaderRef) && <LayoutHeader fixed={true} ref={headerRef} />} |
85 | 52 | |
86 | 53 | <Layout> |
87 | 54 | {() => ( |
88 | 55 | <> |
89 | - {unref(showSideBarRef) && <LayoutSideBar />} | |
56 | + {unref(getShowSidebar) && <LayoutSideBar />} | |
90 | 57 | <Layout class="default-layout__main"> |
91 | 58 | {() => ( |
92 | 59 | <> |
93 | 60 | <LayoutMultipleHeader /> |
94 | 61 | <LayoutContent /> |
95 | - {unref(getShowLayoutFooter) && <LayoutFooter />} | |
62 | + <LayoutFooter /> | |
96 | 63 | </> |
97 | 64 | )} |
98 | 65 | </Layout> | ... | ... |
src/layouts/default/index.vue
0 → 100644
1 | +<template> | |
2 | + <Layout :class="prefixCls"> | |
3 | + <LayoutFeatures /> | |
4 | + <LayoutHeader fixed ref="headerRef" v-if="getShowFullHeaderRef" /> | |
5 | + <Layout> | |
6 | + <LayoutSideBar v-if="getShowSidebar" /> | |
7 | + <Layout :class="`${prefixCls}__main`"> | |
8 | + <LayoutMultipleHeader /> | |
9 | + <LayoutContent /> | |
10 | + <LayoutFooter /> | |
11 | + </Layout> | |
12 | + </Layout> | |
13 | + </Layout> | |
14 | +</template> | |
15 | + | |
16 | +<script lang="ts"> | |
17 | + import { defineComponent, ref } from 'vue'; | |
18 | + import { Layout } from 'ant-design-vue'; | |
19 | + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
20 | + | |
21 | + import LayoutHeader from './header/LayoutHeader'; | |
22 | + import LayoutContent from './content/index.vue'; | |
23 | + import LayoutSideBar from './sider'; | |
24 | + import LayoutMultipleHeader from './header/LayoutMultipleHeader'; | |
25 | + | |
26 | + import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; | |
27 | + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | |
28 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
29 | + import { createLayoutContext } from './useLayoutContext'; | |
30 | + | |
31 | + import { registerGlobComp } from '/@/components/registerGlobComp'; | |
32 | + import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'; | |
33 | + import { isMobile } from '/@/utils/is'; | |
34 | + | |
35 | + export default defineComponent({ | |
36 | + name: 'DefaultLayout', | |
37 | + components: { | |
38 | + LayoutFeatures: createAsyncComponent(() => import('/@/layouts/default/feature/index.vue')), | |
39 | + LayoutFooter: createAsyncComponent(() => import('/@/layouts/default/footer/index.vue')), | |
40 | + LayoutHeader, | |
41 | + LayoutContent, | |
42 | + LayoutSideBar, | |
43 | + LayoutMultipleHeader, | |
44 | + Layout, | |
45 | + }, | |
46 | + setup() { | |
47 | + const headerRef = ref<ComponentRef>(null); | |
48 | + const isMobileRef = ref(false); | |
49 | + | |
50 | + const { prefixCls } = useDesign('default-layout'); | |
51 | + | |
52 | + createLayoutContext({ fullHeader: headerRef, isMobile: isMobileRef }); | |
53 | + | |
54 | + createBreakpointListen(() => { | |
55 | + isMobileRef.value = isMobile(); | |
56 | + }); | |
57 | + | |
58 | + // ! Only register global components here | |
59 | + // ! Can reduce the size of the first screen code | |
60 | + // default layout It is loaded after login. So it won’t be packaged to the first screen | |
61 | + registerGlobComp(); | |
62 | + | |
63 | + const { getShowFullHeaderRef } = useHeaderSetting(); | |
64 | + | |
65 | + const { getShowSidebar } = useMenuSetting(); | |
66 | + | |
67 | + return { | |
68 | + getShowFullHeaderRef, | |
69 | + getShowSidebar, | |
70 | + headerRef, | |
71 | + prefixCls, | |
72 | + }; | |
73 | + }, | |
74 | + }); | |
75 | +</script> | |
76 | +<style lang="less"> | |
77 | + @import (reference) '../../design/index.less'; | |
78 | + @prefix-cls: ~'@{namespace}-default-layout'; | |
79 | + | |
80 | + .@{prefix-cls} { | |
81 | + display: flex; | |
82 | + width: 100%; | |
83 | + min-height: 100%; | |
84 | + background: @content-bg; | |
85 | + flex-direction: column; | |
86 | + | |
87 | + > .ant-layout { | |
88 | + min-height: 100%; | |
89 | + } | |
90 | + | |
91 | + &__main { | |
92 | + margin-left: 1px; | |
93 | + } | |
94 | + } | |
95 | +</style> | ... | ... |
src/layouts/default/setting/index.vue
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 | import { useDesign } from '/@/hooks/web/useDesign'; |
14 | 14 | |
15 | 15 | export default defineComponent({ |
16 | - name: 'SettingBtn', | |
16 | + name: 'SettingButton', | |
17 | 17 | components: { SettingOutlined, SettingDrawer }, |
18 | 18 | setup() { |
19 | 19 | const [register, { openDrawer }] = useDrawer(); | ... | ... |
src/layouts/default/sider/useLayoutSider.tsx
1 | 1 | import type { Ref } from 'vue'; |
2 | 2 | |
3 | 3 | import { computed, unref, onMounted, nextTick, ref } from 'vue'; |
4 | -import LayoutTrigger from '/@/layouts/default/LayoutTrigger'; | |
4 | +import LayoutTrigger from '/@/layouts/default/trigger/index.vue'; | |
5 | 5 | |
6 | 6 | import { TriggerEnum } from '/@/enums/menuEnum'; |
7 | 7 | ... | ... |
src/layouts/default/tabs/components/QuickButton.vue
0 → 100644
1 | +<template> | |
2 | + <TabContent :type="TabContentEnum.EXTRA_TYPE" :tabItem="$route" /> | |
3 | +</template> | |
4 | +<script lang="ts"> | |
5 | + import { defineComponent } from 'vue'; | |
6 | + | |
7 | + import { TabContentEnum } from '../types'; | |
8 | + | |
9 | + import TabContent from './TabContent.vue'; | |
10 | + export default defineComponent({ | |
11 | + name: 'QuickButton', | |
12 | + components: { | |
13 | + TabContent, | |
14 | + }, | |
15 | + setup() { | |
16 | + return { | |
17 | + TabContentEnum, | |
18 | + }; | |
19 | + }, | |
20 | + }); | |
21 | +</script> | ... | ... |
src/layouts/default/multitabs/TabContent.tsx renamed to src/layouts/default/tabs/components/TabContent.vue
1 | -import type { PropType } from 'vue'; | |
2 | -import { Dropdown } from '/@/components/Dropdown/index'; | |
3 | - | |
4 | -import { defineComponent, unref, FunctionalComponent } from 'vue'; | |
1 | +<template> | |
2 | + <Dropdown :dropMenuList="getDropMenuList" :trigger="getTrigger" @menuEvent="handleMenuEvent"> | |
3 | + <div :class="`${prefixCls}__info`" @contextmenu="handleContext" v-if="isTabs"> | |
4 | + <span class="ml-1">{{ getTitle }}</span> | |
5 | + </div> | |
5 | 6 | |
6 | -import { TabContentProps } from './types'; | |
7 | + <span :class="`${prefixCls}__extra`" v-else> | |
8 | + <RightOutlined /> | |
9 | + </span> | |
10 | + </Dropdown> | |
11 | +</template> | |
12 | +<script lang="ts"> | |
13 | + import type { PropType } from 'vue'; | |
7 | 14 | |
8 | -import { RightOutlined } from '@ant-design/icons-vue'; | |
15 | + import { defineComponent, computed } from 'vue'; | |
16 | + import { Dropdown } from '/@/components/Dropdown/index'; | |
9 | 17 | |
10 | -import { TabContentEnum } from './types'; | |
18 | + import { TabContentProps, TabContentEnum } from '../types'; | |
11 | 19 | |
12 | -import { useTabDropdown } from './useTabDropdown'; | |
13 | -import { useI18n } from '/@/hooks/web/useI18n'; | |
20 | + import { RightOutlined } from '@ant-design/icons-vue'; | |
14 | 21 | |
15 | -import { RouteLocationNormalized } from 'vue-router'; | |
22 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
23 | + import { useTabDropdown } from '../useTabDropdown'; | |
24 | + import { useI18n } from '/@/hooks/web/useI18n'; | |
16 | 25 | |
17 | -const { t: titleT } = useI18n(); | |
26 | + import { RouteLocationNormalized } from 'vue-router'; | |
27 | + export default defineComponent({ | |
28 | + name: 'TabContent', | |
29 | + components: { Dropdown, RightOutlined }, | |
30 | + props: { | |
31 | + tabItem: { | |
32 | + type: Object as PropType<RouteLocationNormalized>, | |
33 | + default: null, | |
34 | + }, | |
18 | 35 | |
19 | -const ExtraContent: FunctionalComponent = () => { | |
20 | - return ( | |
21 | - <span class={`multiple-tabs-content__extra `}> | |
22 | - <RightOutlined /> | |
23 | - </span> | |
24 | - ); | |
25 | -}; | |
36 | + type: { | |
37 | + type: Number as PropType<TabContentEnum>, | |
38 | + default: TabContentEnum.TAB_TYPE, | |
39 | + }, | |
40 | + }, | |
41 | + setup(props) { | |
42 | + const { prefixCls } = useDesign('multiple-tabs-content'); | |
43 | + const { t } = useI18n(); | |
26 | 44 | |
27 | -const TabContent: FunctionalComponent<{ tabItem: RouteLocationNormalized; handler: Fn }> = ( | |
28 | - props | |
29 | -) => { | |
30 | - const { tabItem: { meta } = {} } = props; | |
45 | + const getTitle = computed(() => { | |
46 | + const { tabItem: { meta } = {} } = props; | |
47 | + return meta && t(meta.title); | |
48 | + }); | |
31 | 49 | |
32 | - return ( | |
33 | - <div class={`multiple-tabs-content__content `} onContextmenu={props.handler(props.tabItem)}> | |
34 | - <span class="ml-1">{meta && titleT(meta.title)}</span> | |
35 | - </div> | |
36 | - ); | |
37 | -}; | |
50 | + const { | |
51 | + getDropMenuList, | |
52 | + handleMenuEvent, | |
53 | + handleContextMenu, | |
54 | + getTrigger, | |
55 | + isTabs, | |
56 | + } = useTabDropdown(props as TabContentProps); | |
38 | 57 | |
39 | -export default defineComponent({ | |
40 | - name: 'TabContent', | |
41 | - props: { | |
42 | - tabItem: { | |
43 | - type: Object as PropType<RouteLocationNormalized>, | |
44 | - default: null, | |
58 | + function handleContext(e: ChangeEvent) { | |
59 | + props.tabItem && handleContextMenu(props.tabItem)(e); | |
60 | + } | |
61 | + return { | |
62 | + prefixCls, | |
63 | + getDropMenuList, | |
64 | + handleMenuEvent, | |
65 | + handleContext, | |
66 | + getTrigger, | |
67 | + isTabs, | |
68 | + getTitle, | |
69 | + }; | |
45 | 70 | }, |
46 | - | |
47 | - type: { | |
48 | - type: Number as PropType<TabContentEnum>, | |
49 | - default: TabContentEnum.TAB_TYPE, | |
50 | - }, | |
51 | - }, | |
52 | - setup(props) { | |
53 | - const { | |
54 | - getDropMenuList, | |
55 | - handleMenuEvent, | |
56 | - handleContextMenu, | |
57 | - getTrigger, | |
58 | - isTabs, | |
59 | - } = useTabDropdown(props as TabContentProps); | |
60 | - | |
61 | - return () => { | |
62 | - return ( | |
63 | - <Dropdown | |
64 | - dropMenuList={unref(getDropMenuList)} | |
65 | - trigger={unref(getTrigger)} | |
66 | - onMenuEvent={handleMenuEvent} | |
67 | - > | |
68 | - {() => { | |
69 | - if (!unref(isTabs)) { | |
70 | - return <ExtraContent />; | |
71 | - } | |
72 | - return <TabContent handler={handleContextMenu} tabItem={props.tabItem} />; | |
73 | - }} | |
74 | - </Dropdown> | |
75 | - ); | |
76 | - }; | |
77 | - }, | |
78 | -}); | |
71 | + }); | |
72 | +</script> | ... | ... |
src/layouts/default/multitabs/index.less renamed to src/layouts/default/tabs/index.less
1 | 1 | @import (reference) '../../../design/index.less'; |
2 | +@prefix-cls: ~'@{namespace}-multiple-tabs'; | |
2 | 3 | |
3 | -.multiple-tabs { | |
4 | +.@{prefix-cls} { | |
4 | 5 | z-index: 10; |
5 | 6 | height: @multiple-height + 2; |
6 | - padding: 0 0 2px 0; | |
7 | - margin-left: -1px; | |
8 | 7 | line-height: @multiple-height + 2; |
9 | 8 | background: @white; |
10 | 9 | box-shadow: 0 1px 2px 0 rgba(29, 35, 41, 0.05); |
... | ... | @@ -32,13 +31,33 @@ |
32 | 31 | line-height: calc(@multiple-height - 2px); |
33 | 32 | color: @text-color-call-out; |
34 | 33 | background: @white; |
35 | - border: 1px solid darken(@border-color-light, 8%); | |
34 | + border: 1px solid darken(@border-color-light, 6%); | |
36 | 35 | transition: none; |
37 | 36 | |
37 | + &:not(.ant-tabs-tab-active)::before { | |
38 | + position: absolute; | |
39 | + top: -1px; | |
40 | + left: 50%; | |
41 | + width: 100%; | |
42 | + height: 2px; | |
43 | + background-color: @primary-color; | |
44 | + content: ''; | |
45 | + opacity: 0; | |
46 | + transform: translate(-50%, 0) scaleX(0); | |
47 | + transform-origin: center; | |
48 | + transition: none; | |
49 | + } | |
50 | + | |
38 | 51 | &:hover { |
39 | 52 | .ant-tabs-close-x { |
40 | 53 | opacity: 1; |
41 | 54 | } |
55 | + | |
56 | + &:not(.ant-tabs-tab-active)::before { | |
57 | + opacity: 1; | |
58 | + transform: translate(-50%, 0) scaleX(1); | |
59 | + transition: all 0.3s ease-in-out; | |
60 | + } | |
42 | 61 | } |
43 | 62 | |
44 | 63 | .ant-tabs-close-x { |
... | ... | @@ -51,7 +70,7 @@ |
51 | 70 | |
52 | 71 | &:hover { |
53 | 72 | svg { |
54 | - width: 0.75em; | |
73 | + width: 0.8em; | |
55 | 74 | } |
56 | 75 | } |
57 | 76 | } |
... | ... | @@ -73,6 +92,7 @@ |
73 | 92 | color: @white; |
74 | 93 | background: fade(@primary-color, 100%); |
75 | 94 | border: 0; |
95 | + transition: none; | |
76 | 96 | |
77 | 97 | &::before { |
78 | 98 | position: absolute; |
... | ... | @@ -98,7 +118,7 @@ |
98 | 118 | } |
99 | 119 | |
100 | 120 | .ant-tabs-nav > div:nth-child(1) { |
101 | - padding: 0 10px; | |
121 | + padding: 0 6px; | |
102 | 122 | |
103 | 123 | .ant-tabs-tab { |
104 | 124 | margin-right: 3px !important; |
... | ... | @@ -124,36 +144,42 @@ |
124 | 144 | .ant-dropdown-trigger { |
125 | 145 | display: inline-flex; |
126 | 146 | } |
127 | -} | |
128 | 147 | |
129 | -.multiple-tabs-content { | |
130 | - &__extra { | |
131 | - display: inline-block; | |
132 | - width: @multiple-height; | |
133 | - height: @multiple-height; | |
134 | - line-height: @multiple-height; | |
135 | - color: #999; | |
136 | - text-align: center; | |
137 | - cursor: pointer; | |
138 | - border-left: 1px solid #eee; | |
139 | - | |
140 | - &:hover { | |
141 | - color: @text-color-base; | |
148 | + &--hide-close { | |
149 | + .ant-tabs-close-x { | |
150 | + opacity: 0 !important; | |
142 | 151 | } |
152 | + } | |
153 | + | |
154 | + &-content { | |
155 | + &__extra { | |
156 | + display: inline-block; | |
157 | + width: @multiple-height; | |
158 | + height: @multiple-height; | |
159 | + line-height: @multiple-height; | |
160 | + color: #999; | |
161 | + text-align: center; | |
162 | + cursor: pointer; | |
163 | + border-left: 1px solid #eee; | |
164 | + | |
165 | + &:hover { | |
166 | + color: @text-color-base; | |
167 | + } | |
143 | 168 | |
144 | - span[role='img'] { | |
145 | - transform: rotate(90deg); | |
169 | + span[role='img'] { | |
170 | + transform: rotate(90deg); | |
171 | + } | |
146 | 172 | } |
147 | - } | |
148 | 173 | |
149 | - &__content { | |
150 | - display: inline-block; | |
151 | - width: 100%; | |
152 | - height: @multiple-height - 2; | |
153 | - padding-left: 0; | |
154 | - margin-left: -10px; | |
155 | - font-size: 12px; | |
156 | - cursor: pointer; | |
157 | - user-select: none; | |
174 | + &__info { | |
175 | + display: inline-block; | |
176 | + width: 100%; | |
177 | + height: @multiple-height - 2; | |
178 | + padding-left: 0; | |
179 | + margin-left: -10px; | |
180 | + font-size: 12px; | |
181 | + cursor: pointer; | |
182 | + user-select: none; | |
183 | + } | |
158 | 184 | } |
159 | 185 | } | ... | ... |
src/layouts/default/multitabs/index.tsx renamed to src/layouts/default/tabs/index.vue
1 | -import './index.less'; | |
2 | - | |
3 | -import type { TabContentProps } from './types'; | |
4 | - | |
5 | -import { defineComponent, watch, computed, unref, ref } from 'vue'; | |
6 | -import { useRouter } from 'vue-router'; | |
7 | - | |
8 | -import { Tabs } from 'ant-design-vue'; | |
9 | -import TabContent from './TabContent'; | |
10 | - | |
11 | -import { useGo } from '/@/hooks/web/usePage'; | |
12 | - | |
13 | -import { TabContentEnum } from './types'; | |
14 | - | |
15 | -import { tabStore } from '/@/store/modules/tab'; | |
16 | -import { userStore } from '/@/store/modules/user'; | |
17 | - | |
18 | -import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; | |
19 | -import { REDIRECT_NAME } from '/@/router/constant'; | |
20 | - | |
21 | -export default defineComponent({ | |
22 | - name: 'MultipleTabs', | |
23 | - setup() { | |
24 | - const activeKeyRef = ref(''); | |
25 | - | |
26 | - const affixTextList = initAffixTabs(); | |
1 | +<template> | |
2 | + <div :class="getWrapClass"> | |
3 | + <Tabs | |
4 | + type="editable-card" | |
5 | + size="small" | |
6 | + :animated="false" | |
7 | + :hideAdd="true" | |
8 | + :tabBarGutter="3" | |
9 | + :activeKey="activeKeyRef" | |
10 | + @change="handleChange" | |
11 | + @edit="handleEdit" | |
12 | + > | |
13 | + <template v-for="item in getTabsState" :key="item.query ? item.fullPath : item.path"> | |
14 | + <TabPane :closable="!(item && item.meta && item.meta.affix)"> | |
15 | + <template #tab> | |
16 | + <TabContent :tabItem="item" /> | |
17 | + </template> | |
18 | + </TabPane> | |
19 | + </template> | |
20 | + <template #tabBarExtraContent> | |
21 | + <QuickButton /> | |
22 | + </template> | |
23 | + </Tabs> | |
24 | + </div> | |
25 | +</template> | |
26 | +<script lang="ts"> | |
27 | + import { defineComponent, watch, computed, unref, ref } from 'vue'; | |
28 | + | |
29 | + import { Tabs } from 'ant-design-vue'; | |
30 | + import TabContent from './components/TabContent.vue'; | |
31 | + | |
32 | + import { useGo } from '/@/hooks/web/usePage'; | |
33 | + | |
34 | + import { tabStore } from '/@/store/modules/tab'; | |
35 | + import { userStore } from '/@/store/modules/user'; | |
36 | + | |
37 | + import { initAffixTabs, useTabsDrag } from './useMultipleTabs'; | |
38 | + import { REDIRECT_NAME } from '/@/router/constant'; | |
39 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
40 | + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
41 | + | |
42 | + export default defineComponent({ | |
43 | + name: 'MultipleTabs', | |
44 | + components: { | |
45 | + QuickButton: createAsyncComponent(() => import('./components/QuickButton.vue')), | |
46 | + Tabs, | |
47 | + TabPane: Tabs.TabPane, | |
48 | + TabContent, | |
49 | + }, | |
50 | + setup() { | |
51 | + const affixTextList = initAffixTabs(); | |
52 | + | |
53 | + const activeKeyRef = ref(''); | |
54 | + | |
55 | + useTabsDrag(affixTextList); | |
56 | + const { prefixCls } = useDesign('multiple-tabs'); | |
57 | + const go = useGo(); | |
58 | + | |
59 | + const getTabsState = computed(() => tabStore.getTabsState); | |
60 | + | |
61 | + const unClose = computed(() => { | |
62 | + return getTabsState.value.length === 1; | |
63 | + }); | |
27 | 64 | |
28 | - useTabsDrag(affixTextList); | |
65 | + const getWrapClass = computed(() => { | |
66 | + return [ | |
67 | + prefixCls, | |
68 | + { | |
69 | + [`${prefixCls}--hide-close`]: unClose, | |
70 | + }, | |
71 | + ]; | |
72 | + }); | |
29 | 73 | |
30 | - const go = useGo(); | |
74 | + watch( | |
75 | + () => tabStore.getLastChangeRouteState?.path, | |
76 | + () => { | |
77 | + if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) { | |
78 | + return; | |
79 | + } | |
80 | + const lastChangeRoute = unref(tabStore.getLastChangeRouteState); | |
81 | + if (!lastChangeRoute || !userStore.getTokenState) return; | |
82 | + | |
83 | + const { path, fullPath } = lastChangeRoute; | |
84 | + const p = fullPath || path; | |
85 | + | |
86 | + if (activeKeyRef.value !== p) { | |
87 | + activeKeyRef.value = p; | |
88 | + } | |
89 | + | |
90 | + tabStore.addTabAction(lastChangeRoute); | |
91 | + }, | |
92 | + { | |
93 | + immediate: true, | |
94 | + } | |
95 | + ); | |
31 | 96 | |
32 | - const { currentRoute } = useRouter(); | |
97 | + function handleChange(activeKey: any) { | |
98 | + activeKeyRef.value = activeKey; | |
99 | + go(activeKey, false); | |
100 | + } | |
33 | 101 | |
34 | - const getTabsState = computed(() => tabStore.getTabsState); | |
102 | + // Close the current tab | |
103 | + function handleEdit(targetKey: string) { | |
104 | + // Added operation to hide, currently only use delete operation | |
105 | + if (unref(unClose)) return; | |
35 | 106 | |
36 | - watch( | |
37 | - () => tabStore.getLastChangeRouteState?.path, | |
38 | - () => { | |
39 | - if (tabStore.getLastChangeRouteState?.name === REDIRECT_NAME) { | |
40 | - return; | |
41 | - } | |
42 | - const lastChangeRoute = unref(tabStore.getLastChangeRouteState); | |
43 | - if (!lastChangeRoute || !userStore.getTokenState) return; | |
44 | - const { path, fullPath } = lastChangeRoute; | |
45 | - const p = fullPath || path; | |
46 | - if (activeKeyRef.value !== p) { | |
47 | - activeKeyRef.value = p; | |
48 | - } | |
49 | - tabStore.addTabAction(lastChangeRoute); | |
50 | - }, | |
51 | - { | |
52 | - immediate: true, | |
107 | + tabStore.closeTabByKeyAction(targetKey); | |
53 | 108 | } |
54 | - ); | |
55 | - | |
56 | - function handleChange(activeKey: any) { | |
57 | - activeKeyRef.value = activeKey; | |
58 | - go(activeKey, false); | |
59 | - } | |
60 | - | |
61 | - // Close the current tab | |
62 | - function handleEdit(targetKey: string) { | |
63 | - // Added operation to hide, currently only use delete operation | |
64 | - tabStore.closeTabByKeyAction(targetKey); | |
65 | - } | |
66 | - | |
67 | - function renderQuick() { | |
68 | - const tabContentProps: TabContentProps = { | |
69 | - tabItem: currentRoute.value, | |
70 | - type: TabContentEnum.EXTRA_TYPE, | |
109 | + return { | |
110 | + prefixCls, | |
111 | + unClose, | |
112 | + getWrapClass, | |
113 | + handleEdit, | |
114 | + handleChange, | |
115 | + activeKeyRef, | |
116 | + getTabsState, | |
71 | 117 | }; |
72 | - return <TabContent {...tabContentProps} />; | |
73 | - } | |
74 | - | |
75 | - function renderTabs() { | |
76 | - return unref(getTabsState).map((item) => { | |
77 | - const key = item.query ? item.fullPath : item.path; | |
78 | - const closable = !(item && item.meta && item.meta.affix); | |
79 | - | |
80 | - const slots = { | |
81 | - tab: () => <TabContent tabItem={item} />, | |
82 | - }; | |
83 | - return ( | |
84 | - <Tabs.TabPane key={key} closable={closable}> | |
85 | - {slots} | |
86 | - </Tabs.TabPane> | |
87 | - ); | |
88 | - }); | |
89 | - } | |
90 | - | |
91 | - return () => { | |
92 | - const slots = { | |
93 | - default: () => renderTabs(), | |
94 | - tabBarExtraContent: () => renderQuick(), | |
95 | - }; | |
96 | - return ( | |
97 | - <div class="multiple-tabs"> | |
98 | - <Tabs | |
99 | - type="editable-card" | |
100 | - size="small" | |
101 | - animated={false} | |
102 | - hideAdd={true} | |
103 | - tabBarGutter={3} | |
104 | - activeKey={unref(activeKeyRef)} | |
105 | - onChange={handleChange} | |
106 | - onEdit={handleEdit} | |
107 | - > | |
108 | - {slots} | |
109 | - </Tabs> | |
110 | - </div> | |
111 | - ); | |
112 | - }; | |
113 | - }, | |
114 | -}); | |
118 | + }, | |
119 | + }); | |
120 | +</script> | |
121 | +<style lang="less"> | |
122 | + @import './index.less'; | |
123 | +</style> | ... | ... |
src/layouts/default/multitabs/types.ts renamed to src/layouts/default/tabs/types.ts
src/layouts/default/multitabs/useMultipleTabs.ts renamed to src/layouts/default/tabs/useMultipleTabs.ts
... | ... | @@ -2,6 +2,7 @@ import Sortable from 'sortablejs'; |
2 | 2 | import { toRaw, ref, nextTick, onMounted } from 'vue'; |
3 | 3 | import { RouteLocationNormalized } from 'vue-router'; |
4 | 4 | import { useProjectSetting } from '/@/hooks/setting'; |
5 | +import { useDesign } from '/@/hooks/web/useDesign'; | |
5 | 6 | import router from '/@/router'; |
6 | 7 | import { tabStore } from '/@/store/modules/tab'; |
7 | 8 | import { isNullAndUnDef } from '/@/utils/is'; |
... | ... | @@ -48,12 +49,12 @@ export function initAffixTabs(): string[] { |
48 | 49 | export function useTabsDrag(affixTextList: string[]) { |
49 | 50 | const { multiTabsSetting } = useProjectSetting(); |
50 | 51 | |
52 | + const { prefixCls } = useDesign('multiple-tabs'); | |
53 | + | |
51 | 54 | function initSortableTabs() { |
52 | 55 | if (!multiTabsSetting.canDrag) return; |
53 | 56 | nextTick(() => { |
54 | - const el = document.querySelectorAll( | |
55 | - '.multiple-tabs .ant-tabs-nav > div' | |
56 | - )?.[0] as HTMLElement; | |
57 | + const el = document.querySelectorAll(`.${prefixCls} .ant-tabs-nav > div`)?.[0] as HTMLElement; | |
57 | 58 | |
58 | 59 | if (!el) return; |
59 | 60 | Sortable.create(el, { | ... | ... |
src/layouts/default/multitabs/useTabDropdown.ts renamed to src/layouts/default/tabs/useTabDropdown.ts
src/layouts/default/trigger/HeaderTrigger.vue
0 → 100644
1 | +<template> | |
2 | + <span :class="[prefixCls, theme]" @click="toggleCollapsed"> | |
3 | + <MenuUnfoldOutlined v-if="getCollapsed" /> <MenuFoldOutlined v-else /> | |
4 | + </span> | |
5 | +</template> | |
6 | +<script lang="ts"> | |
7 | + import { defineComponent } from 'vue'; | |
8 | + import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue'; | |
9 | + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | |
10 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
11 | + import { propTypes } from '/@/utils/propTypes'; | |
12 | + | |
13 | + export default defineComponent({ | |
14 | + name: 'SiderTrigger', | |
15 | + components: { MenuUnfoldOutlined, MenuFoldOutlined }, | |
16 | + props: { | |
17 | + theme: propTypes.oneOf(['light', 'dark']), | |
18 | + }, | |
19 | + setup() { | |
20 | + const { getCollapsed, toggleCollapsed } = useMenuSetting(); | |
21 | + const { prefixCls } = useDesign('layout-header-trigger'); | |
22 | + return { getCollapsed, toggleCollapsed, prefixCls }; | |
23 | + }, | |
24 | + }); | |
25 | +</script> | ... | ... |
src/layouts/default/trigger/SiderTrigger.vue
0 → 100644
1 | +<template> | |
2 | + <DoubleRightOutlined v-if="getCollapsed" /> | |
3 | + <DoubleLeftOutlined v-else /> | |
4 | +</template> | |
5 | +<script lang="ts"> | |
6 | + import { defineComponent } from 'vue'; | |
7 | + import { DoubleRightOutlined, DoubleLeftOutlined } from '@ant-design/icons-vue'; | |
8 | + import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; | |
9 | + | |
10 | + export default defineComponent({ | |
11 | + name: 'SiderTrigger', | |
12 | + components: { DoubleRightOutlined, DoubleLeftOutlined }, | |
13 | + setup() { | |
14 | + const { getCollapsed } = useMenuSetting(); | |
15 | + return { getCollapsed }; | |
16 | + }, | |
17 | + }); | |
18 | +</script> | ... | ... |
src/layouts/default/trigger/index.vue
0 → 100644
1 | +<template> | |
2 | + <SiderTrigger v-if="sider" /> | |
3 | + <HeaderTrigger v-else :theme="theme" /> | |
4 | +</template> | |
5 | +<script lang="ts"> | |
6 | + import { defineComponent } from 'vue'; | |
7 | + import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
8 | + import { propTypes } from '/@/utils/propTypes'; | |
9 | + | |
10 | + export default defineComponent({ | |
11 | + name: 'LayoutTrigger', | |
12 | + components: { | |
13 | + SiderTrigger: createAsyncComponent(() => import('./SiderTrigger.vue')), | |
14 | + HeaderTrigger: createAsyncComponent(() => import('./HeaderTrigger.vue'), { loading: true }), | |
15 | + }, | |
16 | + props: { | |
17 | + sider: propTypes.bool.def(true), | |
18 | + theme: propTypes.oneOf(['light', 'dark']), | |
19 | + }, | |
20 | + }); | |
21 | +</script> | ... | ... |
src/layouts/iframe/index.vue
1 | 1 | <template> |
2 | - <template v-for="frame in getFramePages" :key="frame.path"> | |
3 | - <FramePage | |
4 | - v-if="frame.meta.frameSrc && hasRenderFrame(frame.name)" | |
5 | - v-show="showIframe(frame)" | |
6 | - :frameSrc="frame.meta.frameSrc" | |
7 | - /> | |
8 | - </template> | |
2 | + <div> | |
3 | + <template v-for="frame in getFramePages" :key="frame.path"> | |
4 | + <FramePage | |
5 | + v-if="frame.meta.frameSrc && hasRenderFrame(frame.name)" | |
6 | + v-show="showIframe(frame)" | |
7 | + :frameSrc="frame.meta.frameSrc" | |
8 | + /> | |
9 | + </template> | |
10 | + </div> | |
9 | 11 | </template> |
10 | 12 | <script lang="ts"> |
11 | 13 | import { defineComponent } from 'vue'; | ... | ... |
src/layouts/iframe/useFrameKeepAlive.ts
1 | 1 | import type { AppRouteRecordRaw } from '/@/router/types'; |
2 | 2 | |
3 | 3 | import { computed, toRaw, unref } from 'vue'; |
4 | -import { useRouter } from 'vue-router'; | |
5 | -import router from '/@/router'; | |
6 | 4 | |
7 | 5 | import { tabStore } from '/@/store/modules/tab'; |
8 | 6 | |
... | ... | @@ -10,8 +8,10 @@ import { unique } from '/@/utils'; |
10 | 8 | |
11 | 9 | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
12 | 10 | |
11 | +import router from '/@/router'; | |
12 | + | |
13 | 13 | export function useFrameKeepAlive() { |
14 | - const { currentRoute } = useRouter(); | |
14 | + const { currentRoute } = router; | |
15 | 15 | const { getShowMultipleTab } = useMultipleTabSetting(); |
16 | 16 | |
17 | 17 | const getFramePages = computed(() => { | ... | ... |
src/layouts/page/index.tsx
... | ... | @@ -10,12 +10,14 @@ import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
10 | 10 | import { useTransitionSetting } from '/@/hooks/setting/useTransitionSetting'; |
11 | 11 | import { useCache } from './useCache'; |
12 | 12 | import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting'; |
13 | +// import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; | |
13 | 14 | |
14 | 15 | interface DefaultContext { |
15 | 16 | Component: FunctionalComponent & { type: { [key: string]: any } }; |
16 | 17 | route: RouteLocation; |
17 | 18 | } |
18 | 19 | |
20 | +// const FrameLayout=createAsyncComponent(()=>'/@/layouts/iframe/index.vue') | |
19 | 21 | export default defineComponent({ |
20 | 22 | name: 'PageLayout', |
21 | 23 | setup() { | ... | ... |
src/layouts/page/useCache.ts
src/locales/lang/en/layout/multipleTab.ts
src/locales/lang/zh_CN/layout/multipleTab.ts
src/router/constant.ts
... | ... | @@ -6,7 +6,7 @@ const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception'); |
6 | 6 | /** |
7 | 7 | * @description: default layout |
8 | 8 | */ |
9 | -export const LAYOUT = () => import('/@/layouts/default/index'); | |
9 | +export const LAYOUT = () => import('/@/layouts/default/index.vue'); | |
10 | 10 | |
11 | 11 | /** |
12 | 12 | * @description: page-layout | ... | ... |