侧边导航栏优化

master
wangjunjie 2025-12-08 17:39:55 +08:00
parent 206adfc16a
commit 4d2d06a46b
2 changed files with 137 additions and 111 deletions

View File

@ -1,104 +1,126 @@
import { import { ElButton, ElIcon, ElMenu, ElMenuItem, ElMenuItemGroup, ElSubMenu, type MenuItemRegistered } from "element-plus";
ElButton, import { elIcons } from "@/common/element/element.ts";
ElIcon, import AIcon from "@/components/a-icon/AIcon.tsx";
ElMenu, import type { IconName } from "@/components/a-icon/iconfont.ts";
ElMenuItem, import styles from "@/pages/a-frame/aaside.module.styl";
ElMenuItemGroup,
ElSubMenu,
type MenuItemRegistered,
} from 'element-plus'
import { elIcons } from '@/common/element/element.ts'
import AIcon from '@/components/a-icon/AIcon.tsx'
import type { IconName } from '@/components/a-icon/iconfont.ts'
import styles from '@/pages/a-frame/aaside.module.styl'
export interface Menu extends G.TreeNode { export interface Menu extends G.TreeNode {
// Id // Id
id: string id: string;
// 编码 // 编码
sn: string sn: string;
// 上级 Id; 层级为 1 的节点值为 0 // 上级 Id; 层级为 1 的节点值为 0
pid: string pid: string;
// 菜单名称 // 菜单名称
title: string title: string;
// 图标 // 图标
icon: string icon: string;
// 层级; >= 1 // 层级; >= 1
tier: number tier: number;
// 排序 // 排序
sort: number sort: number;
// 路由名称 // 路由名称
routeName: string routeName: string;
// 面包路径 // 面包路径
breadcrumb: string[] breadcrumb: string[];
// 类型 // 类型
menuCategory: 'Catalog' | 'Group' | 'Page' | 'SubPage' | 'Btn' menuCategory: "Catalog" | "Group" | "Page" | "SubPage" | "Btn";
// 子菜单 // 子菜单
children?: Menu[] children?: Menu[];
} }
export default defineComponent( export default defineComponent(
(props, {emit}) => { (props, { emit }) => {
const onMenuClick = (it: MenuItemRegistered) => emit('menuClick', it.index) let defaultId = "";
const renderMenu = (it: Menu) => { let path: any = props.defaultPath.split("/");
let renderChildNode: (() => VNode[] | undefined) | undefined = undefined path = path[path.length - 1];
if (it.children != null && it.children.length > 0) { props.menus.forEach((item) => {
renderChildNode = () => (it.children?.map(renderMenu)) if (item.routeName == path) {
defaultId = item.id;
} else if (item.children && item.children.length) {
item.children.forEach((item2) => {
if (item2.routeName == path) {
defaultId = item2.id;
} else if (item2.children && item2.children.length) {
item2.children.forEach((item3) => {
if (item3.routeName == path) {
defaultId = item3.id;
}
});
}
});
} }
let currentNode: VNode });
const onMenuClick = (it: MenuItemRegistered) => emit("menuClick", it.index);
const renderMenu = (it: Menu) => {
let renderChildNode: (() => VNode[] | undefined) | undefined = undefined;
if (it.children != null && it.children.length > 0) {
renderChildNode = () => it.children?.map(renderMenu);
}
let currentNode: VNode;
switch (it.menuCategory) { switch (it.menuCategory) {
case 'Catalog': { case "Catalog": {
currentNode = (<ElSubMenu index={it.id}> currentNode = (
{{ <ElSubMenu index={it.id}>
title: () => (<> {{
<AIcon class={styles.aIcon} name={it.icon as IconName}/> title: () => (
<span>{it.title}</span> <>
</>), <AIcon class={styles.aIcon} name={it.icon as IconName} />
default: renderChildNode, <span>{it.title}</span>
}} </>
</ElSubMenu>) ),
break default: renderChildNode,
}}
</ElSubMenu>
);
break;
} }
case 'Group': { case "Group": {
currentNode = (<ElMenuItemGroup title={it.title}> currentNode = (
{{ <ElMenuItemGroup title={it.title}>
default: renderChildNode, {{
}} default: renderChildNode,
</ElMenuItemGroup>) }}
break </ElMenuItemGroup>
);
break;
} }
case 'Page': { case "Page": {
currentNode = (<ElMenuItem index={it.id} onClick={onMenuClick}> currentNode = (
{{ <ElMenuItem index={it.id} onClick={onMenuClick}>
title: () => <span>{it.title}</span>, {{
default: () => <AIcon class={styles.aIcon} name={it.icon as IconName}/>, title: () => <span>{it.title}</span>,
}} default: () => <AIcon class={styles.aIcon} name={it.icon as IconName} />,
</ElMenuItem>) }}
break </ElMenuItem>
);
break;
} }
default: default:
currentNode = (<></>) currentNode = <></>;
} }
return currentNode return currentNode;
} };
const isCollapse = ref(false) const isCollapse = ref(false);
return () => (<> return () => (
<ElMenu collapse={isCollapse.value} style={{height: '100%', overflow: 'auto', '--el-menu-base-level-padding': '10px'}} class={[ styles.aMenus, 'menus' ]}> <>
{{ <ElMenu default-active={defaultId} collapse={isCollapse.value} style={{ height: "100%", overflow: "auto", "--el-menu-base-level-padding": "10px" }} class={[styles.aMenus, "menus"]}>
default: () => props.menus.map(renderMenu), {{
}} default: () => props.menus.map(renderMenu),
</ElMenu> }}
<ElButton style={{position: 'absolute', right: '6px', bottom: '6px', width: '32px', height: '32px'}} onClick={() => { </ElMenu>
isCollapse.value = !isCollapse.value <ElButton
}}> style={{ position: "absolute", right: "6px", bottom: "6px", width: "32px", height: "32px" }}
<ElIcon style={{cursor: 'pointer'}}> onClick={() => {
{ isCollapse.value = !isCollapse.value;
isCollapse.value ? <elIcons.Fold/> : <elIcons.Expand/> }}
} >
</ElIcon> <ElIcon style={{ cursor: "pointer" }}>{isCollapse.value ? <elIcons.Fold /> : <elIcons.Expand />}</ElIcon>
</ElButton> </ElButton>
</>) </>
);
}, },
{ {
props: { props: {
@ -107,10 +129,14 @@ export default defineComponent(
required: true, required: true,
validator: (value: Menu[]) => value != null && value.length > 0, validator: (value: Menu[]) => value != null && value.length > 0,
}, },
defaultPath: {
type: String,
required: true,
},
}, },
emits: { emits: {
menuClick: (id: string) => id != null, menuClick: (id: string) => id != null,
}, },
name: 'AAside', name: "AAside",
}) }
);

View File

@ -1,63 +1,64 @@
<script lang="ts" setup> <script lang="ts" setup>
import AAside from '@/pages/a-frame/AAside.tsx' import AAside from "@/pages/a-frame/AAside.tsx";
import AAvatar from '@/pages/a-frame/AAvatar.vue' import AAvatar from "@/pages/a-frame/AAvatar.vue";
import { appName } from '@/common' import { appName } from "@/common";
import Colls from '@/common/utils/colls.ts' import Colls from "@/common/utils/colls.ts";
import { MenuCategory } from '@/common/app/constants.ts' import { MenuCategory } from "@/common/app/constants.ts";
import { useAppSettingStore } from '@/common/app/app-setting-store.ts' import { useAppSettingStore } from "@/common/app/app-setting-store.ts";
import Nav from '@/common/router/nav.ts' import Nav from "@/common/router/nav.ts";
import Evt from '@/common/utils/evt.ts' import Evt from "@/common/utils/evt.ts";
import { useRouter } from "vue-router";
const appSettingStore = useAppSettingStore() const appSettingStore = useAppSettingStore();
const id_menu_map = computed(() => { const id_menu_map = computed(() => {
return Colls.keyObj( return Colls.keyObj(
appSettingStore.menus.filter((it) => it.menuCategory === MenuCategory.Page || it.menuCategory === MenuCategory.Group || it.menuCategory === MenuCategory.Catalog), appSettingStore.menus.filter((it) => it.menuCategory === MenuCategory.Page || it.menuCategory === MenuCategory.Group || it.menuCategory === MenuCategory.Catalog),
(it) => it.id, (it) => it.id,
(it) => it, (it) => it
) );
}) });
function onMenuClick(id: string) { function onMenuClick(id: string) {
const menu = id_menu_map.value[id] const menu = id_menu_map.value[id];
Nav.open({ Nav.open({
insId: menu?.routeName ?? '', insId: menu?.routeName ?? "",
routeName: menu?.routeName ?? '', routeName: menu?.routeName ?? "",
}) });
} }
const menuTree = computed(() => { const menuTree = computed(() => {
return Colls.toTree( return Colls.toTree(appSettingStore.menus.filter((it) => it.menuCategory === MenuCategory.Page || it.menuCategory === MenuCategory.Group || it.menuCategory === MenuCategory.Catalog).sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0)));
appSettingStore.menus });
.filter((it) => it.menuCategory === MenuCategory.Page || it.menuCategory === MenuCategory.Group || it.menuCategory === MenuCategory.Catalog)
.sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0)), const router = useRouter();
) const defaultPath= ref<string>(router.currentRoute.value.path);
})
onMounted(() => { onMounted(() => {
Evt.emit('connect_ws') Evt.emit("connect_ws");
}) });
onUnmounted(() => { onUnmounted(() => {
Evt.emit('disconnect_ws') Evt.emit("disconnect_ws");
}) });
</script> </script>
<template> <template>
<ElContainer class="a-frame"> <ElContainer class="a-frame">
<ElHeader> <ElHeader>
<div> <div>
<img alt="" src="@/assets/images/循环.svg"/> <img alt="" src="@/assets/images/循环.svg" />
<div>{{ appName }}</div> <div>{{ appName }}</div>
</div> </div>
<AAvatar/> <AAvatar />
</ElHeader> </ElHeader>
<ElContainer> <ElContainer>
<ElAside> <ElAside>
<AAside :menus="menuTree" @menu-click="onMenuClick"/> <AAside :defaultPath="defaultPath" :menus="menuTree" @menu-click="onMenuClick" />
</ElAside> </ElAside>
<ElMain> <ElMain>
<RouterView #="{ Component }"> <RouterView #="{ Component }">
<Transition name="el-fade-in-linear"> <Transition name="el-fade-in-linear">
<component :is="Component"/> <component :is="Component" />
</Transition> </Transition>
</RouterView> </RouterView>
</ElMain> </ElMain>
@ -118,7 +119,6 @@ onUnmounted(() => {
} }
} }
} }
</style> </style>
<style> <style>
.menus:not(.el-menu--collapse) { .menus:not(.el-menu--collapse) {