菜单组件

master
lzq 2025-12-20 16:12:39 +08:00
parent 4b65fb8d5f
commit e2d1e04b38
3 changed files with 63 additions and 110 deletions

View File

@ -11,6 +11,12 @@ import { elIcons } from '@/common/element/element.ts'
import AIcon from '@/components/a-icon/AIcon.vue' import AIcon from '@/components/a-icon/AIcon.vue'
import type { IconName } from '@/components/a-icon/iconfont.ts' import type { IconName } from '@/components/a-icon/iconfont.ts'
import styles from '@/pages/a-frame/aaside.module.styl' import styles from '@/pages/a-frame/aaside.module.styl'
import { useAppSettingStore } from '@/common/app/app-setting-store.ts'
import Colls from '@/common/utils/colls.ts'
import { MenuCategory } from '@/common/app/constants.ts'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
import Nav from '@/common/router/nav.ts'
export interface Menu extends G.TreeNode { export interface Menu extends G.TreeNode {
// Id // Id
@ -32,43 +38,42 @@ export interface Menu extends G.TreeNode {
// 面包路径 // 面包路径
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 }) => { () => {
let defaultId = ""; const router = useRouter()
let path: any = props.defaultPath.split("/"); const appSettingStore = useAppSettingStore()
path = path[path.length - 1]; const defaultActive = ref('')
props.menus.forEach((item) => { const isCollapse = ref(false)
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;
}
});
}
});
}
});
const onMenuClick = (it: MenuItemRegistered) => emit("menuClick", it.index); onMounted(() => {
const renderMenu = (it: Menu) => { const currentRouteName = router.currentRoute.value.name
let renderChildNode: (() => VNode[] | undefined) | undefined = undefined; defaultActive.value = appSettingStore.menus.find(it => it.routeName === currentRouteName)?.id
if (it.children != null && it.children.length > 0) { })
renderChildNode = () => it.children?.map(renderMenu);
const menuTree = computed(() => {
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)))
})
const onMenuClick = (menuItem: MenuItemRegistered) => {
const menu = appSettingStore.menus.find(it => it.id === menuItem.index)
Nav.open({
insId: menu?.routeName ?? '',
routeName: menu?.routeName ?? '',
})
} }
let currentNode: VNode; 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 = ( currentNode = (
<ElSubMenu index={it.id}> <ElSubMenu index={it.id}>
{{ {{
@ -81,20 +86,20 @@ export default defineComponent(
default: renderChildNode, default: renderChildNode,
}} }}
</ElSubMenu> </ElSubMenu>
); )
break; break
} }
case "Group": { case 'Group': {
currentNode = ( currentNode = (
<ElMenuItemGroup title={it.title}> <ElMenuItemGroup title={it.title}>
{{ {{
default: renderChildNode, default: renderChildNode,
}} }}
</ElMenuItemGroup> </ElMenuItemGroup>
); )
break; break
} }
case "Page": { case 'Page': {
currentNode = ( currentNode = (
<ElMenuItem index={it.id} onClick={onMenuClick}> <ElMenuItem index={it.id} onClick={onMenuClick}>
{{ {{
@ -102,49 +107,34 @@ export default defineComponent(
default: () => <AIcon class={styles.aIcon} name={it.icon as IconName}/>, default: () => <AIcon class={styles.aIcon} name={it.icon as IconName}/>,
}} }}
</ElMenuItem> </ElMenuItem>
); )
break; break
} }
default: default:
currentNode = <></>; currentNode = <></>
}
return currentNode
} }
return currentNode;
};
const isCollapse = ref(false);
return () => ( return () => (
<> <>
<ElMenu default-active={defaultId} collapse={isCollapse.value} style={{ height: "100%", overflow: "auto", "--el-menu-base-level-padding": "10px" }} class={[styles.aMenus, "menus"]}> <ElMenu default-active={defaultActive.value} collapse={isCollapse.value} style={{height: '100%', overflow: 'auto', '--el-menu-base-level-padding': '10px'}} class={[ styles.aMenus, 'menus' ]}>
{{ {{
default: () => props.menus.map(renderMenu), default: () => menuTree.value.map(renderMenu),
}} }}
</ElMenu> </ElMenu>
<ElButton <ElButton
style={{ position: "absolute", right: "6px", bottom: "6px", width: "32px", height: "32px" }} style={{position: 'absolute', right: '6px', bottom: '6px', width: '32px', height: '32px'}}
onClick={() => { onClick={() => {
isCollapse.value = !isCollapse.value; isCollapse.value = !isCollapse.value
}} }}
> >
<ElIcon style={{ cursor: "pointer" }}>{isCollapse.value ? <elIcons.Fold /> : <elIcons.Expand />}</ElIcon> <ElIcon style={{cursor: 'pointer'}}>{isCollapse.value ? <elIcons.Fold/> : <elIcons.Expand/>}</ElIcon>
</ElButton> </ElButton>
</> </>
); )
}, },
{ {
props: { name: 'AAside',
menus: {
type: Object as PropType<Menu[]>,
required: true,
validator: (value: Menu[]) => value != null && value.length > 0,
}, },
defaultPath: { )
type: String,
required: true,
},
},
emits: {
menuClick: (id: string) => id != null,
},
name: "AAside",
}
);

View File

@ -112,15 +112,7 @@ function logoutHandler() {
.el-tabs__nav { .el-tabs__nav {
width 100% width 100%
justify-content space-around justify-content space-around
position relative /*&::before { position relative
content ''
position absolute
left 50%
background-color #D3D7DE
width 2px
height 100%
box-sizing border-box
}*/
} }
} }

View File

@ -2,44 +2,15 @@
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 { MenuCategory } from '@/common/app/constants.ts'
import { useAppSettingStore } from '@/common/app/app-setting-store.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 id_menu_map = computed(() => {
return Colls.keyObj(
appSettingStore.menus.filter((it) => it.menuCategory === MenuCategory.Page || it.menuCategory === MenuCategory.Group || it.menuCategory === MenuCategory.Catalog),
(it) => it.id,
(it) => it
);
});
function onMenuClick(id: string) {
const menu = id_menu_map.value[id];
Nav.open({
insId: menu?.routeName ?? "",
routeName: menu?.routeName ?? "",
});
}
const menuTree = computed(() => {
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)));
});
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>
@ -53,7 +24,7 @@ onUnmounted(() => {
</ElHeader> </ElHeader>
<ElContainer> <ElContainer>
<ElAside> <ElAside>
<AAside :defaultPath="defaultPath" :menus="menuTree" @menu-click="onMenuClick" /> <AAside/>
</ElAside> </ElAside>
<ElMain> <ElMain>
<RouterView #="{ Component }"> <RouterView #="{ Component }">
@ -80,6 +51,7 @@ onUnmounted(() => {
border-bottom 1px solid #E5E7EB; border-bottom 1px solid #E5E7EB;
height 60px height 60px
background-color white background-color white
& > div:first-child { & > div:first-child {
height: 100%; height: 100%;
display: flex; display: flex;
@ -115,7 +87,6 @@ onUnmounted(() => {
padding 5px padding 5px
overflow auto overflow auto
background-color #F7F9FC background-color #F7F9FC
//box-shadow: inset rgba(0, 0, 0, 0.12) 0px 0px 12px 0px;
} }
} }
} }