三原管理系统在线浏览:sanyuan.website/
免费开源仓库地址: gitee.com/hejingyuan0...
在上篇文章中我们简单介绍了我研发的三原管理系统 :用户管理 、菜单管理 、角色管理 、白色/暗色主题 、主题色可自适应 、国际化等等功能。
其中里面有2种布局:侧边栏布局 、垂直布局 ,相比较适合菜单比较少的系统

本次新增两种布局:混合布局 、侧边双栏布局 ,相比来说这种布局更适合菜单项 比较多的场景


并且修复了日志管理获取IP地址的问题,相信大家登录完成后就能看到了。
4种布局菜单支持我们究竟是怎么实现的呢?我们下面一起来看看
多种菜单布局下的思考
- 如何让每个菜单项都根据路由高亮选中?多栏菜单就处理多次吗?
- 如何设计数据结构?点击了两栏布局的第一级(父级)需要默认跳到子级的路由
- 根据选中的一级,动态获取二级数据
- 一定要封装
hooks(useMenu),在各个地方使用,不然每个地方都处理一下会非常麻烦
如何设计这个hook(useMenu)
水平布局: 获取全部菜单;
侧边栏布局: 获取全部菜单;
混合布局: 头部获取一级菜单,侧边栏或者顶部对应选中菜单的剩余菜单;
侧边双栏布局: 第一栏获取一级菜单,第二栏获取对应选中菜单下的剩余菜单;
混合侧边双栏布局(待实现,适用菜单比较多的项目):top获取一级菜单,侧边第一级获取1级数据、侧边二级获取剩余的;
-
- 提供全部菜单数据给水平布局 、侧边栏布局 使用
-
selectedKeys用来高亮选中菜单
-
leave1Menu用来渲染一级菜单数据
-
leave2AndSubLevel用来渲染二级以及子级数据
-
handleMenuClick统一处理菜单跳转
js
export const useMenu = () => {
const route = useRoute()
const router = useRouter()
const layoutStore = useLayoutStore()
const { menuPermission } = usePermissionStore() // 根据用户角色获取到的菜单原始数据
const allMenu = ref(formatDynamicRouter(menuPermission)) // 格式化成菜单数据结构
const selectedKeys = ref([])
const leave1Menu = ref() /
const leave2AndSubLevel = ref([]) // 2级以及其他
const handleMenuClick = () => {}
return {
allMenu,
selectedKeys,
leave1Menu,
leave2AndSubLevel,
handleMenuClick,
}
}
如何让多栏菜单都选中高亮

我写死也行就在一级菜单查询下,二级菜单查询、三级还查询....
上面那种方案过于麻烦了,我直接获取当前路由的整个链路
js
<a-menu
v-if="isShowHorizontal && currentItems.length"
v-model:selectedKeys="selectedKeys"
mode="horizontal"
:items="currentItems"
:key="currentItems"
@click="handleMenuClick"
></a-menu>
selectedKeys是这个样子的数据结构:获取到整个路径['/system', '/system/log', '/system/log/XXXX']
写一个递归获取路径的方法 要根据currentRouteName查询 因为会有url上有params的
js
const getCurrentPathLinks = (data = [], currentName = '') => {
// ps: 获取到整个路径['/system', '/system/log', '/system/log/XXXX']
const results = []
data.forEach((item) => {
if (isIncludesPath(item, currentName) && !item.hidden) {
results.push(item.path)
}
if (item?.children?.length) {
results.push(...getCurrentPathLinks(item.children, currentName))
}
})
return results
}
export const useMenu = () => {
const route = useRoute()
const router = useRouter()
const layoutStore = useLayoutStore()
const { menuPermission } = usePermissionStore() // 根据用户角色获取到的菜单原始数据
const allMenu = ref(formatDynamicRouter(menuPermission)) // 格式化成菜单数据结构
const selectedKeys = ref([])
const leave1Menu = ref() /
const leave2AndSubLevel = ref([]) // 2级以及其他
const handleMenuClick = () => {}
watch(
() => route.path,
() => {
const currentName = route.name
// 根据当前的路由,直接获取整个链路 方便各个菜单选中
selectedKeys.value = getCurrentPathLinks(menuPermission, currentName)
},
{ immediate: true },
)
return {
allMenu,
selectedKeys,
leave1Menu,
leave2AndSubLevel,
handleMenuClick,
}
}
['/system', '/system/log', '/system/log/XXXX']菜单组件本身支持数组的形式高亮,在每个地方都直接使用就行,后面想上混合双栏布局就不用做更改
处理一级数据以及菜单点击处理
把有children的一级数据处理掉,并且点击需要跳转到的子级数据,在一级增加toChildrenPath字段
js
const getFirstChildrenMenu = (data = []) => {
// 找到非菜单目录的数据
for (let i = 0; i < data.length; i++) {
if (!data[i]?.children) {
return data[i]
}
const item = getFirstChildrenMenu(data[i].children)
if (item) {
return item
}
}
}
export const useMenu = () => {
// 使用全部的数据 map出来第一级数据,处理children,并且查找到toChildrenPath字段
const leave1Menu = ref(
allMenu.value.map((item) => {
if (item?.children?.length) {
return {
...item,
toChildrenPath: getFirstChildrenMenu(item.children)?.path,
children: null,
}
}
return item
}),
)
const handleMenuClick = (e) => {
if (e.item.isFrame) {
window.open(e.key)
} else {
// 点击一级 优先跳子级
router.push(e?.item?.toChildrenPath || e.key)
}
}
return {
allMenu,
selectedKeys,
leave1Menu,
leave2AndSubLevel,
handleMenuClick,
}
}
leave2AndSubLevel 二级子级数据处理
只有在侧边双栏布局 、以及混合布局需要处理查询
js
const isIncludesPath = (item, currentName) => {
// 因为有params 参数 导致匹配不到的情况,改用name值判断
if (item.name === currentName) {
return true
}
return (item?.children || []).some((item) => {
return isIncludesPath(item, currentName)
})
}
if (
layoutStore.layoutConfig.mode === LAYOUT_MODE.MIXED ||
layoutStore.layoutConfig.mode === LAYOUT_MODE.TOW_SIDE
) {
// 根据当前选中的路由查询 leave2AndSubLevel 数据
leave2AndSubLevel.value = formatDynamicRouter(
menuPermission.find((item) => isIncludesPath(item, currentName))?.children || [],
)
}
总结
以上就是整个useMenu的设计,其实hooks确定后职责后,业务组件就照着写样式应用就行了。 给大家预留一个混合双栏布局可以想象怎么实现。 顶部一级数据、侧边两栏(一栏二级数据)、二栏(三级以及子集数据),这种布局适合那种大的后台管理使用。
useMenu完整代码: gitee.com/hejingyuan0...