mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 15:53:43 +08:00 
			
		
		
		
	feat(projects): add 'vertical-hybrid-header-first' layout mode
This commit is contained in:
		@@ -23,6 +23,7 @@ export const loginModuleRecord: Record<UnionKey.LoginModule, App.I18n.I18nKey> =
 | 
				
			|||||||
export const themeLayoutModeRecord: Record<UnionKey.ThemeLayoutMode, App.I18n.I18nKey> = {
 | 
					export const themeLayoutModeRecord: Record<UnionKey.ThemeLayoutMode, App.I18n.I18nKey> = {
 | 
				
			||||||
  vertical: 'theme.layout.layoutMode.vertical',
 | 
					  vertical: 'theme.layout.layoutMode.vertical',
 | 
				
			||||||
  'vertical-mix': 'theme.layout.layoutMode.vertical-mix',
 | 
					  'vertical-mix': 'theme.layout.layoutMode.vertical-mix',
 | 
				
			||||||
 | 
					  'vertical-hybrid-header-first': 'theme.layout.layoutMode.vertical-hybrid-header-first',
 | 
				
			||||||
  horizontal: 'theme.layout.layoutMode.horizontal',
 | 
					  horizontal: 'theme.layout.layoutMode.horizontal',
 | 
				
			||||||
  'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first',
 | 
					  'top-hybrid-sidebar-first': 'theme.layout.layoutMode.top-hybrid-sidebar-first',
 | 
				
			||||||
  'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first'
 | 
					  'top-hybrid-header-first': 'theme.layout.layoutMode.top-hybrid-header-first'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,11 @@ const headerProps = computed(() => {
 | 
				
			|||||||
      showMenu: false,
 | 
					      showMenu: false,
 | 
				
			||||||
      showMenuToggler: false
 | 
					      showMenuToggler: false
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    'vertical-hybrid-header-first': {
 | 
				
			||||||
 | 
					      showLogo: !isActiveFirstLevelMenuHasChildren.value,
 | 
				
			||||||
 | 
					      showMenu: true,
 | 
				
			||||||
 | 
					      showMenuToggler: false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    horizontal: {
 | 
					    horizontal: {
 | 
				
			||||||
      showLogo: true,
 | 
					      showLogo: true,
 | 
				
			||||||
      showMenu: true,
 | 
					      showMenu: true,
 | 
				
			||||||
@@ -66,6 +71,8 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
 | 
					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 isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
 | 
					const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
 | 
				
			||||||
@@ -74,36 +81,46 @@ const siderWidth = computed(() => getSiderWidth());
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
 | 
					const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSiderWidth() {
 | 
					function getSiderAndCollapsedWidth(isCollapsed: boolean) {
 | 
				
			||||||
  const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
 | 
					  const {
 | 
				
			||||||
 | 
					    mixChildMenuWidth,
 | 
				
			||||||
 | 
					    collapsedWidth,
 | 
				
			||||||
 | 
					    width: themeWidth,
 | 
				
			||||||
 | 
					    mixCollapsedWidth,
 | 
				
			||||||
 | 
					    mixWidth: themeMixWidth
 | 
				
			||||||
 | 
					  } = themeStore.sider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const width = isCollapsed ? collapsedWidth : themeWidth;
 | 
				
			||||||
 | 
					  const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (isTopHybridHeaderFirst.value) {
 | 
					  if (isTopHybridHeaderFirst.value) {
 | 
				
			||||||
    return isActiveFirstLevelMenuHasChildren.value ? width : 0;
 | 
					    return isActiveFirstLevelMenuHasChildren.value ? width : 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let w = isVerticalMix.value || isTopHybridSidebarFirst.value ? mixWidth : width;
 | 
					  if (isVerticalHybridHeaderFirst.value && !isActiveFirstLevelMenuHasChildren.value) {
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
  if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
 | 
					 | 
				
			||||||
    w += mixChildMenuWidth;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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() {
 | 
					function getSiderCollapsedWidth() {
 | 
				
			||||||
  const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider;
 | 
					  return getSiderAndCollapsedWidth(true);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  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;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
import { computed, ref, watch } from 'vue';
 | 
					import { computed, ref, watch } from 'vue';
 | 
				
			||||||
import { useRoute } from 'vue-router';
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
import { useContext } from '@sa/hooks';
 | 
					import { useContext } from '@sa/hooks';
 | 
				
			||||||
 | 
					import type { RouteKey } from '@elegant-router/types';
 | 
				
			||||||
import { useRouteStore } from '@/store/modules/route';
 | 
					import { useRouteStore } from '@/store/modules/route';
 | 
				
			||||||
 | 
					import { useRouterPush } from '@/hooks/common/router';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
 | 
					export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,6 +11,17 @@ function useMixMenu() {
 | 
				
			|||||||
  const route = useRoute();
 | 
					  const route = useRoute();
 | 
				
			||||||
  const routeStore = useRouteStore();
 | 
					  const routeStore = useRouteStore();
 | 
				
			||||||
  const { selectedKey } = useMenu();
 | 
					  const { selectedKey } = useMenu();
 | 
				
			||||||
 | 
					  const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const firstLevelMenus = computed<App.Global.Menu[]>(() =>
 | 
				
			||||||
 | 
					    routeStore.menus.map(menu => {
 | 
				
			||||||
 | 
					      const { children: _, ...rest } = menu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return rest;
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const activeFirstLevelMenuKey = ref('');
 | 
					  const activeFirstLevelMenuKey = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,20 +35,6 @@ function useMixMenu() {
 | 
				
			|||||||
    setActiveFirstLevelMenuKey(firstLevelRouteName);
 | 
					    setActiveFirstLevelMenuKey(firstLevelRouteName);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const firstLevelMenus = computed<App.Global.Menu[]>(() =>
 | 
					 | 
				
			||||||
    routeStore.menus.map(menu => {
 | 
					 | 
				
			||||||
      const { children: _, ...rest } = menu;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return rest;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const childLevelMenus = computed<App.Global.Menu[]>(
 | 
					 | 
				
			||||||
    () => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const isActiveFirstLevelMenuHasChildren = computed(() => {
 | 
					  const isActiveFirstLevelMenuHasChildren = computed(() => {
 | 
				
			||||||
    if (!activeFirstLevelMenuKey.value) {
 | 
					    if (!activeFirstLevelMenuKey.value) {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
@@ -46,6 +45,54 @@ function useMixMenu() {
 | 
				
			|||||||
    return Boolean(findItem?.children?.length);
 | 
					    return Boolean(findItem?.children?.length);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function handleSelectFirstLevelMenu(key: RouteKey) {
 | 
				
			||||||
 | 
					    setActiveFirstLevelMenuKey(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isActiveFirstLevelMenuHasChildren.value) {
 | 
				
			||||||
 | 
					      routerPushByKeyWithMetaQuery(key);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const secondLevelMenus = computed<App.Global.Menu[]>(
 | 
				
			||||||
 | 
					    () => 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<App.Global.Menu[]>(
 | 
				
			||||||
 | 
					    () => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || []
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  watch(
 | 
					  watch(
 | 
				
			||||||
    () => route.name,
 | 
					    () => route.name,
 | 
				
			||||||
    () => {
 | 
					    () => {
 | 
				
			||||||
@@ -55,13 +102,19 @@ function useMixMenu() {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    allMenus,
 | 
					 | 
				
			||||||
    firstLevelMenus,
 | 
					    firstLevelMenus,
 | 
				
			||||||
    childLevelMenus,
 | 
					 | 
				
			||||||
    isActiveFirstLevelMenuHasChildren,
 | 
					 | 
				
			||||||
    activeFirstLevelMenuKey,
 | 
					    activeFirstLevelMenuKey,
 | 
				
			||||||
    setActiveFirstLevelMenuKey,
 | 
					    setActiveFirstLevelMenuKey,
 | 
				
			||||||
    getActiveFirstLevelMenuKey
 | 
					    isActiveFirstLevelMenuHasChildren,
 | 
				
			||||||
 | 
					    handleSelectFirstLevelMenu,
 | 
				
			||||||
 | 
					    getActiveFirstLevelMenuKey,
 | 
				
			||||||
 | 
					    secondLevelMenus,
 | 
				
			||||||
 | 
					    activeSecondLevelMenuKey,
 | 
				
			||||||
 | 
					    setActiveSecondLevelMenuKey,
 | 
				
			||||||
 | 
					    isActiveSecondLevelMenuHasChildren,
 | 
				
			||||||
 | 
					    handleSelectSecondLevelMenu,
 | 
				
			||||||
 | 
					    getActiveSecondLevelMenuKey,
 | 
				
			||||||
 | 
					    childLevelMenus
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ import { computed } from 'vue';
 | 
				
			|||||||
import { createReusableTemplate } from '@vueuse/core';
 | 
					import { createReusableTemplate } from '@vueuse/core';
 | 
				
			||||||
import { SimpleScrollbar } from '@sa/materials';
 | 
					import { SimpleScrollbar } from '@sa/materials';
 | 
				
			||||||
import { transformColorWithOpacity } from '@sa/color';
 | 
					import { transformColorWithOpacity } from '@sa/color';
 | 
				
			||||||
 | 
					import type { RouteKey } from '@elegant-router/types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({
 | 
					defineOptions({
 | 
				
			||||||
  name: 'FirstLevelMenu'
 | 
					  name: 'FirstLevelMenu'
 | 
				
			||||||
@@ -20,7 +21,7 @@ interface Props {
 | 
				
			|||||||
const props = defineProps<Props>();
 | 
					const props = defineProps<Props>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Emits {
 | 
					interface Emits {
 | 
				
			||||||
  (e: 'select', menu: App.Global.Menu): boolean;
 | 
					  (e: 'select', menuKey: RouteKey): boolean;
 | 
				
			||||||
  (e: 'toggleSiderCollapse'): void;
 | 
					  (e: 'toggleSiderCollapse'): void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,8 +48,8 @@ const selectedBgColor = computed(() => {
 | 
				
			|||||||
  return darkMode ? dark : light;
 | 
					  return darkMode ? dark : light;
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleClickMixMenu(menu: App.Global.Menu) {
 | 
					function handleClickMixMenu(menuKey: RouteKey) {
 | 
				
			||||||
  emit('select', menu);
 | 
					  emit('select', menuKey);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleSiderCollapse() {
 | 
					function toggleSiderCollapse() {
 | 
				
			||||||
@@ -88,7 +89,7 @@ function toggleSiderCollapse() {
 | 
				
			|||||||
        :icon="menu.icon"
 | 
					        :icon="menu.icon"
 | 
				
			||||||
        :active="menu.key === activeMenuKey"
 | 
					        :active="menu.key === activeMenuKey"
 | 
				
			||||||
        :is-mini="siderCollapse"
 | 
					        :is-mini="siderCollapse"
 | 
				
			||||||
        @click="handleClickMixMenu(menu)"
 | 
					        @click="handleClickMixMenu(menu.routeKey)"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </SimpleScrollbar>
 | 
					    </SimpleScrollbar>
 | 
				
			||||||
    <MenuToggler
 | 
					    <MenuToggler
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import { useAppStore } from '@/store/modules/app';
 | 
				
			|||||||
import { useThemeStore } from '@/store/modules/theme';
 | 
					import { useThemeStore } from '@/store/modules/theme';
 | 
				
			||||||
import VerticalMenu from './modules/vertical-menu.vue';
 | 
					import VerticalMenu from './modules/vertical-menu.vue';
 | 
				
			||||||
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
 | 
					import VerticalMixMenu from './modules/vertical-mix-menu.vue';
 | 
				
			||||||
 | 
					import VerticalHybridHeaderFirst from './modules/vertical-hybrid-header-first.vue';
 | 
				
			||||||
import HorizontalMenu from './modules/horizontal-menu.vue';
 | 
					import HorizontalMenu from './modules/horizontal-menu.vue';
 | 
				
			||||||
import TopHybridSidebarFirst from './modules/top-hybrid-sidebar-first.vue';
 | 
					import TopHybridSidebarFirst from './modules/top-hybrid-sidebar-first.vue';
 | 
				
			||||||
import TopHybridHeaderFirst from './modules/top-hybrid-header-first.vue';
 | 
					import TopHybridHeaderFirst from './modules/top-hybrid-header-first.vue';
 | 
				
			||||||
@@ -20,6 +21,7 @@ const activeMenu = computed(() => {
 | 
				
			|||||||
  const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
 | 
					  const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
 | 
				
			||||||
    vertical: VerticalMenu,
 | 
					    vertical: VerticalMenu,
 | 
				
			||||||
    'vertical-mix': VerticalMixMenu,
 | 
					    'vertical-mix': VerticalMixMenu,
 | 
				
			||||||
 | 
					    'vertical-hybrid-header-first': VerticalHybridHeaderFirst,
 | 
				
			||||||
    horizontal: HorizontalMenu,
 | 
					    horizontal: HorizontalMenu,
 | 
				
			||||||
    'top-hybrid-sidebar-first': TopHybridSidebarFirst,
 | 
					    'top-hybrid-sidebar-first': TopHybridSidebarFirst,
 | 
				
			||||||
    'top-hybrid-header-first': TopHybridHeaderFirst
 | 
					    'top-hybrid-header-first': TopHybridHeaderFirst
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref, watch } from 'vue';
 | 
					import { ref, watch } from 'vue';
 | 
				
			||||||
import { useRoute } from 'vue-router';
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
import type { RouteKey } from '@elegant-router/types';
 | 
					 | 
				
			||||||
import { SimpleScrollbar } from '@sa/materials';
 | 
					import { SimpleScrollbar } from '@sa/materials';
 | 
				
			||||||
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
 | 
					import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
 | 
				
			||||||
import { useAppStore } from '@/store/modules/app';
 | 
					import { useAppStore } from '@/store/modules/app';
 | 
				
			||||||
@@ -11,7 +10,7 @@ import { useRouterPush } from '@/hooks/common/router';
 | 
				
			|||||||
import { useMenu, useMixMenuContext } from '../../../context';
 | 
					import { useMenu, useMixMenuContext } from '../../../context';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({
 | 
					defineOptions({
 | 
				
			||||||
  name: 'ReversedHorizontalMixMenu'
 | 
					  name: 'TopHybridHeaderFirst'
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
@@ -19,23 +18,9 @@ const appStore = useAppStore();
 | 
				
			|||||||
const themeStore = useThemeStore();
 | 
					const themeStore = useThemeStore();
 | 
				
			||||||
const routeStore = useRouteStore();
 | 
					const routeStore = useRouteStore();
 | 
				
			||||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
					const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
				
			||||||
const {
 | 
					const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } = useMixMenuContext();
 | 
				
			||||||
  firstLevelMenus,
 | 
					 | 
				
			||||||
  childLevelMenus,
 | 
					 | 
				
			||||||
  activeFirstLevelMenuKey,
 | 
					 | 
				
			||||||
  setActiveFirstLevelMenuKey,
 | 
					 | 
				
			||||||
  isActiveFirstLevelMenuHasChildren
 | 
					 | 
				
			||||||
} = useMixMenuContext();
 | 
					 | 
				
			||||||
const { selectedKey } = useMenu();
 | 
					const { selectedKey } = useMenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleSelectMixMenu(key: RouteKey) {
 | 
					 | 
				
			||||||
  setActiveFirstLevelMenuKey(key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!isActiveFirstLevelMenuHasChildren.value) {
 | 
					 | 
				
			||||||
    routerPushByKeyWithMetaQuery(key);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const expandedKeys = ref<string[]>([]);
 | 
					const expandedKeys = ref<string[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function updateExpandedKeys() {
 | 
					function updateExpandedKeys() {
 | 
				
			||||||
@@ -63,7 +48,7 @@ watch(
 | 
				
			|||||||
      :options="firstLevelMenus"
 | 
					      :options="firstLevelMenus"
 | 
				
			||||||
      :indent="18"
 | 
					      :indent="18"
 | 
				
			||||||
      responsive
 | 
					      responsive
 | 
				
			||||||
      @update:value="handleSelectMixMenu"
 | 
					      @update:value="handleSelectFirstLevelMenu"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  </Teleport>
 | 
					  </Teleport>
 | 
				
			||||||
  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
					  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
				
			||||||
@@ -75,7 +60,7 @@ watch(
 | 
				
			|||||||
        :collapsed="appStore.siderCollapse"
 | 
					        :collapsed="appStore.siderCollapse"
 | 
				
			||||||
        :collapsed-width="themeStore.sider.collapsedWidth"
 | 
					        :collapsed-width="themeStore.sider.collapsedWidth"
 | 
				
			||||||
        :collapsed-icon-size="22"
 | 
					        :collapsed-icon-size="22"
 | 
				
			||||||
        :options="childLevelMenus"
 | 
					        :options="secondLevelMenus"
 | 
				
			||||||
        :indent="18"
 | 
					        :indent="18"
 | 
				
			||||||
        @update:value="routerPushByKeyWithMetaQuery"
 | 
					        @update:value="routerPushByKeyWithMetaQuery"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,22 +7,14 @@ import FirstLevelMenu from '../components/first-level-menu.vue';
 | 
				
			|||||||
import { useMenu, useMixMenuContext } from '../../../context';
 | 
					import { useMenu, useMixMenuContext } from '../../../context';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({
 | 
					defineOptions({
 | 
				
			||||||
  name: 'HorizontalMixMenu'
 | 
					  name: 'TopHybridSidebarFirst'
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const appStore = useAppStore();
 | 
					const appStore = useAppStore();
 | 
				
			||||||
const themeStore = useThemeStore();
 | 
					const themeStore = useThemeStore();
 | 
				
			||||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
					const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
				
			||||||
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
 | 
					const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } = useMixMenuContext();
 | 
				
			||||||
const { selectedKey } = useMenu();
 | 
					const { selectedKey } = useMenu();
 | 
				
			||||||
 | 
					 | 
				
			||||||
function handleSelectMixMenu(menu: App.Global.Menu) {
 | 
					 | 
				
			||||||
  setActiveFirstLevelMenuKey(menu.key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!menu.children?.length) {
 | 
					 | 
				
			||||||
    routerPushByKeyWithMetaQuery(menu.routeKey);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
@@ -30,7 +22,7 @@ function handleSelectMixMenu(menu: App.Global.Menu) {
 | 
				
			|||||||
    <NMenu
 | 
					    <NMenu
 | 
				
			||||||
      mode="horizontal"
 | 
					      mode="horizontal"
 | 
				
			||||||
      :value="selectedKey"
 | 
					      :value="selectedKey"
 | 
				
			||||||
      :options="childLevelMenus"
 | 
					      :options="secondLevelMenus"
 | 
				
			||||||
      :indent="18"
 | 
					      :indent="18"
 | 
				
			||||||
      responsive
 | 
					      responsive
 | 
				
			||||||
      @update:value="routerPushByKeyWithMetaQuery"
 | 
					      @update:value="routerPushByKeyWithMetaQuery"
 | 
				
			||||||
@@ -38,12 +30,12 @@ function handleSelectMixMenu(menu: App.Global.Menu) {
 | 
				
			|||||||
  </Teleport>
 | 
					  </Teleport>
 | 
				
			||||||
  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
					  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
				
			||||||
    <FirstLevelMenu
 | 
					    <FirstLevelMenu
 | 
				
			||||||
      :menus="allMenus"
 | 
					      :menus="firstLevelMenus"
 | 
				
			||||||
      :active-menu-key="activeFirstLevelMenuKey"
 | 
					      :active-menu-key="activeFirstLevelMenuKey"
 | 
				
			||||||
      :sider-collapse="appStore.siderCollapse"
 | 
					      :sider-collapse="appStore.siderCollapse"
 | 
				
			||||||
      :dark-mode="themeStore.darkMode"
 | 
					      :dark-mode="themeStore.darkMode"
 | 
				
			||||||
      :theme-color="themeStore.themeColor"
 | 
					      :theme-color="themeStore.themeColor"
 | 
				
			||||||
      @select="handleSelectMixMenu"
 | 
					      @select="handleSelectFirstLevelMenu"
 | 
				
			||||||
      @toggle-sider-collapse="appStore.toggleSiderCollapse"
 | 
					      @toggle-sider-collapse="appStore.toggleSiderCollapse"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  </Teleport>
 | 
					  </Teleport>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { computed, ref, watch } from 'vue';
 | 
				
			||||||
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
 | 
					import type { RouteKey } from '@elegant-router/types';
 | 
				
			||||||
 | 
					import { SimpleScrollbar } from '@sa/materials';
 | 
				
			||||||
 | 
					import { useBoolean } from '@sa/hooks';
 | 
				
			||||||
 | 
					import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
 | 
				
			||||||
 | 
					import { useAppStore } from '@/store/modules/app';
 | 
				
			||||||
 | 
					import { useThemeStore } from '@/store/modules/theme';
 | 
				
			||||||
 | 
					import { useRouteStore } from '@/store/modules/route';
 | 
				
			||||||
 | 
					import { useRouterPush } from '@/hooks/common/router';
 | 
				
			||||||
 | 
					import { useMenu, useMixMenuContext } from '../../../context';
 | 
				
			||||||
 | 
					import FirstLevelMenu from '../components/first-level-menu.vue';
 | 
				
			||||||
 | 
					import GlobalLogo from '../../global-logo/index.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({
 | 
				
			||||||
 | 
					  name: 'VerticalHybridHeaderFirst'
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const route = useRoute();
 | 
				
			||||||
 | 
					const appStore = useAppStore();
 | 
				
			||||||
 | 
					const themeStore = useThemeStore();
 | 
				
			||||||
 | 
					const routeStore = useRouteStore();
 | 
				
			||||||
 | 
					const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
				
			||||||
 | 
					const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
 | 
				
			||||||
 | 
					const {
 | 
				
			||||||
 | 
					  firstLevelMenus,
 | 
				
			||||||
 | 
					  activeFirstLevelMenuKey,
 | 
				
			||||||
 | 
					  handleSelectFirstLevelMenu,
 | 
				
			||||||
 | 
					  getActiveFirstLevelMenuKey,
 | 
				
			||||||
 | 
					  secondLevelMenus,
 | 
				
			||||||
 | 
					  activeSecondLevelMenuKey,
 | 
				
			||||||
 | 
					  isActiveSecondLevelMenuHasChildren,
 | 
				
			||||||
 | 
					  handleSelectSecondLevelMenu,
 | 
				
			||||||
 | 
					  getActiveSecondLevelMenuKey,
 | 
				
			||||||
 | 
					  childLevelMenus
 | 
				
			||||||
 | 
					} = useMixMenuContext();
 | 
				
			||||||
 | 
					const { selectedKey } = useMenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleSelectMixMenu(key: RouteKey) {
 | 
				
			||||||
 | 
					  handleSelectSecondLevelMenu(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isActiveSecondLevelMenuHasChildren.value) {
 | 
				
			||||||
 | 
					    setDrawerVisible(true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleSelectMenu(key: RouteKey) {
 | 
				
			||||||
 | 
					  handleSelectFirstLevelMenu(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (secondLevelMenus.value.length > 0) {
 | 
				
			||||||
 | 
					    handleSelectMixMenu(secondLevelMenus.value[0].routeKey);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleResetActiveMenu() {
 | 
				
			||||||
 | 
					  setDrawerVisible(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!appStore.mixSiderFixed) {
 | 
				
			||||||
 | 
					    getActiveFirstLevelMenuKey();
 | 
				
			||||||
 | 
					    getActiveSecondLevelMenuKey();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const expandedKeys = ref<string[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateExpandedKeys() {
 | 
				
			||||||
 | 
					  if (appStore.siderCollapse || !selectedKey.value) {
 | 
				
			||||||
 | 
					    expandedKeys.value = [];
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(
 | 
				
			||||||
 | 
					  () => route.name,
 | 
				
			||||||
 | 
					  () => {
 | 
				
			||||||
 | 
					    updateExpandedKeys();
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  { immediate: true }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
 | 
				
			||||||
 | 
					    <NMenu
 | 
				
			||||||
 | 
					      mode="horizontal"
 | 
				
			||||||
 | 
					      :value="activeFirstLevelMenuKey"
 | 
				
			||||||
 | 
					      :options="firstLevelMenus"
 | 
				
			||||||
 | 
					      :indent="18"
 | 
				
			||||||
 | 
					      responsive
 | 
				
			||||||
 | 
					      @update:value="handleSelectMenu"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </Teleport>
 | 
				
			||||||
 | 
					  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
				
			||||||
 | 
					    <div class="h-full flex" @mouseleave="handleResetActiveMenu">
 | 
				
			||||||
 | 
					      <FirstLevelMenu
 | 
				
			||||||
 | 
					        :menus="secondLevelMenus"
 | 
				
			||||||
 | 
					        :active-menu-key="activeSecondLevelMenuKey"
 | 
				
			||||||
 | 
					        :inverted="inverted"
 | 
				
			||||||
 | 
					        :sider-collapse="appStore.siderCollapse"
 | 
				
			||||||
 | 
					        :dark-mode="themeStore.darkMode"
 | 
				
			||||||
 | 
					        :theme-color="themeStore.themeColor"
 | 
				
			||||||
 | 
					        @select="handleSelectMixMenu"
 | 
				
			||||||
 | 
					        @toggle-sider-collapse="appStore.toggleSiderCollapse"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
 | 
				
			||||||
 | 
					      </FirstLevelMenu>
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        class="relative h-full transition-width-300"
 | 
				
			||||||
 | 
					        :style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <DarkModeContainer
 | 
				
			||||||
 | 
					          class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
 | 
				
			||||||
 | 
					          :inverted="inverted"
 | 
				
			||||||
 | 
					          :style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
 | 
				
			||||||
 | 
					            <h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
 | 
				
			||||||
 | 
					            <PinToggler
 | 
				
			||||||
 | 
					              :pin="appStore.mixSiderFixed"
 | 
				
			||||||
 | 
					              :class="{ 'text-white:88 !hover:text-white': inverted }"
 | 
				
			||||||
 | 
					              @click="appStore.toggleMixSiderFixed"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </header>
 | 
				
			||||||
 | 
					          <SimpleScrollbar>
 | 
				
			||||||
 | 
					            <NMenu
 | 
				
			||||||
 | 
					              v-model:expanded-keys="expandedKeys"
 | 
				
			||||||
 | 
					              mode="vertical"
 | 
				
			||||||
 | 
					              :value="selectedKey"
 | 
				
			||||||
 | 
					              :options="childLevelMenus"
 | 
				
			||||||
 | 
					              :inverted="inverted"
 | 
				
			||||||
 | 
					              :indent="18"
 | 
				
			||||||
 | 
					              @update:value="routerPushByKeyWithMetaQuery"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </SimpleScrollbar>
 | 
				
			||||||
 | 
					        </DarkModeContainer>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </Teleport>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped></style>
 | 
				
			||||||
@@ -3,6 +3,7 @@ import { computed, ref, watch } from 'vue';
 | 
				
			|||||||
import { useRoute } from 'vue-router';
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
import { SimpleScrollbar } from '@sa/materials';
 | 
					import { SimpleScrollbar } from '@sa/materials';
 | 
				
			||||||
import { useBoolean } from '@sa/hooks';
 | 
					import { useBoolean } from '@sa/hooks';
 | 
				
			||||||
 | 
					import type { RouteKey } from '@elegant-router/types';
 | 
				
			||||||
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
 | 
					import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
 | 
				
			||||||
import { useAppStore } from '@/store/modules/app';
 | 
					import { useAppStore } from '@/store/modules/app';
 | 
				
			||||||
import { useThemeStore } from '@/store/modules/theme';
 | 
					import { useThemeStore } from '@/store/modules/theme';
 | 
				
			||||||
@@ -24,28 +25,26 @@ const routeStore = useRouteStore();
 | 
				
			|||||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
					const { routerPushByKeyWithMetaQuery } = useRouterPush();
 | 
				
			||||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
 | 
					const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
 | 
				
			||||||
const {
 | 
					const {
 | 
				
			||||||
  allMenus,
 | 
					  firstLevelMenus,
 | 
				
			||||||
  childLevelMenus,
 | 
					  secondLevelMenus,
 | 
				
			||||||
  activeFirstLevelMenuKey,
 | 
					  activeFirstLevelMenuKey,
 | 
				
			||||||
  setActiveFirstLevelMenuKey,
 | 
					  isActiveFirstLevelMenuHasChildren,
 | 
				
			||||||
  getActiveFirstLevelMenuKey
 | 
					  getActiveFirstLevelMenuKey,
 | 
				
			||||||
  //
 | 
					  handleSelectFirstLevelMenu
 | 
				
			||||||
} = useMixMenuContext();
 | 
					} = useMixMenuContext();
 | 
				
			||||||
const { selectedKey } = useMenu();
 | 
					const { selectedKey } = useMenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
 | 
					const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
 | 
					const hasChildMenus = computed(() => secondLevelMenus.value.length > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
 | 
					const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleSelectMixMenu(menu: App.Global.Menu) {
 | 
					function handleSelectMenu(key: RouteKey) {
 | 
				
			||||||
  setActiveFirstLevelMenuKey(menu.key);
 | 
					  handleSelectFirstLevelMenu(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (menu.children?.length) {
 | 
					  if (isActiveFirstLevelMenuHasChildren.value) {
 | 
				
			||||||
    setDrawerVisible(true);
 | 
					    setDrawerVisible(true);
 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    routerPushByKeyWithMetaQuery(menu.routeKey);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,13 +79,13 @@ watch(
 | 
				
			|||||||
  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
					  <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
 | 
				
			||||||
    <div class="h-full flex" @mouseleave="handleResetActiveMenu">
 | 
					    <div class="h-full flex" @mouseleave="handleResetActiveMenu">
 | 
				
			||||||
      <FirstLevelMenu
 | 
					      <FirstLevelMenu
 | 
				
			||||||
        :menus="allMenus"
 | 
					        :menus="firstLevelMenus"
 | 
				
			||||||
        :active-menu-key="activeFirstLevelMenuKey"
 | 
					        :active-menu-key="activeFirstLevelMenuKey"
 | 
				
			||||||
        :inverted="inverted"
 | 
					        :inverted="inverted"
 | 
				
			||||||
        :sider-collapse="appStore.siderCollapse"
 | 
					        :sider-collapse="appStore.siderCollapse"
 | 
				
			||||||
        :dark-mode="themeStore.darkMode"
 | 
					        :dark-mode="themeStore.darkMode"
 | 
				
			||||||
        :theme-color="themeStore.themeColor"
 | 
					        :theme-color="themeStore.themeColor"
 | 
				
			||||||
        @select="handleSelectMixMenu"
 | 
					        @select="handleSelectMenu"
 | 
				
			||||||
        @toggle-sider-collapse="appStore.toggleSiderCollapse"
 | 
					        @toggle-sider-collapse="appStore.toggleSiderCollapse"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
 | 
					        <GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
 | 
				
			||||||
@@ -113,7 +112,7 @@ watch(
 | 
				
			|||||||
              v-model:expanded-keys="expandedKeys"
 | 
					              v-model:expanded-keys="expandedKeys"
 | 
				
			||||||
              mode="vertical"
 | 
					              mode="vertical"
 | 
				
			||||||
              :value="selectedKey"
 | 
					              :value="selectedKey"
 | 
				
			||||||
              :options="childLevelMenus"
 | 
					              :options="secondLevelMenus"
 | 
				
			||||||
              :inverted="inverted"
 | 
					              :inverted="inverted"
 | 
				
			||||||
              :indent="18"
 | 
					              :indent="18"
 | 
				
			||||||
              @update:value="routerPushByKeyWithMetaQuery"
 | 
					              @update:value="routerPushByKeyWithMetaQuery"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,16 +12,13 @@ defineOptions({
 | 
				
			|||||||
const appStore = useAppStore();
 | 
					const appStore = useAppStore();
 | 
				
			||||||
const themeStore = useThemeStore();
 | 
					const themeStore = useThemeStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
 | 
					 | 
				
			||||||
const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
 | 
					const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-hybrid-sidebar-first');
 | 
				
			||||||
const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
 | 
					const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
 | 
				
			||||||
const darkMenu = computed(
 | 
					const darkMenu = computed(
 | 
				
			||||||
  () =>
 | 
					  () =>
 | 
				
			||||||
    !themeStore.darkMode && !isTopHybridSidebarFirst.value && !isTopHybridHeaderFirst.value && themeStore.sider.inverted
 | 
					    !themeStore.darkMode && !isTopHybridSidebarFirst.value && !isTopHybridHeaderFirst.value && themeStore.sider.inverted
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
const showLogo = computed(
 | 
					const showLogo = computed(() => themeStore.layout.mode === 'vertical');
 | 
				
			||||||
  () => !isVerticalMix.value && !isTopHybridSidebarFirst.value && !isTopHybridHeaderFirst.value
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
 | 
					const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,11 @@ const layoutConfig: LayoutConfig = {
 | 
				
			|||||||
    menuClass: 'w-1/4 h-full',
 | 
					    menuClass: 'w-1/4 h-full',
 | 
				
			||||||
    mainClass: 'w-2/3 h-3/4'
 | 
					    mainClass: 'w-2/3 h-3/4'
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  'vertical-hybrid-header-first': {
 | 
				
			||||||
 | 
					    placement: 'bottom',
 | 
				
			||||||
 | 
					    menuClass: 'w-1/4 h-full',
 | 
				
			||||||
 | 
					    mainClass: 'w-2/3 h-3/4'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  horizontal: {
 | 
					  horizontal: {
 | 
				
			||||||
    placement: 'bottom',
 | 
					    placement: 'bottom',
 | 
				
			||||||
    menuClass: 'w-full h-1/4',
 | 
					    menuClass: 'w-full h-1/4',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,14 @@ const themeStore = useThemeStore();
 | 
				
			|||||||
        <div class="layout-main"></div>
 | 
					        <div class="layout-main"></div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
 | 
					    <template #vertical-hybrid-header-first>
 | 
				
			||||||
 | 
					      <div class="layout-sider h-full w-8px !bg-primary"></div>
 | 
				
			||||||
 | 
					      <div class="layout-sider h-full w-16px !bg-primary-300"></div>
 | 
				
			||||||
 | 
					      <div class="vertical-wrapper">
 | 
				
			||||||
 | 
					        <div class="layout-header bg-primary"></div>
 | 
				
			||||||
 | 
					        <div class="layout-main"></div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
    <template #horizontal>
 | 
					    <template #horizontal>
 | 
				
			||||||
      <div class="layout-header !bg-primary"></div>
 | 
					      <div class="layout-header !bg-primary"></div>
 | 
				
			||||||
      <div class="horizontal-wrapper">
 | 
					      <div class="horizontal-wrapper">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,11 +91,14 @@ const local: App.I18n.Schema = {
 | 
				
			|||||||
        vertical: 'Vertical Mode',
 | 
					        vertical: 'Vertical Mode',
 | 
				
			||||||
        horizontal: 'Horizontal Mode',
 | 
					        horizontal: 'Horizontal Mode',
 | 
				
			||||||
        'vertical-mix': 'Vertical Mix Mode',
 | 
					        'vertical-mix': 'Vertical Mix Mode',
 | 
				
			||||||
 | 
					        'vertical-hybrid-header-first': 'Left Hybrid Header-First',
 | 
				
			||||||
        'top-hybrid-sidebar-first': 'Top-Hybrid Sidebar-First',
 | 
					        'top-hybrid-sidebar-first': 'Top-Hybrid Sidebar-First',
 | 
				
			||||||
        'top-hybrid-header-first': 'Top-Hybrid Header-First',
 | 
					        'top-hybrid-header-first': 'Top-Hybrid Header-First',
 | 
				
			||||||
        vertical_detail: 'Vertical menu layout, with the menu on the left and content on the right.',
 | 
					        vertical_detail: 'Vertical menu layout, with the menu on the left and content on the right.',
 | 
				
			||||||
        'vertical-mix_detail':
 | 
					        'vertical-mix_detail':
 | 
				
			||||||
          'Vertical mix-menu layout, with the primary menu on the dark left side and the secondary menu on the lighter right side.',
 | 
					          'Vertical mix-menu layout, with the primary menu on the dark left side and the secondary menu on the lighter left side.',
 | 
				
			||||||
 | 
					        'vertical-hybrid-header-first_detail':
 | 
				
			||||||
 | 
					          'Vertical mix-menu layout, with the primary menu at the top, the secondary menu on the dark left side, and the secondary menu on the lighter left side.',
 | 
				
			||||||
        horizontal_detail: 'Horizontal menu layout, with the menu at the top and content below.',
 | 
					        horizontal_detail: 'Horizontal menu layout, with the menu at the top and content below.',
 | 
				
			||||||
        'top-hybrid-sidebar-first_detail':
 | 
					        'top-hybrid-sidebar-first_detail':
 | 
				
			||||||
          'Top hybrid layout, with the primary menu on the left and the secondary menu at the top.',
 | 
					          'Top hybrid layout, with the primary menu on the left and the secondary menu at the top.',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,11 +90,14 @@ const local: App.I18n.Schema = {
 | 
				
			|||||||
        title: '布局模式',
 | 
					        title: '布局模式',
 | 
				
			||||||
        vertical: '左侧菜单模式',
 | 
					        vertical: '左侧菜单模式',
 | 
				
			||||||
        'vertical-mix': '左侧菜单混合模式',
 | 
					        'vertical-mix': '左侧菜单混合模式',
 | 
				
			||||||
 | 
					        'vertical-hybrid-header-first': '左侧混合-顶部优先',
 | 
				
			||||||
        horizontal: '顶部菜单模式',
 | 
					        horizontal: '顶部菜单模式',
 | 
				
			||||||
        'top-hybrid-sidebar-first': '顶部混合-侧边优先',
 | 
					        'top-hybrid-sidebar-first': '顶部混合-侧边优先',
 | 
				
			||||||
        'top-hybrid-header-first': '顶部混合-顶部优先',
 | 
					        'top-hybrid-header-first': '顶部混合-顶部优先',
 | 
				
			||||||
        vertical_detail: '左侧菜单布局,菜单在左,内容在右。',
 | 
					        vertical_detail: '左侧菜单布局,菜单在左,内容在右。',
 | 
				
			||||||
        'vertical-mix_detail': '左侧双菜单布局,一级菜单在左侧深色区域,二级菜单在右侧浅色区域。',
 | 
					        'vertical-mix_detail': '左侧双菜单布局,一级菜单在左侧深色区域,二级菜单在左侧浅色区域。',
 | 
				
			||||||
 | 
					        'vertical-hybrid-header-first_detail':
 | 
				
			||||||
 | 
					          '左侧混合布局,一级菜单在顶部,二级菜单在左侧浅色区域,三级菜单在左侧深色区域。',
 | 
				
			||||||
        horizontal_detail: '顶部菜单布局,菜单在顶部,内容在下方。',
 | 
					        horizontal_detail: '顶部菜单布局,菜单在顶部,内容在下方。',
 | 
				
			||||||
        'top-hybrid-sidebar-first_detail': '顶部混合布局,一级菜单在左侧,二级菜单在顶部。',
 | 
					        'top-hybrid-sidebar-first_detail': '顶部混合布局,一级菜单在左侧,二级菜单在顶部。',
 | 
				
			||||||
        'top-hybrid-header-first_detail': '顶部混合布局,一级菜单在顶部,二级菜单在左侧。'
 | 
					        'top-hybrid-header-first_detail': '顶部混合布局,一级菜单在顶部,二级菜单在左侧。'
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								src/typings/union-key.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/typings/union-key.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -35,6 +35,7 @@ declare namespace UnionKey {
 | 
				
			|||||||
    | 'vertical'
 | 
					    | 'vertical'
 | 
				
			||||||
    | 'horizontal'
 | 
					    | 'horizontal'
 | 
				
			||||||
    | 'vertical-mix'
 | 
					    | 'vertical-mix'
 | 
				
			||||||
 | 
					    | 'vertical-hybrid-header-first'
 | 
				
			||||||
    | 'top-hybrid-sidebar-first'
 | 
					    | 'top-hybrid-sidebar-first'
 | 
				
			||||||
    | 'top-hybrid-header-first';
 | 
					    | 'top-hybrid-header-first';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user