Skip to content

Commit

Permalink
feat(projects): HorizontalMixMenu
Browse files Browse the repository at this point in the history
  • Loading branch information
honghuangdc committed Nov 11, 2023
1 parent c9329f9 commit 2d59dd6
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 234 deletions.
27 changes: 11 additions & 16 deletions packages/hooks/src/use-context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { inject, provide } from 'vue';
import type { InjectionKey } from 'vue';

type ContextFn<T> = () => T;

/**
* use context
* @param contextName context name
Expand Down Expand Up @@ -60,27 +58,24 @@ type ContextFn<T> = () => T;
*
* // C.vue is same as B.vue
*/
export default function useContext<T>(contextName: string, fn: ContextFn<T>) {
const { useProvide, useInject } = createContext<T>(contextName);
export default function useContext<T extends (...args: any[]) => any>(contextName: string, fn: T) {
type Context = ReturnType<T>;

const context = fn();
const { useProvide, useInject: useStore } = createContext<Context>(contextName);

/**
* setup store in the parent component
*/
function setupStore() {
function setupStore(...args: Parameters<T>) {
const context: Context = fn(...args);
return useProvide(context);
}

/**
* use store in the child component
*/
function useStore() {
return useInject();
}

return {
/**
* setup store in the parent component
*/
setupStore,
/**
* use store in the child component
*/
useStore
};
}
Expand Down
13 changes: 9 additions & 4 deletions src/layouts/base-layout/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import GlobalTab from '../modules/global-tab/index.vue';
import GlobalContent from '../modules/global-content/index.vue';
import GlobalFooter from '../modules/global-footer/index.vue';
import ThemeDrawer from '../modules/theme-drawer/index.vue';
import { setupMixMenuContext } from '../hooks/use-mix-menu';
defineOptions({
name: 'BaseLayout'
Expand Down Expand Up @@ -42,8 +43,8 @@ const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps
},
'horizontal-mix': {
showLogo: true,
showMenu: false,
showMenuToggler: true
showMenu: true,
showMenuToggler: false
}
};
Expand All @@ -53,14 +54,16 @@ const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
const siderWidth = computed(() => getSiderWidth());
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
function getSiderWidth() {
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
let w = isVerticalMix.value ? mixWidth : width;
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
if (isVerticalMix.value && appStore.mixSiderFixed) {
w += mixChildMenuWidth;
Expand All @@ -72,14 +75,16 @@ function getSiderWidth() {
function getSiderCollapsedWidth() {
const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider;
let w = isVerticalMix.value ? mixCollapsedWidth : collapsedWidth;
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
if (isVerticalMix.value && appStore.mixSiderFixed) {
w += mixChildMenuWidth;
}
return w;
}
setupMixMenuContext();
</script>

<template>
Expand Down
47 changes: 47 additions & 0 deletions src/layouts/hooks/use-mix-menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ref, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useContext } from '@sa/hooks';
import { useRouteStore } from '@/store/modules/route';

export function useMixMenu() {
const route = useRoute();
const routeStore = useRouteStore();

const activeFirstLevelMenuKey = ref('');

function setActiveFirstLevelMenuKey(key: string) {
activeFirstLevelMenuKey.value = key;
}

function getActiveFirstLevelMenuKey() {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;

const routeName = (hideInMenu ? activeMenu : name) || name;

const [firstLevelRouteName] = routeName.split('_');

setActiveFirstLevelMenuKey(firstLevelRouteName);
}

const menus = computed(
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
);

watch(
() => route.name,
() => {
getActiveFirstLevelMenuKey();
},
{ immediate: true }
);

return {
activeFirstLevelMenuKey,
setActiveFirstLevelMenuKey,
getActiveFirstLevelMenuKey,
menus
};
}

export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
21 changes: 19 additions & 2 deletions src/layouts/modules/global-header/index.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<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 HorizontalMenu from '../global-menu/horizontal-menu.vue';
import { useRouteStore } from '@/store/modules/route';
import HorizontalMenu from '../global-menu/base-menu.vue';
import GlobalLogo from '../global-logo/index.vue';
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
import ThemeButton from './components/theme-button.vue';
import UserAvatar from './components/user-avatar.vue';
import { useMixMenuContext } from '../../hooks/use-mix-menu';
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { isFullscreen, toggle } = useFullscreen();
const { menus } = useMixMenuContext();
defineOptions({
name: 'GlobalHeader'
Expand All @@ -32,12 +37,24 @@ interface Props {
}
defineProps<Props>();
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="flex-y-center h-full shadow-header">
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
<HorizontalMenu v-if="showMenu" />
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
<div v-else class="flex-1-hidden flex-y-center h-full">
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
Expand Down
8 changes: 5 additions & 3 deletions src/layouts/modules/global-menu/base-menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import type { MenuInfo, MenuMode } from 'ant-design-vue/es/menu/src/interface';
import { SimpleScrollbar } from '@sa/materials';
import { transformColorWithOpacity } from '@sa/utils';
import type { RouteKey } from '@elegant-router/types';
import { useAppStore } from '@/store/modules/app';
Expand All @@ -16,6 +17,7 @@ defineOptions({
interface Props {
darkTheme?: boolean;
mode?: MenuMode;
menus: App.Global.Menu[];
}
const props = withDefaults(defineProps<Props>(), {
Expand Down Expand Up @@ -72,11 +74,11 @@ function handleClickMenu(menuInfo: MenuInfo) {
</script>

<template>
<div class="menu-wrapper wh-full" :class="{ 'select-menu': !darkTheme }">
<SimpleScrollbar class="menu-wrapper flex-1-hidden wh-full" :class="{ 'select-menu': !darkTheme }">
<AMenu
:mode="mode"
:theme="menuTheme"
:items="routeStore.menus"
:items="menus"
:selected-keys="selectedKeys"
:open-keys="openKeys"
:inline-collapsed="inlineCollapsed"
Expand All @@ -85,7 +87,7 @@ function handleClickMenu(menuInfo: MenuInfo) {
:class="{ 'bg-container!': !darkTheme, 'horizontal-menu': isHorizontal }"
@click="handleClickMenu"
/>
</div>
</SimpleScrollbar>
</template>

<style scoped>
Expand Down
114 changes: 114 additions & 0 deletions src/layouts/modules/global-menu/first-level-menu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script setup lang="ts">
import { computed } from 'vue';
import { createReusableTemplate } from '@vueuse/core';
import { SimpleScrollbar } from '@sa/materials';
import { transformColorWithOpacity } from '@sa/utils';
import { useAppStore } from '@/store/modules/app';
import { useRouteStore } from '@/store/modules/route';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({
name: 'FirstLevelMenu'
});
interface Props {
activeMenuKey?: string;
}
defineProps<Props>();
interface Emits {
(e: 'select', menu: App.Global.Menu): boolean;
}
const emit = defineEmits<Emits>();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
interface MixMenuItemProps {
/**
* menu item label
*/
label: App.Global.Menu['label'];
/**
* menu item icon
*/
icon: App.Global.Menu['icon'];
/**
* active menu item
*/
active: boolean;
/**
* mini size
*/
isMini: boolean;
}
const [DefineMixMenuItem, MixMenuItem] = createReusableTemplate<MixMenuItemProps>();
const siderInverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
const selectedBgColor = computed(() => {
const { darkMode, themeColor } = themeStore;
const light = transformColorWithOpacity(themeColor, 0.1, '#ffffff');
const dark = transformColorWithOpacity(themeColor, 0.3, '#000000');
return darkMode ? dark : light;
});
function handleClickMixMenu(menu: App.Global.Menu) {
emit('select', menu);
}
</script>

<template>
<!-- define component: MixMenuItem -->
<DefineMixMenuItem v-slot="{ label, icon, active, isMini }">
<div
class="flex-vertical-center mx-4px mb-6px py-8px px-4px rounded-8px bg-transparent transition-300 cursor-pointer hover:bg-[rgb(0,0,0,0.08)]"
:class="{
'text-primary selected-mix-menu': active,
'text-white:65 hover:text-white': siderInverted,
'!text-white !bg-primary': active && siderInverted
}"
>
<component :is="icon" :class="[isMini ? 'text-icon-small' : 'text-icon-large']" />
<p
class="w-full text-center ellipsis-text text-12px transition-height-300"
:class="[isMini ? 'h-0 pt-0' : 'h-24px pt-4px']"
>
{{ label }}
</p>
</div>
</DefineMixMenuItem>

<!-- template -->
<div class="flex-1-hidden flex-vertical-stretch h-full">
<slot></slot>
<SimpleScrollbar>
<MixMenuItem
v-for="menu in routeStore.menus"
:key="menu.key"
:label="menu.label"
:icon="menu.icon"
:active="menu.key === activeMenuKey"
:is-mini="appStore.siderCollapse"
@click="handleClickMixMenu(menu)"
/>
</SimpleScrollbar>
<MenuToggler
arrow-icon
:collapsed="appStore.siderCollapse"
:class="{ 'text-white:88 !hover:text-white': siderInverted }"
@click="appStore.toggleSiderCollapse"
/>
</div>
</template>

<style scoped>
.selected-mix-menu {
background-color: v-bind(selectedBgColor);
}
</style>
18 changes: 0 additions & 18 deletions src/layouts/modules/global-menu/horizontal-menu.vue

This file was deleted.

Loading

0 comments on commit 2d59dd6

Please sign in to comment.