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

三原管理系统在线浏览: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...

相关推荐
敲代码的嘎仔17 分钟前
用 Redis 合并写 + DelayQueue 延迟检测,把视频播放进度写库频率降到 1/60
java·开发语言·数据库·redis·缓存·音视频·高并发
youyou-06061 小时前
AUTOSAR‑COM 常见问题 QA (1)
java·linux·服务器·单片机·汽车
谢亮_vipxieliang1 小时前
ValidX时间段验证详解:ISO 8601标准与简化格式
java·spring boot·后端·spring·spring cloud·hibernate
Lsetea1 小时前
Java 请求 HTTPS 报 PKIX path building failed:从证书链到 truststore 的完整排查
java·https·ssl证书·keytool·truststore
一拳不是超人1 小时前
一个没测暗色模式的 Bug,吃掉了我一半 谷歌扩展用户
前端·javascript·程序员
liangshanbo12151 小时前
React useState 函数式更新面试题整理
前端·react.js·前端框架
涛涛ing1 小时前
为什么你的页面在 Safari 上总出问题?Interop 2027 正在解决这个 20 年老毛病
前端
不一样的少年_1 小时前
WebP 压缩到底在干嘛?小白也能看懂的原理拆解
前端·后端·图片资源
问心无愧05131 小时前
ctf show web 177
前端·笔记
不一样的少年_2 小时前
PNG/JPG 如何变成 WebP?真相不是改后缀!
前端·后端·图片资源