refactor(projects)!: refactor global menu & support reversed-horizontal-mix-menu
. close #365
This commit is contained in:
parent
00f41dd25e
commit
087e532613
@ -1,5 +1,9 @@
|
||||
import { transformRecordToOption } from '@/utils/common';
|
||||
|
||||
export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__';
|
||||
|
||||
export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__';
|
||||
|
||||
export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> = {
|
||||
light: 'theme.themeSchema.light',
|
||||
dark: 'theme.themeSchema.dark',
|
||||
|
@ -30,17 +30,30 @@ export function useRouterPush(inSetup = true) {
|
||||
name: key
|
||||
};
|
||||
|
||||
if (query) {
|
||||
if (Object.keys(query || {}).length) {
|
||||
routeLocation.query = query;
|
||||
}
|
||||
|
||||
if (params) {
|
||||
if (Object.keys(params || {}).length) {
|
||||
routeLocation.params = params;
|
||||
}
|
||||
|
||||
return routerPush(routeLocation);
|
||||
}
|
||||
|
||||
function routerPushByKeyWithMetaQuery(key: RouteKey) {
|
||||
const allRoutes = router.getRoutes();
|
||||
const meta = allRoutes.find(item => item.name === key)?.meta || null;
|
||||
|
||||
const query: Record<string, string> = {};
|
||||
|
||||
meta?.query?.forEach(item => {
|
||||
query[item.key] = item.value;
|
||||
});
|
||||
|
||||
return routerPushByKey(key, { query });
|
||||
}
|
||||
|
||||
async function toHome() {
|
||||
return routerPushByKey('root');
|
||||
}
|
||||
@ -95,6 +108,7 @@ export function useRouterPush(inSetup = true) {
|
||||
routerPush,
|
||||
routerBack,
|
||||
routerPushByKey,
|
||||
routerPushByKeyWithMetaQuery,
|
||||
toLogin,
|
||||
toggleLoginModule,
|
||||
redirectFromLogin
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@sa/materials';
|
||||
import type { LayoutMode } from '@sa/materials';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
@ -18,7 +18,9 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { menus } = setupMixMenuContext();
|
||||
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext();
|
||||
|
||||
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
|
||||
|
||||
const layoutMode = computed(() => {
|
||||
const vertical: LayoutMode = 'vertical';
|
||||
@ -26,30 +28,34 @@ const layoutMode = computed(() => {
|
||||
return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
|
||||
});
|
||||
|
||||
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
||||
vertical: {
|
||||
showLogo: false,
|
||||
showMenu: false,
|
||||
showMenuToggler: true
|
||||
},
|
||||
'vertical-mix': {
|
||||
showLogo: false,
|
||||
showMenu: false,
|
||||
showMenuToggler: false
|
||||
},
|
||||
horizontal: {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: false
|
||||
},
|
||||
'horizontal-mix': {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: false
|
||||
}
|
||||
};
|
||||
const headerProps = computed(() => {
|
||||
const { mode, reverseHorizontalMix } = themeStore.layout;
|
||||
|
||||
const headerProps = computed(() => headerPropsConfig[themeStore.layout.mode]);
|
||||
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
||||
vertical: {
|
||||
showLogo: false,
|
||||
showMenu: false,
|
||||
showMenuToggler: true
|
||||
},
|
||||
'vertical-mix': {
|
||||
showLogo: false,
|
||||
showMenu: false,
|
||||
showMenuToggler: false
|
||||
},
|
||||
horizontal: {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: false
|
||||
},
|
||||
'horizontal-mix': {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value
|
||||
}
|
||||
};
|
||||
|
||||
return headerPropsConfig[mode];
|
||||
});
|
||||
|
||||
const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
|
||||
|
||||
@ -62,11 +68,16 @@ const siderWidth = computed(() => getSiderWidth());
|
||||
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
|
||||
|
||||
function getSiderWidth() {
|
||||
const { reverseHorizontalMix } = themeStore.layout;
|
||||
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
|
||||
|
||||
if (isHorizontalMix.value && reverseHorizontalMix) {
|
||||
return isActiveFirstLevelMenuHasChildren.value ? width : 0;
|
||||
}
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
w += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
@ -78,7 +89,7 @@ function getSiderCollapsedWidth() {
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
w += mixChildMenuWidth;
|
||||
}
|
||||
|
||||
@ -116,6 +127,7 @@ function getSiderCollapsedWidth() {
|
||||
<template #sider>
|
||||
<GlobalSider />
|
||||
</template>
|
||||
<GlobalMenu />
|
||||
<GlobalContent />
|
||||
<ThemeDrawer />
|
||||
<template #footer>
|
||||
|
@ -26,10 +26,30 @@ function useMixMenu() {
|
||||
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
||||
}
|
||||
|
||||
const menus = computed(
|
||||
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(() => {
|
||||
if (!activeFirstLevelMenuKey.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
|
||||
|
||||
return Boolean(findItem?.children?.length);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
@ -39,9 +59,12 @@ function useMixMenu() {
|
||||
);
|
||||
|
||||
return {
|
||||
allMenus,
|
||||
firstLevelMenus,
|
||||
childLevelMenus,
|
||||
isActiveFirstLevelMenuHasChildren,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
getActiveFirstLevelMenuKey,
|
||||
menus
|
||||
getActiveFirstLevelMenuKey
|
||||
};
|
||||
}
|
||||
|
@ -1,14 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import HorizontalMenu from '../global-menu/base-menu.vue';
|
||||
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
|
||||
import GlobalLogo from '../global-logo/index.vue';
|
||||
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
||||
import GlobalSearch from '../global-search/index.vue';
|
||||
import { useMixMenuContext } from '../../context';
|
||||
import ThemeButton from './components/theme-button.vue';
|
||||
import UserAvatar from './components/user-avatar.vue';
|
||||
|
||||
@ -29,29 +26,15 @@ defineProps<Props>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
const { menus } = useMixMenuContext();
|
||||
|
||||
const headerMenus = computed(() => {
|
||||
if (themeStore.layout.mode === 'horizontal') {
|
||||
return routeStore.menus;
|
||||
}
|
||||
|
||||
if (themeStore.layout.mode === 'horizontal-mix') {
|
||||
return menus.value;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DarkModeContainer class="h-full flex-y-center px-12px shadow-header">
|
||||
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
|
||||
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
|
||||
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
|
||||
<div v-if="showMenu" :id="GLOBAL_HEADER_MENU_ID" class="h-full flex-y-center flex-1-hidden"></div>
|
||||
<div v-else class="h-full flex-y-center flex-1-hidden">
|
||||
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
|
||||
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
|
||||
</div>
|
||||
<div class="h-full flex-y-center justify-end">
|
||||
|
@ -1,96 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { MentionOption, MenuProps } from 'naive-ui';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
|
||||
defineOptions({
|
||||
name: 'BaseMenu'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
darkTheme?: boolean;
|
||||
mode?: MenuProps['mode'];
|
||||
menus: App.Global.Menu[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: 'vertical'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const naiveMenus = computed(() => props.menus as unknown as MentionOption[]);
|
||||
|
||||
const isHorizontal = computed(() => props.mode === 'horizontal');
|
||||
|
||||
const siderCollapse = computed(() => themeStore.layout.mode === 'vertical' && appStore.siderCollapse);
|
||||
|
||||
const headerHeight = computed(() => `${themeStore.header.height}px`);
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
|
||||
function updateExpandedKeys() {
|
||||
if (isHorizontal.value || siderCollapse.value || !selectedKey.value) {
|
||||
expandedKeys.value = [];
|
||||
return;
|
||||
}
|
||||
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
||||
}
|
||||
|
||||
function handleClickMenu(key: RouteKey) {
|
||||
const query = routeStore.getRouteQueryOfMetaByKey(key);
|
||||
|
||||
routerPushByKey(key, { query });
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
updateExpandedKeys();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SimpleScrollbar>
|
||||
<NMenu
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
:mode="mode"
|
||||
:value="selectedKey"
|
||||
:collapsed="siderCollapse"
|
||||
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||
:collapsed-icon-size="22"
|
||||
:options="naiveMenus"
|
||||
:inverted="darkTheme"
|
||||
:indent="18"
|
||||
responsive
|
||||
@update:value="handleClickMenu"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.n-menu--horizontal) {
|
||||
--n-item-height: v-bind(headerHeight) !important;
|
||||
}
|
||||
</style>
|
@ -3,31 +3,29 @@ import { computed } from 'vue';
|
||||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { transformColorWithOpacity } from '@sa/color';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
defineOptions({
|
||||
name: 'FirstLevelMenu'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
menus: App.Global.Menu[];
|
||||
activeMenuKey?: string;
|
||||
inverted?: boolean;
|
||||
siderCollapse?: boolean;
|
||||
darkMode?: boolean;
|
||||
themeColor: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'select', menu: App.Global.Menu): boolean;
|
||||
(e: 'toggleSiderCollapse'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
|
||||
interface MixMenuItemProps {
|
||||
/** Menu item label */
|
||||
label: App.Global.Menu['label'];
|
||||
@ -36,12 +34,12 @@ interface MixMenuItemProps {
|
||||
/** Active menu item */
|
||||
active: boolean;
|
||||
/** Mini size */
|
||||
isMini: boolean;
|
||||
isMini?: boolean;
|
||||
}
|
||||
const [DefineMixMenuItem, MixMenuItem] = createReusableTemplate<MixMenuItemProps>();
|
||||
|
||||
const selectedBgColor = computed(() => {
|
||||
const { darkMode, themeColor } = themeStore;
|
||||
const { darkMode, themeColor } = props;
|
||||
|
||||
const light = transformColorWithOpacity(themeColor, 0.1, '#ffffff');
|
||||
const dark = transformColorWithOpacity(themeColor, 0.3, '#000000');
|
||||
@ -52,6 +50,10 @@ const selectedBgColor = computed(() => {
|
||||
function handleClickMixMenu(menu: App.Global.Menu) {
|
||||
emit('select', menu);
|
||||
}
|
||||
|
||||
function toggleSiderCollapse() {
|
||||
emit('toggleSiderCollapse');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -80,21 +82,21 @@ function handleClickMixMenu(menu: App.Global.Menu) {
|
||||
<slot></slot>
|
||||
<SimpleScrollbar>
|
||||
<MixMenuItem
|
||||
v-for="menu in routeStore.menus"
|
||||
v-for="menu in menus"
|
||||
:key="menu.key"
|
||||
:label="menu.label"
|
||||
:icon="menu.icon"
|
||||
:active="menu.key === activeMenuKey"
|
||||
:is-mini="appStore.siderCollapse"
|
||||
:is-mini="siderCollapse"
|
||||
@click="handleClickMixMenu(menu)"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
<MenuToggler
|
||||
arrow-icon
|
||||
:collapsed="appStore.siderCollapse"
|
||||
:collapsed="siderCollapse"
|
||||
:z-index="99"
|
||||
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
||||
@click="appStore.toggleSiderCollapse"
|
||||
@click="toggleSiderCollapse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useMixMenuContext } from '../../context';
|
||||
import FirstLevelMenu from './first-level-menu.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'HorizontalMixMenu'
|
||||
});
|
||||
|
||||
const { activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
|
||||
if (!menu.children?.length) {
|
||||
routerPushByKey(menu.routeKey);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FirstLevelMenu :active-menu-key="activeFirstLevelMenuKey" @select="handleSelectMixMenu">
|
||||
<slot></slot>
|
||||
</FirstLevelMenu>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
33
src/layouts/modules/global-menu/index.vue
Normal file
33
src/layouts/modules/global-menu/index.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import VerticalMenu from './modules/vertical-menu.vue';
|
||||
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
|
||||
import HorizontalMenu from './modules/horizontal-menu.vue';
|
||||
import HorizontalMixMenu from './modules/horizontal-mix-menu.vue';
|
||||
import ReversedHorizontalMixMenu from './modules/reversed-horizontal-mix-menu.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalMenu'
|
||||
});
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
|
||||
vertical: VerticalMenu,
|
||||
'vertical-mix': VerticalMixMenu,
|
||||
horizontal: HorizontalMenu,
|
||||
'horizontal-mix': themeStore.layout.reverseHorizontalMix ? ReversedHorizontalMixMenu : HorizontalMixMenu
|
||||
};
|
||||
|
||||
return menuMap[themeStore.layout.mode];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="activeMenu" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
39
src/layouts/modules/global-menu/modules/horizontal-menu.vue
Normal file
39
src/layouts/modules/global-menu/modules/horizontal-menu.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
|
||||
defineOptions({
|
||||
name: 'HorizontalMenu'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||
<NMenu
|
||||
mode="horizontal"
|
||||
:value="selectedKey"
|
||||
:options="routeStore.menus"
|
||||
:indent="18"
|
||||
responsive
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
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 { useRouterPush } from '@/hooks/common/router';
|
||||
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||
import { useMixMenuContext } from '../../../context';
|
||||
|
||||
defineOptions({
|
||||
name: 'HorizontalMixMenu'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
|
||||
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
|
||||
if (!menu.children?.length) {
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||
<NMenu
|
||||
mode="horizontal"
|
||||
:value="selectedKey"
|
||||
:options="childLevelMenus"
|
||||
:indent="18"
|
||||
responsive
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
<FirstLevelMenu
|
||||
:menus="allMenus"
|
||||
:active-menu-key="activeFirstLevelMenuKey"
|
||||
:inverted="inverted"
|
||||
:sider-collapse="appStore.siderCollapse"
|
||||
:dark-mode="themeStore.darkMode"
|
||||
:theme-color="themeStore.themeColor"
|
||||
@select="handleSelectMixMenu"
|
||||
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||
>
|
||||
<slot></slot>
|
||||
</FirstLevelMenu>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -0,0 +1,97 @@
|
||||
<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 { 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 { useMixMenuContext } from '../../../context';
|
||||
|
||||
defineOptions({
|
||||
name: 'ReversedHorizontalMixMenu'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const {
|
||||
firstLevelMenus,
|
||||
childLevelMenus,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
isActiveFirstLevelMenuHasChildren
|
||||
} = useMixMenuContext();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
|
||||
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
function handleSelectMixMenu(key: RouteKey) {
|
||||
setActiveFirstLevelMenuKey(key);
|
||||
|
||||
if (!isActiveFirstLevelMenuHasChildren.value) {
|
||||
routerPushByKeyWithMetaQuery(key);
|
||||
}
|
||||
}
|
||||
|
||||
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="handleSelectMixMenu"
|
||||
/>
|
||||
</Teleport>
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
<SimpleScrollbar>
|
||||
<NMenu
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
mode="vertical"
|
||||
:value="selectedKey"
|
||||
:collapsed="appStore.siderCollapse"
|
||||
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||
:collapsed-icon-size="22"
|
||||
:options="childLevelMenus"
|
||||
:inverted="inverted"
|
||||
:indent="18"
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
70
src/layouts/modules/global-menu/modules/vertical-menu.vue
Normal file
70
src/layouts/modules/global-menu/modules/vertical-menu.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
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 { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
|
||||
defineOptions({
|
||||
name: 'VerticalMenu'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
|
||||
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
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_SIDER_MENU_ID}`">
|
||||
<SimpleScrollbar>
|
||||
<NMenu
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
mode="vertical"
|
||||
:value="selectedKey"
|
||||
:collapsed="appStore.siderCollapse"
|
||||
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||
:collapsed-icon-size="22"
|
||||
:options="routeStore.menus"
|
||||
:inverted="inverted"
|
||||
:indent="18"
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
135
src/layouts/modules/global-menu/modules/vertical-mix-menu.vue
Normal file
135
src/layouts/modules/global-menu/modules/vertical-mix-menu.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
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 { $t } from '@/locales';
|
||||
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
import { useMixMenuContext } from '../../../context';
|
||||
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||
import GlobalLogo from '../../global-logo/index.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'VerticalMenuMix'
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
||||
const {
|
||||
allMenus,
|
||||
childLevelMenus,
|
||||
activeFirstLevelMenuKey,
|
||||
setActiveFirstLevelMenuKey,
|
||||
getActiveFirstLevelMenuKey
|
||||
//
|
||||
} = useMixMenuContext();
|
||||
|
||||
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(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
|
||||
if (menu.children?.length) {
|
||||
setDrawerVisible(true);
|
||||
} else {
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
}
|
||||
}
|
||||
|
||||
function handleResetActiveMenu() {
|
||||
getActiveFirstLevelMenuKey();
|
||||
setDrawerVisible(false);
|
||||
}
|
||||
|
||||
const selectedKey = computed(() => {
|
||||
const { hideInMenu, activeMenu } = route.meta;
|
||||
const name = route.name as string;
|
||||
|
||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||
|
||||
return routeName;
|
||||
});
|
||||
|
||||
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_SIDER_MENU_ID}`">
|
||||
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
||||
<FirstLevelMenu
|
||||
:menus="allMenus"
|
||||
:active-menu-key="activeFirstLevelMenuKey"
|
||||
: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"
|
||||
:options="childLevelMenus"
|
||||
:collapsed="appStore.siderCollapse"
|
||||
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||
:collapsed-icon-size="22"
|
||||
:inverted="inverted"
|
||||
:indent="18"
|
||||
@update:value="routerPushByKeyWithMetaQuery"
|
||||
/>
|
||||
</SimpleScrollbar>
|
||||
</DarkModeContainer>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,72 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { $t } from '@/locales';
|
||||
import { useMixMenuContext } from '../../context';
|
||||
import FirstLevelMenu from './first-level-menu.vue';
|
||||
import BaseMenu from './base-menu.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'VerticalMixMenu'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
||||
const { menus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey, getActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||
|
||||
const siderInverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||
|
||||
const hasMenus = computed(() => menus.value.length > 0);
|
||||
|
||||
const showDrawer = computed(() => hasMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
|
||||
if (menu.children?.length) {
|
||||
setDrawerVisible(true);
|
||||
} else {
|
||||
routerPushByKey(menu.routeKey);
|
||||
}
|
||||
}
|
||||
|
||||
function handleResetActiveMenu() {
|
||||
getActiveFirstLevelMenuKey();
|
||||
setDrawerVisible(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
||||
<FirstLevelMenu :active-menu-key="activeFirstLevelMenuKey" :inverted="siderInverted" @select="handleSelectMixMenu">
|
||||
<slot></slot>
|
||||
</FirstLevelMenu>
|
||||
<div
|
||||
class="relative h-full transition-width-300"
|
||||
:style="{ width: appStore.mixSiderFixed && hasMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||
>
|
||||
<DarkModeContainer
|
||||
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
||||
:inverted="siderInverted"
|
||||
: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': siderInverted }"
|
||||
@click="appStore.toggleMixSiderFixed"
|
||||
/>
|
||||
</header>
|
||||
<BaseMenu :dark-theme="siderInverted" :menus="menus" />
|
||||
</DarkModeContainer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -2,11 +2,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
import GlobalLogo from '../global-logo/index.vue';
|
||||
import VerticalMenu from '../global-menu/base-menu.vue';
|
||||
import VerticalMixMenu from '../global-menu/vertical-mix-menu.vue';
|
||||
import HorizontalMixMenu from '../global-menu/horizontal-mix-menu.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalSider'
|
||||
@ -14,12 +11,12 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
|
||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
||||
const darkMenu = computed(() => !themeStore.darkMode && !isHorizontalMix.value && themeStore.sider.inverted);
|
||||
const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
||||
const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -29,11 +26,7 @@ const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
||||
:show-title="!appStore.siderCollapse"
|
||||
:style="{ height: themeStore.header.height + 'px' }"
|
||||
/>
|
||||
<VerticalMixMenu v-if="isVerticalMix">
|
||||
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
|
||||
</VerticalMixMenu>
|
||||
<HorizontalMixMenu v-else-if="isHorizontalMix" />
|
||||
<VerticalMenu v-else :dark-theme="darkMenu" :menus="routeStore.menus" />
|
||||
<div :id="GLOBAL_SIDER_MENU_ID" :class="menuWrapperClass"></div>
|
||||
</DarkModeContainer>
|
||||
</template>
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { $t } from '@/locales';
|
||||
import LayoutModeCard from '../components/layout-mode-card.vue';
|
||||
import SettingItem from '../components/setting-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'LayoutMode'
|
||||
@ -10,6 +11,10 @@ defineOptions({
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
function handleReverseHorizontalMixChange(value: boolean) {
|
||||
themeStore.setLayoutReverseHorizontalMix(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -44,6 +49,13 @@ const themeStore = useThemeStore();
|
||||
</div>
|
||||
</template>
|
||||
</LayoutModeCard>
|
||||
<SettingItem
|
||||
v-if="themeStore.layout.mode === 'horizontal-mix'"
|
||||
:label="$t('theme.layoutMode.reverseHorizontalMix')"
|
||||
class="mt-16px"
|
||||
>
|
||||
<NSwitch :value="themeStore.layout.reverseHorizontalMix" @update:value="handleReverseHorizontalMixChange" />
|
||||
</SettingItem>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@ -70,7 +70,8 @@ const local: App.I18n.Schema = {
|
||||
vertical: 'Vertical Menu Mode',
|
||||
horizontal: 'Horizontal Menu Mode',
|
||||
'vertical-mix': 'Vertical Mix Menu Mode',
|
||||
'horizontal-mix': 'Horizontal Mix menu Mode'
|
||||
'horizontal-mix': 'Horizontal Mix menu Mode',
|
||||
reverseHorizontalMix: 'Reverse first level menus and child level menus position'
|
||||
},
|
||||
recommendColor: 'Apply Recommended Color Algorithm',
|
||||
recommendColorDesc: 'The recommended color algorithm refers to',
|
||||
|
@ -70,7 +70,8 @@ const local: App.I18n.Schema = {
|
||||
vertical: '左侧菜单模式',
|
||||
'vertical-mix': '左侧菜单混合模式',
|
||||
horizontal: '顶部菜单模式',
|
||||
'horizontal-mix': '顶部菜单混合模式'
|
||||
'horizontal-mix': '顶部菜单混合模式',
|
||||
reverseHorizontalMix: '一级菜单与子级菜单位置反转'
|
||||
},
|
||||
recommendColor: '应用推荐算法的颜色',
|
||||
recommendColorDesc: '推荐颜色的算法参照',
|
||||
|
@ -354,34 +354,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route meta by key
|
||||
*
|
||||
* @param key Route key
|
||||
*/
|
||||
function getRouteMetaByKey(key: string) {
|
||||
const allRoutes = router.getRoutes();
|
||||
|
||||
return allRoutes.find(route => route.name === key)?.meta || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route query of meta by key
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
function getRouteQueryOfMetaByKey(key: string) {
|
||||
const meta = getRouteMetaByKey(key);
|
||||
|
||||
const query: Record<string, string> = {};
|
||||
|
||||
meta?.query?.forEach(item => {
|
||||
query[item.key] = item.value;
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
return {
|
||||
resetStore,
|
||||
routeHome,
|
||||
@ -398,7 +370,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
isInitAuthRoute,
|
||||
setIsInitAuthRoute,
|
||||
getIsAuthRouteExist,
|
||||
getSelectedMenuKeyPath,
|
||||
getRouteQueryOfMetaByKey
|
||||
getSelectedMenuKeyPath
|
||||
};
|
||||
});
|
||||
|
@ -132,6 +132,14 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
);
|
||||
addThemeVarsToGlobal(themeTokens, darkThemeTokens);
|
||||
}
|
||||
/**
|
||||
* Set layout reverse horizontal mix
|
||||
*
|
||||
* @param reverse Reverse horizontal mix
|
||||
*/
|
||||
function setLayoutReverseHorizontalMix(reverse: boolean) {
|
||||
settings.value.layout.reverseHorizontalMix = reverse;
|
||||
}
|
||||
|
||||
/** Cache theme settings */
|
||||
function cacheThemeSettings() {
|
||||
@ -193,6 +201,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
setThemeScheme,
|
||||
toggleThemeScheme,
|
||||
updateThemeColors,
|
||||
setThemeLayout
|
||||
setThemeLayout,
|
||||
setLayoutReverseHorizontalMix
|
||||
};
|
||||
});
|
||||
|
@ -13,7 +13,8 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
isInfoFollowPrimary: true,
|
||||
layout: {
|
||||
mode: 'vertical',
|
||||
scrollMode: 'content'
|
||||
scrollMode: 'content',
|
||||
reverseHorizontalMix: false
|
||||
},
|
||||
page: {
|
||||
animate: true,
|
||||
|
12
src/typings/app.d.ts
vendored
12
src/typings/app.d.ts
vendored
@ -24,6 +24,12 @@ declare namespace App {
|
||||
mode: UnionKey.ThemeLayoutMode;
|
||||
/** Scroll mode */
|
||||
scrollMode: UnionKey.ThemeScrollMode;
|
||||
/**
|
||||
* Whether to reverse the horizontal mix
|
||||
*
|
||||
* if true, the vertical child level menus in left and horizontal first level menus in top
|
||||
*/
|
||||
reverseHorizontalMix?: boolean;
|
||||
};
|
||||
/** Page */
|
||||
page: {
|
||||
@ -164,7 +170,7 @@ declare namespace App {
|
||||
}
|
||||
|
||||
/** The global menu */
|
||||
interface Menu {
|
||||
type Menu = {
|
||||
/**
|
||||
* The menu key
|
||||
*
|
||||
@ -183,7 +189,7 @@ declare namespace App {
|
||||
icon?: () => VNode;
|
||||
/** The menu children */
|
||||
children?: Menu[];
|
||||
}
|
||||
};
|
||||
|
||||
type Breadcrumb = Omit<Menu, 'children'> & {
|
||||
options?: Breadcrumb[];
|
||||
@ -326,7 +332,7 @@ declare namespace App {
|
||||
theme: {
|
||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||
grayscale: string;
|
||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||
layoutMode: { title: string; reverseHorizontalMix: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||
recommendColor: string;
|
||||
recommendColorDesc: string;
|
||||
themeColor: {
|
||||
|
2
src/typings/union-key.d.ts
vendored
2
src/typings/union-key.d.ts
vendored
@ -20,7 +20,7 @@ declare namespace UnionKey {
|
||||
* - vertical: the vertical menu in left
|
||||
* - horizontal: the horizontal menu in top
|
||||
* - vertical-mix: two vertical mixed menus in left
|
||||
* - horizontal-mix: the vertical menu in left and horizontal menu in top
|
||||
* - horizontal-mix: the vertical first level menus in left and horizontal child level menus in top
|
||||
*/
|
||||
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user