feat(components): 添加vertical-mix的导航模式下的菜单

This commit is contained in:
Soybean 2021-09-27 00:47:04 +08:00
parent 2fe3d27a36
commit f24ec1c532
15 changed files with 251 additions and 54 deletions

View File

@ -54,7 +54,5 @@
"[markdown]": { "[markdown]": {
"editor.defaultFormatter": "yzhang.markdown-all-in-one" "editor.defaultFormatter": "yzhang.markdown-all-in-one"
}, },
"volar.tsPlugin": true,
"volar.tsPluginStatus": true,
"workbench.productIconTheme": "fluent-icons", "workbench.productIconTheme": "fluent-icons",
} }

View File

@ -66,12 +66,12 @@ interface HorizontalMenuPositionList {
interface MenuStyle { interface MenuStyle {
/** 菜单宽度 */ /** 菜单宽度 */
width: number; width: number;
/** 混合菜单的宽度 */
mixWidth: number;
/** 菜单折叠时的宽度 */ /** 菜单折叠时的宽度 */
collapsedWidth: number; collapsedWidth: number;
/** 固定菜单 */ /** 混合菜单的宽度 */
fixed: boolean; mixWidth: number;
/** 混合菜单折叠时的宽度 */
mixCollapsedWidth: number;
/** 分割菜单 */ /** 分割菜单 */
splitMenu: boolean; splitMenu: boolean;
/** 水平模式的菜单的位置 */ /** 水平模式的菜单的位置 */

View File

@ -5,10 +5,16 @@
<div v-if="!theme.isVerticalNav" class="menu-width h-full"> <div v-if="!theme.isVerticalNav" class="menu-width h-full">
<global-logo /> <global-logo />
</div> </div>
<div class="flex-1-hidden flex-y-center h-full" :style="{ justifyContent: theme.menuStyle.horizontalPosition }"> <div v-if="theme.navStyle.mode !== 'horizontal'" class="flex-1-hidden flex-y-center h-full">
<menu-collapse v-if="theme.navStyle.mode !== 'horizontal'" /> <menu-collapse v-if="theme.navStyle.mode !== 'vertical-mix'" />
<global-breadcrumb v-if="theme.crumbsStyle.visible && theme.navStyle.mode !== 'horizontal'" /> <global-breadcrumb v-if="theme.crumbsStyle.visible" />
<header-menu v-if="theme.navStyle.mode === 'horizontal'" /> </div>
<div
v-else
class="flex-1-hidden flex-y-center h-full"
:style="{ justifyContent: theme.menuStyle.horizontalPosition }"
>
<header-menu />
</div> </div>
<div class="flex justify-end h-full"> <div class="flex justify-end h-full">
<gihub-site /> <gihub-site />

View File

@ -0,0 +1,71 @@
<template>
<n-layout-sider
:style="{ zIndex }"
:native-scrollbar="false"
:inverted="inverted"
collapse-mode="width"
:collapsed="app.menu.collapsed"
:collapsed-width="theme.menuStyle.collapsedWidth"
:width="menuWidth"
@collapse="handleMenuCollapse(true)"
@expand="handleMenuCollapse(false)"
>
<global-logo v-if="theme.isVerticalNav" />
<n-menu
:value="activeKey"
:collapsed="app.menu.collapsed"
:collapsed-width="theme.menuStyle.collapsedWidth"
:collapsed-icon-size="22"
:options="menus"
@update:value="handleUpdateMenu"
/>
</n-layout-sider>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { NMenu, NLayoutSider } from 'naive-ui';
import type { MenuOption } from 'naive-ui';
import { useThemeStore, useAppStore } from '@/store';
import { menus } from '@/router';
import { GlobalMenuOption } from '@/interface';
import { GlobalLogo } from '../../../common';
defineProps({
zIndex: {
type: Number,
default: 0
}
});
const theme = useThemeStore();
const app = useAppStore();
const router = useRouter();
const route = useRoute();
const { handleMenuCollapse } = useAppStore();
const inverted = computed(() => {
return theme.navStyle.theme !== 'light';
});
const menuWidth = computed(() => {
const { collapsed } = app.menu;
const { mode } = theme.navStyle;
const { collapsedWidth, width, mixWidth } = theme.menuStyle;
const modeWidth = mode === 'vertical-mix' ? mixWidth : width;
return collapsed ? collapsedWidth : modeWidth;
});
const activeKey = computed(() => getActiveKey());
function getActiveKey() {
return route.name as string;
}
function handleUpdateMenu(key: string, item: MenuOption) {
const menuItem = item as GlobalMenuOption;
router.push(menuItem.routePath);
}
</script>
<style scoped></style>

View File

@ -0,0 +1,39 @@
<template>
<div class="mb-6px px-4px cursor-pointer">
<div
class="flex-center flex-col py-12px rounded-2px"
:class="{ 'text-primary bg-primary bg-opacity-10': isActive }"
>
<component :is="icon" :class="[isMini ? 'text-16px' : 'text-20px']" />
<p v-show="!isMini" class="pt-8px text-12px">{{ label }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import type { PropType, VNodeChild } from 'vue';
defineProps({
routeName: {
type: String,
required: true
},
label: {
type: String,
default: ''
},
icon: {
type: Function as PropType<() => VNodeChild>,
required: true
},
isActive: {
type: Boolean,
default: false
},
isMini: {
type: Boolean,
default: false
}
});
</script>
<style scoped></style>

View File

@ -0,0 +1,14 @@
<template>
<div class="flex-center h-36px border-t-1px border-[#eee] cursor-pointer" @click="toggleMenu">
<icon-ph-caret-double-right-bold v-if="app.menu.collapsed" class="text-16px" />
<icon-ph:caret-double-left-bold v-else class="text-16px" />
</div>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/store';
const app = useAppStore();
const { toggleMenu } = useAppStore();
</script>
<style scoped></style>

View File

@ -0,0 +1,4 @@
import MixMenu from './MixMenu.vue';
import MixMenuCollapse from './MixMenuCollapse.vue';
export { MixMenu, MixMenuCollapse };

View File

@ -0,0 +1,71 @@
<template>
<div
class="h-full transition-width duration-500 ease-in-out"
:class="[app.menu.collapsed ? 'mix-menu-collapsed-width' : 'mix-menu-width']"
>
<div class="flex-col-stretch h-full">
<global-logo />
<div class="flex-1-hidden">
<n-scrollbar>
<mix-menu
v-for="item in firstDegreeMenus"
:key="item.routeName"
:route-name="item.routeName"
:label="item.label"
:icon="item.icon"
:is-active="activeParentRouteName === item.routeName"
:is-mini="app.menu.collapsed"
/>
</n-scrollbar>
</div>
<mix-menu-collapse />
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { VNodeChild } from 'vue';
import { NScrollbar } from 'naive-ui';
import { useRoute } from 'vue-router';
import { useAppStore, useThemeStore } from '@/store';
import { menus } from '@/router';
import { MixMenu, MixMenuCollapse } from './components';
import { GlobalLogo } from '../../../common';
const theme = useThemeStore();
const app = useAppStore();
const route = useRoute();
const mixMenuWidth = computed(() => `${theme.menuStyle.mixWidth}px`);
const mixMenuCollapsedWidth = computed(() => `${theme.menuStyle.mixCollapsedWidth}px`);
const firstDegreeMenus = menus.map(item => {
const { routeName } = item;
const label = item.label as string;
const icon = item.icon! as () => VNodeChild;
return {
routeName,
label,
icon
};
});
const activeParentRouteName = computed(() => {
let name = '';
const { matched } = route;
if (matched.length) {
name = matched[0].name as string;
}
return name;
});
</script>
<style scoped>
.mix-menu-width {
width: v-bind(mixMenuWidth);
}
.mix-menu-collapsed-width {
width: v-bind(mixMenuCollapsedWidth);
}
</style>

View File

@ -0,0 +1,4 @@
import DefaultSider from './DefaultSider/index.vue';
import VerticalMixSider from './VerticalMixSider/index.vue';
export { DefaultSider, VerticalMixSider };

View File

@ -1,28 +1,16 @@
<template> <template>
<n-layout-sider <div class="h-full" :class="{ 'sider-padding': theme.navStyle.mode === 'horizontal-mix' }">
class="global-sider h-full" <default-sider v-if="theme.navStyle.mode !== 'vertical-mix'" class="global-sider sider-z-index h-full" />
:style="{ zIndex }" <vertical-mix-sider v-else class="global-sider sider-z-index relative h-full" />
:native-scrollbar="false" </div>
:inverted="inverted"
collapse-mode="width"
:collapsed="app.menu.collapsed"
:collapsed-width="theme.menuStyle.collapsedWidth"
:width="menuWidth"
@collapse="handleMenuCollapse(true)"
@expand="handleMenuCollapse(false)"
>
<global-logo v-if="theme.isVerticalNav" />
<global-menu />
</n-layout-sider>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { NLayoutSider } from 'naive-ui'; import { useThemeStore } from '@/store';
import { useThemeStore, useAppStore } from '@/store'; import { DefaultSider, VerticalMixSider } from './components';
import { GlobalLogo, GlobalMenu } from '../common';
defineProps({ const props = defineProps({
zIndex: { zIndex: {
type: Number, type: Number,
default: 0 default: 0
@ -30,19 +18,11 @@ defineProps({
}); });
const theme = useThemeStore(); const theme = useThemeStore();
const app = useAppStore();
const { handleMenuCollapse } = useAppStore();
const inverted = computed(() => { const classZIndex = computed(() => props.zIndex);
return theme.navStyle.theme !== 'light'; const headerHeight = computed(() => {
}); const { height } = theme.headerStyle;
return `${height}px`;
const menuWidth = computed(() => {
const { collapsed } = app.menu;
const { mode } = theme.navStyle;
const { collapsedWidth, width, mixWidth } = theme.menuStyle;
const modeWidth = mode === 'vertical-mix' ? mixWidth : width;
return collapsed ? collapsedWidth : modeWidth;
}); });
</script> </script>
<style scoped> <style scoped>
@ -50,4 +30,12 @@ const menuWidth = computed(() => {
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%); box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
} }
.sider-z-index {
z-index: v-bind(classZIndex) !important;
}
.sider-padding {
padding-top: v-bind(headerHeight);
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<a href="/" class="nowrap-hidden flex-center h-64px cursor-pointer"> <a href="/" class="logo-height nowrap-hidden flex-center cursor-pointer">
<img src="@/assets/img/common/logo.png" alt="" class="w-32px h-32px" /> <img src="@/assets/img/common/logo.png" alt="" class="w-32px h-32px" />
<h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold">{{ title }}</h2> <h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold">{{ title }}</h2>
</a> </a>
@ -17,5 +17,13 @@ const title = useAppTitle();
const showTitle = computed( const showTitle = computed(
() => !theme.isVerticalNav || (!app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix') () => !theme.isVerticalNav || (!app.menu.collapsed && theme.navStyle.mode !== 'vertical-mix')
); );
const headerHeight = computed(() => {
const { height } = theme.headerStyle;
return `${height}px`;
});
</script> </script>
<style scoped></style> <style scoped>
.logo-height {
height: v-bind(headerHeight);
}
</style>

View File

@ -3,7 +3,7 @@
<global-sider v-if="theme.isVerticalNav" :z-index="3" /> <global-sider v-if="theme.isVerticalNav" :z-index="3" />
<global-header v-if="isHorizontalMix" :z-index="4" /> <global-header v-if="isHorizontalMix" :z-index="4" />
<div class="flex-1-hidden flex h-full"> <div class="flex-1-hidden flex h-full">
<global-sider v-if="isHorizontalMix" class="sider-margin" :z-index="3" /> <global-sider v-if="isHorizontalMix" :z-index="3" />
<n-scrollbar <n-scrollbar
ref="scrollbar" ref="scrollbar"
class="h-full" class="h-full"
@ -40,10 +40,6 @@ const routeProps = useRouteProps();
const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix'); const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix');
const headerHeight = computed(() => {
const { height } = theme.headerStyle;
return `${height}px`;
});
const headerAndMultiTabHeight = computed(() => { const headerAndMultiTabHeight = computed(() => {
const { const {
headerStyle: { height: hHeight }, headerStyle: { height: hHeight },
@ -63,9 +59,7 @@ watch(
:deep(.n-scrollbar-rail) { :deep(.n-scrollbar-rail) {
z-index: 11; z-index: 11;
} }
.sider-margin {
margin-top: v-bind(headerHeight);
}
.content-padding { .content-padding {
padding-top: v-bind(headerAndMultiTabHeight); padding-top: v-bind(headerAndMultiTabHeight);
} }

View File

@ -38,9 +38,9 @@ const themeSettings: ThemeSettings = {
}, },
menuStyle: { menuStyle: {
width: 200, width: 200,
mixWidth: 80,
collapsedWidth: 64, collapsedWidth: 64,
fixed: true, mixWidth: 80,
mixCollapsedWidth: 48,
splitMenu: false, splitMenu: false,
horizontalPosition: 'flex-start', horizontalPosition: 'flex-start',
horizontalPositionList: [ horizontalPositionList: [

View File

@ -69,7 +69,7 @@ const themeStore = defineStore({
setNavMode(mode: NavMode) { setNavMode(mode: NavMode) {
this.navStyle.mode = mode; this.navStyle.mode = mode;
}, },
/** 折叠菜单 */ /** 切割菜单(顶部混合模式horizontal-mix) */
handleSplitMenu(isSplit: boolean) { handleSplitMenu(isSplit: boolean) {
this.menuStyle.splitMenu = isSplit; this.menuStyle.splitMenu = isSplit;
}, },