diff --git a/packages/materials/src/libs/admin-layout/index.vue b/packages/materials/src/libs/admin-layout/index.vue index 543f7dc0..8bbc5526 100644 --- a/packages/materials/src/libs/admin-layout/index.vue +++ b/packages/materials/src/libs/admin-layout/index.vue @@ -127,7 +127,6 @@ function handleClickMask() { :class="[ style['layout-header'], commonClass, - headerClass, headerLeftGapClass, { 'absolute top-0 left-0 w-full': fixedHeaderAndTab } ]" diff --git a/packages/materials/src/types/index.ts b/packages/materials/src/types/index.ts index bbcfb9d6..583f0907 100644 --- a/packages/materials/src/types/index.ts +++ b/packages/materials/src/types/index.ts @@ -6,12 +6,6 @@ interface AdminLayoutHeaderConfig { * @default true */ headerVisible?: boolean; - /** - * Header class - * - * @default '' - */ - headerClass?: string; /** * Header height * diff --git a/src/App.vue b/src/App.vue index 014a6428..9758e4cb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,7 +4,6 @@ import { NConfigProvider, darkTheme } from 'naive-ui'; import type { WatermarkProps } from 'naive-ui'; import { useAppStore } from './store/modules/app'; import { useThemeStore } from './store/modules/theme'; -import { useAuthStore } from './store/modules/auth'; import { naiveDateLocales, naiveLocales } from './locales/naive'; defineOptions({ @@ -13,7 +12,6 @@ defineOptions({ const appStore = useAppStore(); const themeStore = useThemeStore(); -const authStore = useAuthStore(); const naiveDarkTheme = computed(() => (themeStore.darkMode ? darkTheme : undefined)); @@ -26,13 +24,8 @@ const naiveDateLocale = computed(() => { }); const watermarkProps = computed(() => { - const content = - themeStore.watermark.enableUserName && authStore.userInfo.userName - ? authStore.userInfo.userName - : themeStore.watermark.text; - return { - content, + content: themeStore.watermarkContent, cross: true, fullscreen: true, fontSize: 16, diff --git a/src/constants/app.ts b/src/constants/app.ts index 4767c479..948e2836 100644 --- a/src/constants/app.ts +++ b/src/constants/app.ts @@ -5,9 +5,9 @@ export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__'; export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__'; export const themeSchemaRecord: Record = { - light: 'theme.themeSchema.light', - dark: 'theme.themeSchema.dark', - auto: 'theme.themeSchema.auto' + light: 'theme.appearance.themeSchema.light', + dark: 'theme.appearance.themeSchema.dark', + auto: 'theme.appearance.themeSchema.auto' }; export const themeSchemaOptions = transformRecordToOption(themeSchemaRecord); @@ -21,45 +21,57 @@ export const loginModuleRecord: Record = }; export const themeLayoutModeRecord: Record = { - vertical: 'theme.layoutMode.vertical', - 'vertical-mix': 'theme.layoutMode.vertical-mix', - horizontal: 'theme.layoutMode.horizontal', - 'horizontal-mix': 'theme.layoutMode.horizontal-mix' + 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' }; export const themeLayoutModeOptions = transformRecordToOption(themeLayoutModeRecord); export const themeScrollModeRecord: Record = { - wrapper: 'theme.scrollMode.wrapper', - content: 'theme.scrollMode.content' + wrapper: 'theme.layout.content.scrollMode.wrapper', + content: 'theme.layout.content.scrollMode.content' }; export const themeScrollModeOptions = transformRecordToOption(themeScrollModeRecord); export const themeTabModeRecord: Record = { - chrome: 'theme.tab.mode.chrome', - button: 'theme.tab.mode.button' + chrome: 'theme.layout.tab.mode.chrome', + button: 'theme.layout.tab.mode.button' }; export const themeTabModeOptions = transformRecordToOption(themeTabModeRecord); export const themePageAnimationModeRecord: Record = { - 'fade-slide': 'theme.page.mode.fade-slide', - fade: 'theme.page.mode.fade', - 'fade-bottom': 'theme.page.mode.fade-bottom', - 'fade-scale': 'theme.page.mode.fade-scale', - 'zoom-fade': 'theme.page.mode.zoom-fade', - 'zoom-out': 'theme.page.mode.zoom-out', - none: 'theme.page.mode.none' + 'fade-slide': 'theme.layout.content.page.mode.fade-slide', + fade: 'theme.layout.content.page.mode.fade', + 'fade-bottom': 'theme.layout.content.page.mode.fade-bottom', + 'fade-scale': 'theme.layout.content.page.mode.fade-scale', + 'zoom-fade': 'theme.layout.content.page.mode.zoom-fade', + 'zoom-out': 'theme.layout.content.page.mode.zoom-out', + none: 'theme.layout.content.page.mode.none' }; export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord); export const resetCacheStrategyRecord: Record = { - close: 'theme.resetCacheStrategy.close', - refresh: 'theme.resetCacheStrategy.refresh' + close: 'theme.layout.resetCacheStrategy.close', + refresh: 'theme.layout.resetCacheStrategy.refresh' }; export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord); export const DARK_CLASS = 'dark'; + +export const watermarkTimeFormatOptions = [ + { label: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm' }, + { label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss' }, + { label: 'YYYY/MM/DD HH:mm', value: 'YYYY/MM/DD HH:mm' }, + { label: 'YYYY/MM/DD HH:mm:ss', value: 'YYYY/MM/DD HH:mm:ss' }, + { label: 'HH:mm', value: 'HH:mm' }, + { label: 'HH:mm:ss', value: 'HH:mm:ss' }, + { label: 'MM-DD HH:mm', value: 'MM-DD HH:mm' } +]; diff --git a/src/layouts/base-layout/index.vue b/src/layouts/base-layout/index.vue index 7debe5d1..c7d5b39d 100644 --- a/src/layouts/base-layout/index.vue +++ b/src/layouts/base-layout/index.vue @@ -29,7 +29,7 @@ const layoutMode = computed(() => { }); const headerProps = computed(() => { - const { mode, reverseHorizontalMix } = themeStore.layout; + const { mode } = themeStore.layout; const headerPropsConfig: Record = { vertical: { @@ -42,15 +42,25 @@ const headerProps = computed(() => { showMenu: false, showMenuToggler: false }, + 'vertical-hybrid-header-first': { + showLogo: !isActiveFirstLevelMenuHasChildren.value, + showMenu: true, + showMenuToggler: false + }, horizontal: { showLogo: true, showMenu: true, showMenuToggler: false }, - 'horizontal-mix': { + 'top-hybrid-sidebar-first': { showLogo: true, showMenu: true, - showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value + showMenuToggler: false + }, + 'top-hybrid-header-first': { + showLogo: true, + showMenu: true, + showMenuToggler: isActiveFirstLevelMenuHasChildren.value } }; @@ -61,44 +71,56 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal'); const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix'); -const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-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'); const siderWidth = computed(() => getSiderWidth()); const siderCollapsedWidth = computed(() => getSiderCollapsedWidth()); -function getSiderWidth() { - const { reverseHorizontalMix } = themeStore.layout; - const { width, mixWidth, mixChildMenuWidth } = themeStore.sider; +function getSiderAndCollapsedWidth(isCollapsed: boolean) { + const { + mixChildMenuWidth, + collapsedWidth, + width: themeWidth, + mixCollapsedWidth, + mixWidth: themeMixWidth + } = themeStore.sider; - if (isHorizontalMix.value && reverseHorizontalMix) { + const width = isCollapsed ? collapsedWidth : themeWidth; + const mixWidth = isCollapsed ? mixCollapsedWidth : themeMixWidth; + + if (isTopHybridHeaderFirst.value) { return isActiveFirstLevelMenuHasChildren.value ? width : 0; } - let w = isVerticalMix.value || isHorizontalMix.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 { reverseHorizontalMix } = themeStore.layout; - const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider; - - if (isHorizontalMix.value && reverseHorizontalMix) { - return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0; - } - - let w = isVerticalMix.value || isHorizontalMix.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..9496f072 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,61 @@ 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 keys = selectedKey.value.split('_'); + + if (keys.length < 2) { + setActiveSecondLevelMenuKey(''); + return; + } + + const [firstLevelRouteName, level2SuffixName] = keys; + + 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 +109,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, - 'horizontal-mix': themeStore.layout.reverseHorizontalMix ? ReversedHorizontalMixMenu : HorizontalMixMenu + 'top-hybrid-sidebar-first': TopHybridSidebarFirst, + 'top-hybrid-header-first': TopHybridHeaderFirst }; return menuMap[themeStore.layout.mode]; diff --git a/src/layouts/modules/global-menu/modules/reversed-horizontal-mix-menu.vue b/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue similarity index 77% rename from src/layouts/modules/global-menu/modules/reversed-horizontal-mix-menu.vue rename to src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue index 4f24f378..d4c2ca0c 100644 --- a/src/layouts/modules/global-menu/modules/reversed-horizontal-mix-menu.vue +++ b/src/layouts/modules/global-menu/modules/top-hybrid-header-first.vue @@ -1,7 +1,6 @@