Skip to content

Commit b4e5c6d

Browse files
wenyuanwhonghuangdc
authored andcommitted
feat(projects): add 'vertical-hybrid-header-first' layout mode
1 parent b6ac310 commit b4e5c6d

File tree

15 files changed

+305
-89
lines changed

15 files changed

+305
-89
lines changed

src/constants/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> =
2323
export const themeLayoutModeRecord: Record<UnionKey.ThemeLayoutMode, App.I18n.I18nKey> = {
2424
vertical: 'theme.layout.layoutMode.vertical',
2525
'vertical-mix': 'theme.layout.layoutMode.vertical-mix',
26+
'vertical-hybrid-header-first': 'theme.layout.layoutMode.vertical-hybrid-header-first',
2627
horizontal: 'theme.layout.layoutMode.horizontal',
2728
'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first',
2829
'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first'

src/layouts/base-layout/index.vue

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ const headerProps = computed(() => {
4242
showMenu: false,
4343
showMenuToggler: false
4444
},
45+
'vertical-hybrid-header-first': {
46+
showLogo: !isActiveFirstLevelMenuHasChildren.value,
47+
showMenu: true,
48+
showMenuToggler: false
49+
},
4550
horizontal: {
4651
showLogo: true,
4752
showMenu: true,
@@ -66,6 +71,8 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
6671
6772
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
6873
74+
const isVerticalHybridHeaderFirst = computed(() => themeStore.layout.mode === 'vertical-hybrid-header-first');
75+
6976
const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
7077
7178
const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
@@ -74,36 +81,46 @@ const siderWidth = computed(() => getSiderWidth());
7481
7582
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
7683
77-
function getSiderWidth() {
78-
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
84+
function getSiderAndCollapsedWidth(isCollapsed: boolean) {
85+
const {
86+
mixChildMenuWidth,
87+
collapsedWidth,
88+
width: themeWidth,
89+
mixCollapsedWidth,
90+
mixWidth: themeMixWidth
91+
} = themeStore.sider;
92+
93+
const width = isCollapsed ? collapsedWidth : themeWidth;
94+
const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth;
7995
8096
if (isTopHybridHeaderFirst.value) {
8197
return isActiveFirstLevelMenuHasChildren.value ? width : 0;
8298
}
8399
84-
let w = isVerticalMix.value || isTopHybridSidebarFirst.value ? mixWidth : width;
85-
86-
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
87-
w += mixChildMenuWidth;
100+
if (isVerticalHybridHeaderFirst.value && !isActiveFirstLevelMenuHasChildren.value) {
101+
return 0;
88102
}
89103
90-
return w;
91-
}
104+
const isMixMode = isVerticalMix.value || isTopHybridSidebarFirst.value || isVerticalHybridHeaderFirst.value;
105+
let finalWidth = isMixMode ? mixWidth : width;
92106
93-
function getSiderCollapsedWidth() {
94-
const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider;
107+
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
108+
finalWidth += mixChildMenuWidth;
109+
}
95110
96-
if (isTopHybridHeaderFirst.value) {
97-
return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0;
111+
if (isVerticalHybridHeaderFirst.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
112+
finalWidth += mixChildMenuWidth;
98113
}
99114
100-
let w = isVerticalMix.value || isTopHybridSidebarFirst.value ? mixCollapsedWidth : collapsedWidth;
115+
return finalWidth;
116+
}
101117
102-
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
103-
w += mixChildMenuWidth;
104-
}
118+
function getSiderWidth() {
119+
return getSiderAndCollapsedWidth(false);
120+
}
105121
106-
return w;
122+
function getSiderCollapsedWidth() {
123+
return getSiderAndCollapsedWidth(true);
107124
}
108125
</script>
109126

src/layouts/context/index.ts

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
import { computed, ref, watch } from 'vue';
22
import { useRoute } from 'vue-router';
33
import { useContext } from '@sa/hooks';
4+
import type { RouteKey } from '@elegant-router/types';
45
import { useRouteStore } from '@/store/modules/route';
6+
import { useRouterPush } from '@/hooks/common/router';
57

68
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
79

810
function useMixMenu() {
911
const route = useRoute();
1012
const routeStore = useRouteStore();
1113
const { selectedKey } = useMenu();
14+
const { routerPushByKeyWithMetaQuery } = useRouterPush();
15+
16+
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
17+
18+
const firstLevelMenus = computed<App.Global.Menu[]>(() =>
19+
routeStore.menus.map(menu => {
20+
const { children: _, ...rest } = menu;
21+
22+
return rest;
23+
})
24+
);
1225

1326
const activeFirstLevelMenuKey = ref('');
1427

@@ -22,30 +35,64 @@ function useMixMenu() {
2235
setActiveFirstLevelMenuKey(firstLevelRouteName);
2336
}
2437

25-
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
38+
const isActiveFirstLevelMenuHasChildren = computed(() => {
39+
if (!activeFirstLevelMenuKey.value) {
40+
return false;
41+
}
2642

27-
const firstLevelMenus = computed<App.Global.Menu[]>(() =>
28-
routeStore.menus.map(menu => {
29-
const { children: _, ...rest } = menu;
43+
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
3044

31-
return rest;
32-
})
33-
);
45+
return Boolean(findItem?.children?.length);
46+
});
3447

35-
const childLevelMenus = computed<App.Global.Menu[]>(
36-
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
48+
function handleSelectFirstLevelMenu(key: RouteKey) {
49+
setActiveFirstLevelMenuKey(key);
50+
51+
if (!isActiveFirstLevelMenuHasChildren.value) {
52+
routerPushByKeyWithMetaQuery(key);
53+
}
54+
}
55+
56+
const secondLevelMenus = computed<App.Global.Menu[]>(
57+
() => allMenus.value.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
3758
);
3859

39-
const isActiveFirstLevelMenuHasChildren = computed(() => {
40-
if (!activeFirstLevelMenuKey.value) {
60+
const activeSecondLevelMenuKey = ref('');
61+
62+
function setActiveSecondLevelMenuKey(key: string) {
63+
activeSecondLevelMenuKey.value = key;
64+
}
65+
66+
function getActiveSecondLevelMenuKey() {
67+
const [firstLevelRouteName, level2SuffixName] = selectedKey.value.split('_');
68+
69+
const secondLevelRouteName = `${firstLevelRouteName}_${level2SuffixName}`;
70+
71+
setActiveSecondLevelMenuKey(secondLevelRouteName);
72+
}
73+
74+
const isActiveSecondLevelMenuHasChildren = computed(() => {
75+
if (!activeSecondLevelMenuKey.value) {
4176
return false;
4277
}
4378

44-
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
79+
const findItem = secondLevelMenus.value.find(item => item.key === activeSecondLevelMenuKey.value);
4580

4681
return Boolean(findItem?.children?.length);
4782
});
4883

84+
function handleSelectSecondLevelMenu(key: RouteKey) {
85+
setActiveSecondLevelMenuKey(key);
86+
87+
if (!isActiveSecondLevelMenuHasChildren.value) {
88+
routerPushByKeyWithMetaQuery(key);
89+
}
90+
}
91+
92+
const childLevelMenus = computed<App.Global.Menu[]>(
93+
() => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || []
94+
);
95+
4996
watch(
5097
() => route.name,
5198
() => {
@@ -55,13 +102,19 @@ function useMixMenu() {
55102
);
56103

57104
return {
58-
allMenus,
59105
firstLevelMenus,
60-
childLevelMenus,
61-
isActiveFirstLevelMenuHasChildren,
62106
activeFirstLevelMenuKey,
63107
setActiveFirstLevelMenuKey,
64-
getActiveFirstLevelMenuKey
108+
isActiveFirstLevelMenuHasChildren,
109+
handleSelectFirstLevelMenu,
110+
getActiveFirstLevelMenuKey,
111+
secondLevelMenus,
112+
activeSecondLevelMenuKey,
113+
setActiveSecondLevelMenuKey,
114+
isActiveSecondLevelMenuHasChildren,
115+
handleSelectSecondLevelMenu,
116+
getActiveSecondLevelMenuKey,
117+
childLevelMenus
65118
};
66119
}
67120

src/layouts/modules/global-menu/components/first-level-menu.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { computed } from 'vue';
33
import { createReusableTemplate } from '@vueuse/core';
44
import { SimpleScrollbar } from '@sa/materials';
55
import { transformColorWithOpacity } from '@sa/color';
6+
import type { RouteKey } from '@elegant-router/types';
67
78
defineOptions({
89
name: 'FirstLevelMenu'
@@ -20,7 +21,7 @@ interface Props {
2021
const props = defineProps<Props>();
2122
2223
interface Emits {
23-
(e: 'select', menu: App.Global.Menu): boolean;
24+
(e: 'select', menuKey: RouteKey): boolean;
2425
(e: 'toggleSiderCollapse'): void;
2526
}
2627
@@ -47,8 +48,8 @@ const selectedBgColor = computed(() => {
4748
return darkMode ? dark : light;
4849
});
4950
50-
function handleClickMixMenu(menu: App.Global.Menu) {
51-
emit('select', menu);
51+
function handleClickMixMenu(menuKey: RouteKey) {
52+
emit('select', menuKey);
5253
}
5354
5455
function toggleSiderCollapse() {
@@ -88,7 +89,7 @@ function toggleSiderCollapse() {
8889
:icon="menu.icon"
8990
:active="menu.key === activeMenuKey"
9091
:is-mini="siderCollapse"
91-
@click="handleClickMixMenu(menu)"
92+
@click="handleClickMixMenu(menu.routeKey)"
9293
/>
9394
</SimpleScrollbar>
9495
<MenuToggler

src/layouts/modules/global-menu/index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useAppStore } from '@/store/modules/app';
55
import { useThemeStore } from '@/store/modules/theme';
66
import VerticalMenu from './modules/vertical-menu.vue';
77
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
8+
import VerticalHybridHeaderFirst from './modules/vertical-hybrid-header-first.vue';
89
import HorizontalMenu from './modules/horizontal-menu.vue';
910
import TopHybridSidebarFirst from './modules/top-hybrid-sidebar-first.vue';
1011
import TopHybridHeaderFirst from './modules/top-hybrid-header-first.vue';
@@ -20,6 +21,7 @@ const activeMenu = computed(() => {
2021
const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
2122
vertical: VerticalMenu,
2223
'vertical-mix': VerticalMixMenu,
24+
'vertical-hybrid-header-first': VerticalHybridHeaderFirst,
2325
horizontal: HorizontalMenu,
2426
'top-hybrid-sidebar-first': TopHybridSidebarFirst,
2527
'top-hybrid-header-first': TopHybridHeaderFirst

src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script setup lang="ts">
22
import { ref, watch } from 'vue';
33
import { useRoute } from 'vue-router';
4-
import type { RouteKey } from '@elegant-router/types';
54
import { SimpleScrollbar } from '@sa/materials';
65
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
76
import { useAppStore } from '@/store/modules/app';
@@ -11,31 +10,17 @@ import { useRouterPush } from '@/hooks/common/router';
1110
import { useMenu, useMixMenuContext } from '../../../context';
1211
1312
defineOptions({
14-
name: 'ReversedHorizontalMixMenu'
13+
name: 'TopHybridHeaderFirst'
1514
});
1615
1716
const route = useRoute();
1817
const appStore = useAppStore();
1918
const themeStore = useThemeStore();
2019
const routeStore = useRouteStore();
2120
const { routerPushByKeyWithMetaQuery } = useRouterPush();
22-
const {
23-
firstLevelMenus,
24-
childLevelMenus,
25-
activeFirstLevelMenuKey,
26-
setActiveFirstLevelMenuKey,
27-
isActiveFirstLevelMenuHasChildren
28-
} = useMixMenuContext();
21+
const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } = useMixMenuContext();
2922
const { selectedKey } = useMenu();
3023
31-
function handleSelectMixMenu(key: RouteKey) {
32-
setActiveFirstLevelMenuKey(key);
33-
34-
if (!isActiveFirstLevelMenuHasChildren.value) {
35-
routerPushByKeyWithMetaQuery(key);
36-
}
37-
}
38-
3924
const expandedKeys = ref<string[]>([]);
4025
4126
function updateExpandedKeys() {
@@ -63,7 +48,7 @@ watch(
6348
:options="firstLevelMenus"
6449
:indent="18"
6550
responsive
66-
@update:value="handleSelectMixMenu"
51+
@update:value="handleSelectFirstLevelMenu"
6752
/>
6853
</Teleport>
6954
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
@@ -75,7 +60,7 @@ watch(
7560
:collapsed="appStore.siderCollapse"
7661
:collapsed-width="themeStore.sider.collapsedWidth"
7762
:collapsed-icon-size="22"
78-
:options="childLevelMenus"
63+
:options="secondLevelMenus"
7964
:indent="18"
8065
@update:value="routerPushByKeyWithMetaQuery"
8166
/>

src/layouts/modules/global-menu/modules/top-hybrid-sidebar-first.vue

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,35 @@ import FirstLevelMenu from '../components/first-level-menu.vue';
77
import { useMenu, useMixMenuContext } from '../../../context';
88
99
defineOptions({
10-
name: 'HorizontalMixMenu'
10+
name: 'TopHybridSidebarFirst'
1111
});
1212
1313
const appStore = useAppStore();
1414
const themeStore = useThemeStore();
1515
const { routerPushByKeyWithMetaQuery } = useRouterPush();
16-
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
16+
const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } = useMixMenuContext();
1717
const { selectedKey } = useMenu();
18-
19-
function handleSelectMixMenu(menu: App.Global.Menu) {
20-
setActiveFirstLevelMenuKey(menu.key);
21-
22-
if (!menu.children?.length) {
23-
routerPushByKeyWithMetaQuery(menu.routeKey);
24-
}
25-
}
2618
</script>
2719

2820
<template>
2921
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
3022
<NMenu
3123
mode="horizontal"
3224
:value="selectedKey"
33-
:options="childLevelMenus"
25+
:options="secondLevelMenus"
3426
:indent="18"
3527
responsive
3628
@update:value="routerPushByKeyWithMetaQuery"
3729
/>
3830
</Teleport>
3931
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
4032
<FirstLevelMenu
41-
:menus="allMenus"
33+
:menus="firstLevelMenus"
4234
:active-menu-key="activeFirstLevelMenuKey"
4335
:sider-collapse="appStore.siderCollapse"
4436
:dark-mode="themeStore.darkMode"
4537
:theme-color="themeStore.themeColor"
46-
@select="handleSelectMixMenu"
38+
@select="handleSelectFirstLevelMenu"
4739
@toggle-sider-collapse="appStore.toggleSiderCollapse"
4840
/>
4941
</Teleport>

0 commit comments

Comments
 (0)