mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-19 10:06:38 +08:00
feat(components): 添加vertical-mix的导航模式下的菜单
This commit is contained in:
parent
2fe3d27a36
commit
f24ec1c532
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -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",
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
/** 水平模式的菜单的位置 */
|
/** 水平模式的菜单的位置 */
|
||||||
|
@ -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 />
|
||||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -0,0 +1,4 @@
|
|||||||
|
import MixMenu from './MixMenu.vue';
|
||||||
|
import MixMenuCollapse from './MixMenuCollapse.vue';
|
||||||
|
|
||||||
|
export { MixMenu, MixMenuCollapse };
|
@ -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>
|
@ -0,0 +1,4 @@
|
|||||||
|
import DefaultSider from './DefaultSider/index.vue';
|
||||||
|
import VerticalMixSider from './VerticalMixSider/index.vue';
|
||||||
|
|
||||||
|
export { DefaultSider, VerticalMixSider };
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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: [
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user