Code前端首页关于Code前端联系我们

Vue3后台管理系统通用布局怎么做?有哪些好用的小技巧和避坑点?

terry 2天前 阅读数 454 #Vue

为什么通用后台布局要先从Vue3的Composition API+路由守卫+插槽入手?

刚从Vue2转过来或者第一次做后台系统的朋友,可能会直接复制别人的组件库示例,但往往用不了多久就发现想改侧边栏折叠动画、想加顶部面包屑联动权限、想实现路由懒加载缓存嵌套页面就麻烦,其实通用后台的核心逻辑,从来不是照搬Element Plus或Ant Design Vue的ProLayout,而是搞懂三个核心:用Composition API把布局的交互状态(比如侧边栏折叠、顶部标签页)抽成可复用的逻辑模块,减少重复代码;用路由前置/后置守卫处理权限跳转、面包屑动态生成这些全局触发的需求;用插槽给通用布局留足自定义空间,适配不同项目的顶部操作栏、侧边栏快捷入口之类的需求。

举个简单的例子,之前有个朋友用Ant Design Vue的ProLayout做电商后台,一开始用得挺顺,后来要加“侧边栏有新增按钮就高亮对应未展开菜单”的需求,翻了半天组件库文档都没找到合适的props或插槽,最后不得不直接覆盖组件的CSS类,还得写watch监听路由变化去加active状态,代码越堆越乱,后来换了自己基于插槽和Composition API写的轻量通用布局,半小时就搞定了这个需求。

轻量通用后台布局的整体结构应该怎么搭?

通用后台的经典布局无非是三种固定变体:顶部导航+侧边栏导航+内容区(最常用)、侧边栏全宽导航+顶部操作栏+内容区、侧边栏可折叠全宽+内容区,不管选哪种,我们可以先搭一个基础的“骨架”,然后通过变量控制变体。

基础骨架的文件结构建议这样设计:

src/
├── layout/
│   ├── index.vue          # 主布局入口
│   ├── components/
│   │   ├── Sidebar.vue    # 侧边栏组件(可复用,支持折叠、高亮、懒加载菜单图标)
│   │   ├── Topbar.vue     # 顶部栏组件(面包屑、用户信息、标签页)
│   │   ├── Tabbar.vue     # 可选,顶部标签页组件(缓存、关闭、刷新)
│   │   └── Content.vue    # 内容区容器(路由占位、keep-alive缓存逻辑)
│   ├── hooks/
│   │   ├── useLayout.js   # 抽离布局交互状态(折叠、主题色、是否显示标签页)
│   │   ├── useBreadcrumb.js # 抽离面包屑生成逻辑
│   │   ├── useSidebarMenu.js # 抽离侧边栏菜单权限过滤、树结构处理
│   │   └── useTabbar.js   # 抽离标签页操作逻辑
│   └── style/
│       ├── index.scss     # 主布局样式
│       └── variables.scss # 布局相关变量(侧边栏折叠前后宽度、主题色等)
└── router/
    └── index.js           # 路由配置,要包含meta信息(标题、是否需要缓存、是否显示侧边栏、权限)

这种分层设计的好处是,每个组件和逻辑模块只负责自己的功能,修改起来特别方便,比如要换侧边栏的图标库,只需要改Sidebar.vue和useSidebarMenu.js里的图标引入;要换主题色,只需要改variables.scss里的变量;要去掉标签页,只需要在主布局入口根据变量判断是否渲染Tabbar.vue。

主布局入口index.vue的核心代码逻辑是什么?

主布局入口的核心是组合各个子组件、传入/接收逻辑模块的状态,首先我们要引入抽好的useLayout、useBreadcrumb(如果面包屑在Topbar.vue里,也可以在Topbar.vue里引入,但主入口一起引入更方便调试全局依赖)、可能还要引入Vuex/Pinia的用户模块和权限模块(如果权限是全局管理的)。

然后是模板部分,要使用router-view的具名插槽吗?其实不一定,但如果项目有特殊需求,登录页不用侧边栏和顶部栏”“404页不用标签页”,用具名插槽会更灵活,不过经典的通用做法是,在路由配置里把不需要通用布局的页面(比如登录页、注册页、404页)放在layout外层,需要通用布局的页面作为layout的子路由,这样在主布局入口的模板里,就只需要放Sidebar.vue、Topbar.vue、Tabbar.vue、Content.vue这些组件,然后在Content.vue里放

Content.vue里的还要配合keep-alive,实现路由缓存功能,这里有个小坑,就是keep-alive的include属性,不能直接传字符串数组,最好传ref或computed的响应式数组,因为标签页关闭、新增的时候,要动态更新include数组,keep-alive的exclude属性如果和include属性同时用,include属性会生效,exclude属性会被忽略,这点一定要注意。

再来说说路由配置的meta信息,这个真的很重要,面包屑、侧边栏菜单权限过滤、是否需要缓存、标题更新都是靠meta信息实现的,比如meta信息可以这样设计:

{
  path: '/system',
  component: () => import('@/layout/index.vue'),
  meta: { title: '系统管理', icon: 'SettingOutlined' }, // 这里的icon可以用字符串和图标库的组件名对应,也可以直接传组件
  children: [
    {
      path: 'user',
      name: 'SystemUser',
      component: () => import('@/views/system/user/index.vue'),
      meta: { title: '用户管理', icon: 'UserOutlined', cache: true, permission: ['system:user:view'] }
    },
    {
      path: 'role',
      name: 'SystemRole',
      component: () => import('@/views/system/role/index.vue'),
      meta: { title: '角色管理', icon: 'TeamOutlined', cache: true, permission: ['system:role:view'] }
    }
  ]
}

这里的name属性也很重要,keep-alive的include属性就是靠name属性匹配组件的,所以每个需要缓存的页面组件都必须有唯一的name属性,而且要和路由配置里的name属性一致。

侧边栏组件Sidebar.vue的小技巧和避坑点?

侧边栏是通用后台里交互最多的组件之一,这里有几个好用的小技巧和避坑点。

第一个小技巧是侧边栏折叠动画的优化,很多朋友直接用transition组件加width的动画,但这样会导致侧边栏里的文字和图标出现卡顿,体验不好,更好的做法是,给侧边栏加min-width和width的属性,用CSS的transition过渡width,然后给侧边栏里的文字、二级菜单的箭头加opacity的过渡,当侧边栏折叠的时候,把min-width和width都设成折叠后的宽度,把文字和箭头的opacity设成0,把一级菜单的padding-left设成折叠后居中的宽度,这样动画会非常流畅。

第二个小技巧是侧边栏菜单的权限过滤和树结构处理,权限过滤最好不要在侧边栏组件里写循环判断,而是抽成useSidebarMenu.js这个逻辑模块,因为可能别的地方也需要用到过滤后的菜单树(比如顶部标签页的右键菜单“打开所有子菜单”),树结构处理的话,如果后端返回的菜单是扁平结构,要先转换成树结构;如果后端返回的是树结构,要过滤掉没有权限的节点,还要过滤掉没有子节点且没有component的节点(比如有些菜单只是分组,不需要跳转)。

第一个避坑点是侧边栏菜单的高亮逻辑,很多朋友直接用router-link的active-class属性,但这样有个问题,如果是嵌套路由,父路由和子路由都会高亮,而我们通常只需要高亮当前选中的子路由,或者高亮子路由对应的最近的展开的父路由,更好的做法是,不用router-link的active-class属性,而是自己写一个computed属性,判断当前路由的path或者name是否和菜单的path或者name匹配,如果是子路由,还要递归查找最近的展开的父路由,然后手动给对应的菜单加active类。

第二个避坑点是侧边栏菜单的懒加载图标,如果侧边栏菜单的图标是用Element Plus或Ant Design Vue的icon组件,而且菜单很多的话,一次性引入所有图标会导致首屏加载时间变长,更好的做法是,在useSidebarMenu.js里处理菜单的时候,用import()动态引入图标组件,或者用iconfont,这样只有当菜单展开或者渲染的时候,才会加载对应的图标。

第三个避坑点是侧边栏折叠后的悬浮菜单,很多朋友折叠侧边栏后,悬浮菜单是用CSS的hover属性实现的,但这样有个问题,如果鼠标移动得太快,悬浮菜单会消失,体验不好,更好的做法是,用Vue的teleport组件把悬浮菜单挂载到body上,然后用mouseenter和mouseleave事件加setTimeout和clearTimeout,控制悬浮菜单的显示和隐藏,延迟时间一般设成200ms到300ms比较合适。

顶部栏组件Topbar.vue的小技巧和避坑点?

顶部栏组件主要包括面包屑、用户信息、全局搜索、主题切换、全屏切换、通知中心这些功能,这里有几个好用的小技巧和避坑点。

第一个小技巧是面包屑的动态生成,面包屑的动态生成最好抽成useBreadcrumb.js这个逻辑模块,靠路由配置里的meta信息和$route.matched数组实现。$route.matched数组是一个包含当前路由及其所有父路由的数组,我们可以遍历这个数组,过滤掉不需要显示在面包屑里的路由(比如有些路由只是重定向),然后把每个路由的meta.title拼接起来,就是面包屑了,面包屑的最后一项通常不需要加链接,我们可以在遍历的时候判断一下是不是最后一项,如果是,就不用router-link,直接用span。

第二个小技巧是全局搜索功能,全局搜索功能可以搜索侧边栏的菜单,也可以搜索内容区的页面数据,搜索侧边栏菜单的话,可以用computed属性监听搜索框的输入,然后过滤抽好的菜单树,过滤的时候要递归查找子节点,如果子节点匹配,也要把父节点一起返回来,这样搜索结果才能正确显示树结构,搜索结果可以用一个下拉框展示,点击下拉框的选项可以跳转到对应的路由。

第一个避坑点是面包屑的路由匹配,很多朋友在遍历$route.matched数组的时候,直接用路由的path作为router-link的to属性,但这样有个问题,如果路由是动态路由(user/:id),$route.matched数组里的path会是/user/123,而我们通常希望面包屑里的/user链接是跳转到/user(用户列表页),而不是跳转到/user/123(用户详情页),更好的做法是,在路由配置里给需要作为面包屑链接的父路由加一个redirect属性,或者加一个meta.breadcrumbPath属性,然后在遍历的时候用这个属性作为router-link的to属性。

第二个避坑点是用户信息下拉框的关闭,很多朋友的用户信息下拉框是用CSS的hover属性实现的,或者用点击事件加v-if,但这样有个问题,如果点击了页面的其他地方,下拉框不会关闭,体验不好,更好的做法是,用Vue的自定义指令,监听页面的点击事件,如果点击的不是下拉框或触发下拉框的按钮,就关闭下拉框。

标签页组件Tabbar.vue的小技巧和避坑点?

标签页组件主要包括标签页的新增、关闭、刷新、右键菜单这些功能,这里有几个好用的小技巧和避坑点。

第一个小技巧是标签页的缓存逻辑,标签页的缓存逻辑和keep-alive的include属性配合使用,当标签页新增的时候,把标签页对应的页面组件的name属性添加到include数组里;当标签页关闭的时候,把标签页对应的页面组件的name属性从include数组里移除;当标签页刷新的时候,先把标签页对应的页面组件的name属性从include数组里移除,然后过一会儿再添加回去,这样页面就会重新渲染。

第二个小技巧是标签页的右键菜单,标签页的右键菜单可以用Vue的teleport组件挂载到body上,然后用contextmenu事件和preventDefault阻止浏览器的默认右键菜单,用mouseenter和mouseleave事件或者v-model控制右键菜单的显示和隐藏,右键菜单的功能可以包括“刷新当前页”“关闭当前页”“关闭其他页”“关闭左侧页”“关闭右侧页”“关闭所有页”。

第一个避坑点是标签页的去重,很多朋友在标签页新增的时候,没有判断标签页是否已经存在,导致标签页重复,体验不好,更好的做法是,在标签页新增的时候,遍历标签页数组,判断标签页对应的路由path或者name是否已经存在,如果已经存在,就切换到对应的标签页,不再新增;如果不存在,就新增标签页。

第二个避坑点是标签页关闭后的路由跳转,很多朋友在标签页关闭的时候,没有处理路由跳转,导致标签页关闭了,但路由还停留在原来的页面,体验不好,更好的做法是,在标签页关闭的时候,判断关闭的是不是当前选中的标签页,如果是,就跳转到标签页数组里的前一个或后一个标签页;如果标签页数组里只剩下一个标签页(通常是首页),就不能关闭这个标签页,或者关闭后跳转到首页。

第三个避坑点是标签页的name属性,刚才在主布局入口里已经说过了,每个需要缓存的页面组件都必须有唯一的name属性,而且要和路由配置里的name属性一致,这点一定要注意,不然keep-alive的include属性就匹配不到组件,标签页的缓存功能就失效了。

Vue3后台管理系统的通用布局,其实并不难,只要搞懂Composition API+路由守卫+插槽这三个核心,再注意一些小技巧和避坑点,就能搭出一个轻量、灵活、可复用的通用布局,不要一开始就照搬组件库的ProLayout,组件库的ProLayout虽然功能强大,但往往定制化程度不高,修改起来特别麻烦,自己搭一个轻量通用布局,虽然前期需要花一点时间,但后期修改起来非常方便,而且能学到很多Vue3的核心知识。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门