From b4e5c6d99083f6488a08ca97fd92f252027968b6 Mon Sep 17 00:00:00 2001 From: wenyuan <49969025+wenyuanw@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:35:45 +0800 Subject: [PATCH] feat(projects): add 'vertical-hybrid-header-first' layout mode --- src/constants/app.ts | 1 + src/layouts/base-layout/index.vue | 57 ++++--- src/layouts/context/index.ts | 89 ++++++++--- .../components/first-level-menu.vue | 9 +- src/layouts/modules/global-menu/index.vue | 2 + .../modules/top-hybrid-header-first.vue | 23 +-- .../modules/top-hybrid-sidebar-first.vue | 18 +-- .../modules/vertical-hybrid-header-first.vue | 149 ++++++++++++++++++ .../global-menu/modules/vertical-mix-menu.vue | 27 ++-- src/layouts/modules/global-sider/index.vue | 5 +- .../components/layout-mode-card.vue | 5 + .../modules/layout/modules/layout-mode.vue | 8 + src/locales/langs/en-us.ts | 5 +- src/locales/langs/zh-cn.ts | 5 +- src/typings/union-key.d.ts | 1 + 15 files changed, 310 insertions(+), 94 deletions(-) create mode 100644 src/layouts/modules/global-menu/modules/vertical-hybrid-header-first.vue diff --git a/src/constants/app.ts b/src/constants/app.ts index 269ff960..948e2836 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -23,6 +23,7 @@ export const loginModuleRecord: Record = export const themeLayoutModeRecord: Record = { vertical: 'theme.layout.layoutMode.vertical', 'vertical-mix': 'theme.layout.layoutMode.vertical-mix', + 'vertical-hybrid-header-first': 'theme.layout.layoutMode.vertical-hybrid-header-first', horizontal: 'theme.layout.layoutMode.horizontal', 'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first', 'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first' diff --git a/src/layouts/base-layout/index.vue b/src/layouts/base-layout/index.vue index 9c9680d2..c7d5b39d 100644 --- a/src/layouts/base-layout/index.vue +++ b/src/layouts/base-layout/index.vue @@ -42,6 +42,11 @@ const headerProps = computed(() => { showMenu: false, showMenuToggler: false }, + 'vertical-hybrid-header-first': { + showLogo: !isActiveFirstLevelMenuHasChildren.value, + showMenu: true, + showMenuToggler: false + }, horizontal: { showLogo: true, showMenu: true, @@ -66,6 +71,8 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal'); const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix'); +const isVerticalHybridHeaderFirst = computed(() => themeStore.layout.mode === 'vertical-hybrid-header-first'); + const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first'); const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first'); @@ -74,36 +81,46 @@ const siderWidth = computed(() => getSiderWidth()); const siderCollapsedWidth = computed(() => getSiderCollapsedWidth()); -function getSiderWidth() { - const { width, mixWidth, mixChildMenuWidth } = themeStore.sider; +function getSiderAndCollapsedWidth(isCollapsed: boolean) { + const { + mixChildMenuWidth, + collapsedWidth, + width: themeWidth, + mixCollapsedWidth, + mixWidth: themeMixWidth + } = themeStore.sider; + + const width = isCollapsed ? collapsedWidth : themeWidth; + const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth; if (isTopHybridHeaderFirst.value) { return isActiveFirstLevelMenuHasChildren.value ? width : 0; } - let w = isVerticalMix.value || isTopHybridSidebarFirst.value ? mixWidth : width; - - if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { - w += mixChildMenuWidth; + if (isVerticalHybridHeaderFirst.value && !isActiveFirstLevelMenuHasChildren.value) { + return 0; } - return w; + const isMixMode = isVerticalMix.value || isTopHybridSidebarFirst.value || isVerticalHybridHeaderFirst.value; + let finalWidth = isMixMode ? mixWidth : width; + + if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { + finalWidth += mixChildMenuWidth; + } + + if (isVerticalHybridHeaderFirst.value && appStore.mixSiderFixed && childLevelMenus.value.length) { + finalWidth += mixChildMenuWidth; + } + + return finalWidth; +} + +function getSiderWidth() { + return getSiderAndCollapsedWidth(false); } function getSiderCollapsedWidth() { - const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider; - - if (isTopHybridHeaderFirst.value) { - return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0; - } - - let w = isVerticalMix.value || isTopHybridSidebarFirst.value ? mixCollapsedWidth : collapsedWidth; - - if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) { - w += mixChildMenuWidth; - } - - return w; + return getSiderAndCollapsedWidth(true); } diff --git a/src/layouts/context/index.ts b/src/layouts/context/index.ts index 8b542644..b6890c3c 100644 --- a/src/layouts/context/index.ts +++ b/src/layouts/context/index.ts @@ -1,7 +1,9 @@ import { computed, ref, watch } from 'vue'; import { useRoute } from 'vue-router'; import { useContext } from '@sa/hooks'; +import type { RouteKey } from '@elegant-router/types'; import { useRouteStore } from '@/store/modules/route'; +import { useRouterPush } from '@/hooks/common/router'; export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu); @@ -9,6 +11,17 @@ function useMixMenu() { const route = useRoute(); const routeStore = useRouteStore(); const { selectedKey } = useMenu(); + const { routerPushByKeyWithMetaQuery } = useRouterPush(); + + const allMenus = computed(() => routeStore.menus); + + const firstLevelMenus = computed(() => + routeStore.menus.map(menu => { + const { children: _, ...rest } = menu; + + return rest; + }) + ); const activeFirstLevelMenuKey = ref(''); @@ -22,20 +35,6 @@ function useMixMenu() { setActiveFirstLevelMenuKey(firstLevelRouteName); } - const allMenus = computed(() => routeStore.menus); - - const firstLevelMenus = computed(() => - routeStore.menus.map(menu => { - const { children: _, ...rest } = menu; - - return rest; - }) - ); - - const childLevelMenus = computed( - () => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || [] - ); - const isActiveFirstLevelMenuHasChildren = computed(() => { if (!activeFirstLevelMenuKey.value) { return false; @@ -46,6 +45,54 @@ function useMixMenu() { return Boolean(findItem?.children?.length); }); + function handleSelectFirstLevelMenu(key: RouteKey) { + setActiveFirstLevelMenuKey(key); + + if (!isActiveFirstLevelMenuHasChildren.value) { + routerPushByKeyWithMetaQuery(key); + } + } + + const secondLevelMenus = computed( + () => allMenus.value.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || [] + ); + + const activeSecondLevelMenuKey = ref(''); + + function setActiveSecondLevelMenuKey(key: string) { + activeSecondLevelMenuKey.value = key; + } + + function getActiveSecondLevelMenuKey() { + const [firstLevelRouteName, level2SuffixName] = selectedKey.value.split('_'); + + const secondLevelRouteName = `${firstLevelRouteName}_${level2SuffixName}`; + + setActiveSecondLevelMenuKey(secondLevelRouteName); + } + + const isActiveSecondLevelMenuHasChildren = computed(() => { + if (!activeSecondLevelMenuKey.value) { + return false; + } + + const findItem = secondLevelMenus.value.find(item => item.key === activeSecondLevelMenuKey.value); + + return Boolean(findItem?.children?.length); + }); + + function handleSelectSecondLevelMenu(key: RouteKey) { + setActiveSecondLevelMenuKey(key); + + if (!isActiveSecondLevelMenuHasChildren.value) { + routerPushByKeyWithMetaQuery(key); + } + } + + const childLevelMenus = computed( + () => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || [] + ); + watch( () => route.name, () => { @@ -55,13 +102,19 @@ function useMixMenu() { ); return { - allMenus, firstLevelMenus, - childLevelMenus, - isActiveFirstLevelMenuHasChildren, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey, - getActiveFirstLevelMenuKey + isActiveFirstLevelMenuHasChildren, + handleSelectFirstLevelMenu, + getActiveFirstLevelMenuKey, + secondLevelMenus, + activeSecondLevelMenuKey, + setActiveSecondLevelMenuKey, + isActiveSecondLevelMenuHasChildren, + handleSelectSecondLevelMenu, + getActiveSecondLevelMenuKey, + childLevelMenus }; } diff --git a/src/layouts/modules/global-menu/components/first-level-menu.vue b/src/layouts/modules/global-menu/components/first-level-menu.vue index b9c895a9..d0a6fdfc 100644 --- a/src/layouts/modules/global-menu/components/first-level-menu.vue +++ b/src/layouts/modules/global-menu/components/first-level-menu.vue @@ -3,6 +3,7 @@ import { computed } from 'vue'; import { createReusableTemplate } from '@vueuse/core'; import { SimpleScrollbar } from '@sa/materials'; import { transformColorWithOpacity } from '@sa/color'; +import type { RouteKey } from '@elegant-router/types'; defineOptions({ name: 'FirstLevelMenu' @@ -20,7 +21,7 @@ interface Props { const props = defineProps(); interface Emits { - (e: 'select', menu: App.Global.Menu): boolean; + (e: 'select', menuKey: RouteKey): boolean; (e: 'toggleSiderCollapse'): void; } @@ -47,8 +48,8 @@ const selectedBgColor = computed(() => { return darkMode ? dark : light; }); -function handleClickMixMenu(menu: App.Global.Menu) { - emit('select', menu); +function handleClickMixMenu(menuKey: RouteKey) { + emit('select', menuKey); } function toggleSiderCollapse() { @@ -88,7 +89,7 @@ function toggleSiderCollapse() { :icon="menu.icon" :active="menu.key === activeMenuKey" :is-mini="siderCollapse" - @click="handleClickMixMenu(menu)" + @click="handleClickMixMenu(menu.routeKey)" /> { const menuMap: Record = { vertical: VerticalMenu, 'vertical-mix': VerticalMixMenu, + 'vertical-hybrid-header-first': VerticalHybridHeaderFirst, horizontal: HorizontalMenu, 'top-hybrid-sidebar-first': TopHybridSidebarFirst, 'top-hybrid-header-first': TopHybridHeaderFirst diff --git a/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue b/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue index 4f24f378..d4c2ca0c 100644 --- a/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue +++ b/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue @@ -1,7 +1,6 @@ +