附源码:三原管理系统新增俩种常用布局

三原管理系统在线浏览:sanyuan.website/

免费开源仓库地址: gitee.com/hejingyuan0...

在上篇文章中我们简单介绍了我研发的三原管理系统用户管理菜单管理角色管理白色/暗色主题主题色可自适应国际化等等功能。

其中里面有2种布局:侧边栏布局垂直布局 ,相比较适合菜单比较少的系统

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

并且修复了日志管理获取IP地址的问题,相信大家登录完成后就能看到了。

4种布局菜单支持我们究竟是怎么实现的呢?我们下面一起来看看

多种菜单布局下的思考

  • 如何让每个菜单项都根据路由高亮选中?多栏菜单就处理多次吗?
  • 如何设计数据结构?点击了两栏布局的第一级(父级)需要默认跳到子级的路由
  • 根据选中的一级,动态获取二级数据
  • 一定要封装hooks(useMenu),在各个地方使用,不然每个地方都处理一下会非常麻烦

如何设计这个hook(useMenu)

水平布局: 获取全部菜单;

侧边栏布局: 获取全部菜单;

混合布局: 头部获取一级菜单,侧边栏或者顶部对应选中菜单的剩余菜单;

侧边双栏布局: 第一栏获取一级菜单,第二栏获取对应选中菜单下的剩余菜单;

混合侧边双栏布局(待实现,适用菜单比较多的项目):top获取一级菜单,侧边第一级获取1级数据、侧边二级获取剩余的;

    1. 提供全部菜单数据给水平布局侧边栏布局 使用
    1. selectedKeys 用来高亮选中菜单
    1. leave1Menu 用来渲染一级菜单数据
    1. leave2AndSubLevel 用来渲染二级以及子级数据
    1. 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...

相关推荐
CodeGuru4 小时前
UniApp Vue3 生成海报并分享到朋友圈
前端
布局呆星4 小时前
Vue3 | 组件化开发---组件插槽与通信
前端·javascript·vue.js
DyLatte4 小时前
当我想把所有角色都做好时,就开始内耗了
前端·后端·程序员
現実君4 小时前
现代化嵌入式AI编程-IDEA指南
java·intellij-idea·ai编程
Java面试题总结4 小时前
2026年Java面试题最新整理,附白话答案
java·开发语言·jvm·笔记·spring·intellij-idea
芒果披萨4 小时前
日志管理 logging
java·开发语言·c++
a1117764 小时前
汽车展厅项目 开源项目 ThreeJS
前端·开源·html
阳火锅4 小时前
Element / AntD 官方都没做好的功能,被这个开源小插件搞定了!
前端·vue.js·面试
大阳光男孩4 小时前
Uniapp+Vue3树形选择器
前端·javascript·uni-app