RuoYi-Vue-Plus (系统菜单路由获取、路由生成逻辑、前端按钮控制、自定义权限角色组件)

时间:2024-12-20 07:15:33

一、router 获取系统路由菜单

1-后端生成菜单方法
getRouters 方法位于 类中,

作用:根据角色获取菜单

  1. @GetMapping("getRouters")
  2. public R<List<RouterVo>> getRouters() {
  3. Long userId = ();
  4. List<SysMenu> menus = (userId);
  5. return ((menus));
  6. }

返回新构建,VO菜单集合逻辑

  1. /**
  2. * 构建前端路由所需要的菜单
  3. *
  4. * @param menus 菜单列表
  5. * @return 路由列表
  6. */
  7. @Override
  8. public List<RouterVo> buildMenus(List<SysMenu> menus) {
  9. List<RouterVo> routers = new LinkedList<>();
  10. for (SysMenu menu : menus) {
  11. RouterVo router = new RouterVo();
  12. ("1".equals(())); //是否显示
  13. (getRouteName(menu));
  14. (getRouterPath(menu));//设置路由地址
  15. (getComponent(menu));//设置组件路径
  16. (());//设置请求参数
  17. (new MetaVo((), (), ("1", ()), ()));//设置其他信息
  18. List<SysMenu> cMenus = ();
  19. //2-菜单类型是目录 不跳转
  20. if ((cMenus) && UserConstants.TYPE_DIR.equals(())) {
  21. (true);//是否显示
  22. ("noRedirect");//不重定向
  23. (buildMenus(cMenus));//递归子节点
  24. } else if (isMenuFrame(menu)) {
  25. //3-如果内部跳转
  26. (null);
  27. List<RouterVo> childrenList = new ArrayList<>();
  28. RouterVo children = new RouterVo();
  29. (());
  30. (());
  31. ((()));
  32. (new MetaVo((), (), ("1", ()), ()));
  33. (());
  34. (children);
  35. (childrenList);
  36. } else if (().intValue() == 0 && isInnerLink(menu)) {
  37. //4- ,是否内链跳转,菜单目录为1级
  38. (new MetaVo((), ()));
  39. ("/");
  40. List<RouterVo> childrenList = new ArrayList<>();
  41. RouterVo children = new RouterVo();
  42. String routerPath = innerLinkReplaceEach(());
  43. (routerPath);
  44. (UserConstants.INNER_LINK);
  45. ((routerPath));
  46. (new MetaVo((), (), ()));
  47. (children);
  48. (childrenList);
  49. }
  50. (router);
  51. }
  52. return routers;
  53. }
2-前端调用getRouters 方法,生成路由:

src\api\中,定义了get请求

  1. import request from '@/utils/request'
  2. // 获取路由
  3. export const getRouters = () => {
  4. return request({
  5. url: '/getRouters',
  6. method: 'get'
  7. })
  8. }

vuex中发送请求,GenerateRoutes 是生成路由的方法

  1. actions: {
  2. // 生成路由
  3. GenerateRoutes({ commit }) {
  4. return new Promise(resolve => {
  5. // 向后端请求路由数据
  6. getRouters().then(res => {
  7. const sdata = JSON.parse(JSON.stringify(res.data))
  8. const rdata = JSON.parse(JSON.stringify(res.data))
  9. //处理菜单类型,根据组件类型渲染菜单组件
  10. const sidebarRoutes = filterAsyncRouter(sdata)
  11. //比上面多了参数,此处是对子路由进行处理(额外多了,拼接路由路径)
  12. const rewriteRoutes = filterAsyncRouter(rdata, false, true)
  13. //
  14. const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
  15. rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
  16. router.addRoutes(asyncRoutes);
  17. // 设置到状态管理中去
  18. commit('SET_ROUTES', rewriteRoutes)
  19. // 设置侧边栏路由
  20. commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
  21. // 设置默认路由
  22. commit('SET_DEFAULT_ROUTES', sidebarRoutes)
  23. // 顶部菜单路由
  24. commit('SET_TOPBAR_ROUTES', sidebarRoutes)
  25. resolve(rewriteRoutes)
  26. })
  27. })
  28. }
  29. }

而生成路由GenerateRoutes方法:src\ 前置路由守卫中(之前文章有写过路由守卫的逻辑)

最后在,rc\router\ 中导出路由

  1. //2-导出路由的实例
  2. export default new Router({
  3. base: process.env.VUE_APP_CONTEXT_PATH,
  4. mode: 'history', // 去掉url中的#
  5. scrollBehavior: () => ({ y: 0 }),
  6. routes: constantRoutes
  7. })

二、侧边菜单和顶部菜单生成

src\layout\index.vue,主页面入口

  1. <template>
  2. <div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
  3. <div v-if="device==='mobile'&&" class="drawer-bg" @click="handleClickOutside"/>
  4. <!-- 侧边 -->
  5. <sidebar v-if="!" class="sidebar-container"/>
  6. <div :class="{hasTagsView:needTagsView,sidebarHide:}" class="main-container">
  7. <div :class="{'fixed-header':fixedHeader}">
  8. <navbar/>
  9. <tags-view v-if="needTagsView"/>
  10. </div>
  11. <!-- 主页面 -->
  12. <app-main/>
  13. <!-- 右边面板 -->
  14. <right-panel>
  15. <settings/>
  16. </right-panel>
  17. </div>
  18. </div>
  19. </template>

 点击侧边组件,进入 src\layout\components\Sidebar\ 组件中,渲染侧边栏

  1. <template>
  2. <!--1- el-menu渲染菜单 log 、主题等-->
  3. <div :class="{'has-logo':showLogo}" :style="{ backgroundColor: === 'theme-dark' ? : }">
  4. <logo v-if="showLogo" :collapse="isCollapse" />
  5. <el-scrollbar :class="" wrap-class="scrollbar-wrapper">
  6. <el-menu
  7. :default-active="activeMenu"
  8. :collapse="isCollapse"
  9. :background-color=" === 'theme-dark' ? : "
  10. :text-color=" === 'theme-dark' ? : "
  11. :unique-opened="true"
  12. :active-text-color=""
  13. :collapse-transition="false"
  14. mode="vertical"
  15. >
  16. <!--2- 侧边栏 -->
  17. <sidebar-item
  18. v-for="(route, index) in sidebarRouters"
  19. :key=" + index"
  20. :item="route"
  21. :base-path=""
  22. />
  23. </el-menu>
  24. </el-scrollbar>
  25. </div>
  26. </template>

 点击注释2 ,进入侧边栏src\layout\components\Sidebar\,渲染菜单

  1. <template>
  2. <div v-if="!">
  3. <!-- 1 - 只有一个菜单时候(不需要展开),页面跳转-->
  4. <template v-if="hasOneShowingChild(,item) && (!||)&&!">
  5. <app-link v-if="" :to="resolvePath(, )">
  6. <el-menu-item :index="resolvePath()" :class="{'submenu-title-noDropdown':!isNest}">
  7. <item :icon="||(&&)" :title="" />
  8. </el-menu-item>
  9. </app-link>
  10. </template>
  11. <!-- 2-有子菜单的时间,递归显示(展开子节点) -->
  12. <el-submenu v-else ref="subMenu" :index="resolvePath()" popper-append-to-body>
  13. <template slot="title">
  14. <item v-if="" :icon=" && " :title="" />
  15. </template>
  16. <sidebar-item
  17. v-for="child in "
  18. :key=""
  19. :is-nest="true"
  20. :item="child"
  21. :base-path="resolvePath()"
  22. class="nest-menu"
  23. />
  24. </el-submenu>
  25. </div>
  26. </template>

 Navbar 导航栏  src\layout\components\

  1. <template>
  2. <div class="navbar">
  3. <!-- 1- 是否水平折叠 -->
  4. <hamburger id="hamburger-container" :is-active="" class="hamburger-container" @toggleClick="toggleSideBar" />
  5. <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
  6. <!-- 2- 是否顶部导航 -->
  7. <top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
  8. 。。。。。。。。。。。。。。
  9. </div>
  10. </template>

三、前端按钮控制、自定义权限角色组件

admin 和 普通用户的对比,权限方面的差距。

下面我以用户管理模块,来对比权限是怎么来控制的。

http://localhost/dev-api/getRouters 只是获取菜单。(用户关联角色--> 角色关联菜单)

  1. {
  2. "code": 200,
  3. "msg": "操作成功",
  4. "data": [
  5. {
  6. "name": "System",
  7. "path": "/system",
  8. "hidden": false,
  9. "redirect": "noRedirect",
  10. "component": "Layout",
  11. "alwaysShow": true,
  12. "meta": {
  13. "title": "系统管理",
  14. "icon": "system",
  15. "noCache": false,
  16. "link": null
  17. },
  18. "children": [
  19. {
  20. "name": "User",
  21. "path": "user",
  22. "hidden": false,
  23. "component": "system/user/index",
  24. "meta": {
  25. "title": "用户管理",
  26. "icon": "user",
  27. "noCache": false,
  28. "link": null
  29. }
  30. }
  31. ]
  32. }
  33. ]
  34. }

下面我们看下admin页面上的新增 修改 删除 导入 导出 几个按钮是怎么控制权限的。

看下用户管理的页面路径 : src\views\system\user\,中判断权限的代码:  v-hasPermi="['system:user:add']"

  1. <el-col :span="1.5">
  2. <el-button
  3. type="primary"
  4. plain
  5. icon="el-icon-plus"
  6. size="mini"
  7. @click="handleAdd"
  8. v-hasPermi="['system:user:add']"
  9. >新增</el-button>
  10. </el-col>

 其实就是在菜单管理权限表标识字段控制

 

v-hasPermi自定义指令的路径 src\directive\,自定义代码注释如下:

  1. // 自定义指令
  2. // 是否有角色
  3. import hasRole from './permission/hasRole'
  4. // 是否有权限
  5. import hasPermi from './permission/hasPermi'
  6. // 弹窗拖拽
  7. import dialogDrag from './dialog/drag'
  8. // 拖拽弹窗高度 宽度
  9. import dialogDragWidth from './dialog/dragWidth'
  10. import dialogDragHeight from './dialog/dragHeight'
  11. // 剪辑板
  12. import clipboard from './module/clipboard'
  13. // 引入的模块声明为自定义指令
  14. const install = function(Vue) {
  15. Vue.directive('hasRole', hasRole)
  16. Vue.directive('hasPermi', hasPermi)
  17. Vue.directive('clipboard', clipboard)
  18. Vue.directive('dialogDrag', dialogDrag)
  19. Vue.directive('dialogDragWidth', dialogDragWidth)
  20. Vue.directive('dialogDragHeight', dialogDragHeight)
  21. }
  22. if (window.Vue) {
  23. window['hasRole'] = hasRole
  24. window['hasPermi'] = hasPermi
  25. Vue.use(install); // eslint-disable-line
  26. }
  27. // 导出
  28. export default install

然后在src\中,导入组件并使用

  1. // 引入自定组件
  2. import directive from './directive' // directive
  3. Vue.use(directive)

 最后看下 v-hasPermi 里面的逻辑

  1. // 1- 引入状态管理
  2. import store from '@/store'
  3. // 2- 创建指令
  4. export default {
  5. // 3- 监听权限变化, inserted自定义指令的钩子函数(el:指令操作的元素,binding:指令绑定的操作对象,vnode:vue生成虚拟节点用于对当前节点以及子节点操作)
  6. inserted(el, binding, vnode) {
  7. const { value } = binding
  8. const all_permission = "*:*:*";//超级管理员
  9. const permissions = store.getters && store.getters.permissions // 获取权限,这边权限是在 中获取
  10. if (value && value instanceof Array && value.length > 0) {
  11. const permissionFlag = value
  12. // 遇到符合的就不会继续往下执行,hasPermissions = true表示有权限,反正则没有
  13. const hasPermissions = permissions.some(permission => {
  14. return all_permission === permission || permissionFlag.includes(permission)
  15. })
  16. // 5-没有权限则移除节点
  17. if (!hasPermissions) {
  18. el.parentNode && el.parentNode.removeChild(el)
  19. }
  20. } else {
  21. throw new Error(`请设置操作权限标签值`)
  22. }
  23. }
  24. }