(十五)VUE权限菜单之角色管理--基于SpringBoot+MySQL+Vue+ElementUI+Mybatis前后端分离面向小白管理系统搭建

任务十四 权限菜单之菜单管理

任务十五 权限菜单之角色管理

前面的任务我们完成了基于JWT验证的系统登录。任务十四-任务十六开始我将进行权限菜单模块的开发,也就是要实现根据不同角色赋予不同权限(可以访问的菜单和可以进行的操作),不同用户赋予不同角色,当用户登录时,不仅进行账号判断还需要获取不同用户对应不同角色的权限,即认证、授权;本次任务完成角色管理,主要是角色表的增删改查、角色分配菜单等,通过本次任务,大家能够:
(1)熟练掌握前面定义的统一接口规范使用;
(2)理解用户与角色、角色与权限(即菜单)的逻辑关系;
(3)熟练使用ElementUI树型菜单,进行权限菜单分配;

一、数据表设计

1. 数据表

这次任务把用户、角色、菜单、角色菜单的逻辑关系全部理论,所以把涉及到的四张数据表全部放出来。
在这里插入图片描述
(1)用户表中添加了role角色字段(是一个标识,比如admin的role为ROLE_ADMIN),这样在登录的时候就可以用这个角色标识获取他的权限菜单;

数据示例:

在这里插入图片描述

(2)菜单表与任务十四的一致;

数据示例:

在这里插入图片描述

(3)角色表的字段说明为:
在这里插入图片描述

数据示例:

在这里插入图片描述
(4)角色菜单表
这个表需要大家有些理解,通俗的来说就是哪个角色对应哪些菜单。但是由于都是使用id标识,所以,需要对应上相应菜单的id和相应角色的id理解。
在这里插入图片描述

数据示例:

在这里插入图片描述
比如:角色id为1的是管理员,对应的菜单id有:2、4、5、6、7、8、17、18、19。其中菜单id为4、5分别为系统管理和用户管理,那么管理员就可以访问“系统管理”和“菜单管理”这两个菜单,当某个用户的角色是管理员,那么他可以访问的菜单就是2、4、5、6、7、8、17、18、19对应的菜单。
下一个任务将实现动态路由设计,该用户登录系统后看到的菜单即是他的权限拥有的菜单,没有分配到的菜单看不到,也无法访问。
在这里插入图片描述

2. 实体类

为了便于对任务十四、任务十五、任务十六的理解,这里把前面做过的菜单、用户、以及登录类的实体类都列出来,如果延续前面任务的同学,只需要添加Role和RoleMenu 类。

(1)角色实体类 Role

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.sql.Date;

@Data
//可以使用 @TableName 表名注解指定当前实体类对应的表名,比如下面 Role 实体类对应表名为 sys_role
@TableName(value="sys_role")
public class Role {
    //可以使用 @TableId 注解(标注在主键上)和 @TableField 注解(标注在其他成员属性上)来指定对应的字段名
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String name;
    private String description;
    private String flag;
}

(2)角色菜单实体类RoleMenu

package com.example.demo.entity;


import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
//可以使用 @TableName 表名注解指定当前实体类对应的表名,比如下面 RoleMenu 实体类对应表名为 sys_role_menu
@TableName(value="sys_role_menu")
public class RoleMenu {
    private Integer roleId;
    private Integer menuId;
}

(3)菜单实体类Menu

package com.example.demo.entity;


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.List;

@Data
//可以使用 @TableName 表名注解指定当前实体类对应的表名,比如下面 Menu 实体类对应表名为 sys_menu
@TableName(value="sys_menu")
public class Menu {

    //可以使用 @TableId 注解(标注在主键上)和 @TableField 注解(标注在其他成员属性上)来指定对应的字段名
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String name;
    private String path;
    private String icon;
    private String description;

    //在数据表中没有children这个字段,这个在做菜单的时候会用到,所以使用exist=false忽略
    @TableField(exist = false)
    private List<Menu> children;

    private Integer pid;
    @TableField(value="page_path")//这样处理的主要目的是java对带有下划线的字段不识别,所以改为驼峰形式
    private String pagePath;
}

(4)用户实体类User

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.sun.javafx.beans.IDProperty;
import lombok.Data;

import java.sql.Date;


@Data
//可以使用 @TableName 表名注解指定当前实体类对应的表名,比如下面 User 实体类对应表名为 sys_user
@TableName(value="sys_user")
public class User {
    //可以使用 @TableId 注解(标注在主键上)和 @TableField 注解(标注在其他成员属性上)来指定对应的字段名
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private String email;
    private String phone;
    private String nickname;
    private String address;
    @TableField(value="created_time")//这样处理的主要目的是java对带有下划线的字段不识别,所以改为驼峰形式
    private Date createdTime;//如果需要年月日格式的可以使用Date类型,如果需要具体到时分秒就使用String类型
    private String avatar;
    private String role;
}

(5)登录用户类UserDTO

package com.example.demo.controller.dto;

import com.example.demo.entity.Menu;
import lombok.Data;

import java.util.List;


//UserDTO用来接受前端登录时传递的用户名和密码
@Data
public class UserDTO {
    private String username;
    private String password;
    private String nickname;
    private String avatar;
    private String token;
    //把当前登录用户的角色以及他的菜单项带出来
    private String role;
    private List<Menu> menus;
}

二、后端增删改查接口设计

首先梳理一下这里的逻辑;
(1)先理解增加、删除、修改、查找角色,这与我们常见的任一个数据表的增删改查没有任何区别;
(2)给某个角色分配菜单,这里的逻辑需要理解:
第一步: 点击“分配菜单”的按钮之后,首先打开“分配菜单”对话框:初始显示菜单表中的所有数据,但一定是一级、二级等父子级关系的菜单树型结构显示。此时需要调用的是MenuService的findMenus方法。具体代码参考任务十四;
第二步:根据当前角色Id,在RoleMenu表中获取对应的权限菜单id。
(3)为了确定第二步获取的菜单id就是菜单Menu表中(防止恶意篡改)的数据,在设置树型选中的时候再次进行比较,通过roleid获取的菜单id与菜单表Menu中的所有取出ids逐一比较,如果一致则让树型菜单处于选中状态。
特别说明: 这里的知识点如果想要搞透,务必仔细查看element UI官网的Tree 树型控件,尤其是Attributes。
(4)当需要绑定权限菜单保存的时候,需要调用RoleService的setRoleMenu,先将原来的绑定清除,然后重新绑定一级、二级菜单给当前角色。
在这里插入图片描述
在这里插入图片描述

1. 添加RoleMapper接口

这里的selectByflag方法用来根据登录用户的role(角色)得到角色标识,如某用户的role为ROLE_ADMIN(这就是角色标识),由这个角色标识可以获取他的角色,进而获取他的权限菜单。

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
importcom.example.demo.entity.Role;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface RoleMapper extends BaseMapper<Role> {
    //根据角色唯一标识flag查找角色id
    @Select("select id from sys_role where flag=#{flag}")
    Integer selectByflag(@Param("flag") String role);
}

2. 添加RoleMenuMapper接口

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.RoleMenu;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface RoleMenuMapper  extends BaseMapper<RoleMenu> {
    //根据角色id删除角色菜单数据
    @Delete("delete from sys_role_menu where role_id=#{roleId}")
    int deleteByRoleId(@Param("roleId") Integer roleId);

    //根据角色id查找菜单id
    @Select("select menu_Id from sys_role_menu where role_id=#{roleId}")
    List<Integer> selectByRoleId(@Param("roleId") Integer roleId);
}

3. 添加RoleService类

package com.example.demo.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Menu;
import com.example.demo.entity.Role;
import com.example.demo.entity.RoleMenu;
import com.example.demo.entity.User;
import com.example.demo.mapper.RoleMapper;
import com.example.demo.mapper.RoleMenuMapper;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

@Service
public class RoleService extends ServiceImpl<RoleMapper, Role> {
@Resource
private RoleMenuMapper roleMenuMapper;
@Resource
private MenuService menuService;
@Transactional
public void setRoleMenu(Integer roleId, List menuIds) {
//先删除当前角色id所有的绑定关系
roleMenuMapper.deleteByRoleId(roleId);

    //再把前端传递过来的菜单id数组绑定到角色id上
      for(Integer menuId:menuIds){
          Menu menu=menuService.getById(menuId);
          if(menu.getPid()!=null && !menuIds.contains(menu.getPid())){//二级菜单,并且传过来的menuId数组中没有父级id
              RoleMenu roleMenu=new RoleMenu();
              roleMenu.setRoleId(roleId);
              roleMenu.setMenuId(menuId);
              roleMenuMapper.insert(roleMenu);
          }
          RoleMenu roleMenu=new RoleMenu();
          roleMenu.setRoleId(roleId);
          roleMenu.setMenuId(menuId);
          roleMenuMapper.insert(roleMenu);
      }
}
//根据角色id查找相应的菜单id
public List<Integer> getRoleMenu(Integer roleId) {
    return roleMenuMapper.selectByRoleId(roleId);
}

}

4. 添加RoleController类

(1)增加角色

package com.example.demo.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.common.Result;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import com.example.demo.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/role")
public class RoleController {

    @Autowired
    private RoleService roleService;
   //增加角色
    @PostMapping
    public Result save(@RequestBody Role role){
        roleService.saveOrUpdate(role);
        return Result.success();
    }

(2)删除角色

    //根据id删除角色
    @DeleteMapping("/{id}")
    public Result deleteById(@PathVariable Integer id){
        roleService.removeById(id);
        return Result.success();
    }

(3)批量删除角色

    //批量删除角色
    @PostMapping("/del/batch")
    public Result deleteBatch(@RequestBody List<Integer> ids){
        roleService.removeByIds(ids);
        return Result.success();
    }

(4)查找全部角色

//查询全部角色
@GetMapping
public Result findAll(){
    return Result.success(roleService.list());
}

(5)根据id查找角色

    //根据id查找角色
    @GetMapping("/{id}")
    public Result findById(@PathVariable Integer id){
        return Result.success(roleService.getById(id));
    }

(6)分页查找

    //分页查找
    @GetMapping("/page")
    public Result findPage(@RequestParam Integer pageNum,
                           @RequestParam Integer pageSize,
                           @RequestParam(defaultValue = "") String name){
        QueryWrapper<Role> queryWrapper=new QueryWrapper<>();
        queryWrapper.like("name",name);
        queryWrapper.orderByDesc("id");
        return Result.success(roleService.page(new Page<>(pageNum,pageSize),queryWrapper));
    }

(7)根据当前角色Id,roleId找到绑定的权限菜单

    /**
     * 设置初始绑定结果
     *
     * @param roleId 角色Id
     * @return
     */
    @GetMapping("/roleMenu/{roleId}")
    public Result getRoleMenu(@PathVariable Integer roleId){
        return Result.success(roleService.getRoleMenu(roleId));
    }

(8)roleService.getRoleMenu接口为

package com.example.demo.service;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Menu;
import com.example.demo.entity.Role;
import com.example.demo.entity.RoleMenu;
import com.example.demo.entity.User;
import com.example.demo.mapper.RoleMapper;
import com.example.demo.mapper.RoleMenuMapper;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

@Service
public class RoleService extends ServiceImpl<RoleMapper, Role> {
    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private MenuService menuService;
    @Transactional
    //根据角色id查找相应的菜单id
    public List<Integer> getRoleMenu(Integer roleId) {
        return roleMenuMapper.selectByRoleId(roleId);
    }
}

(9)roleMenuMapper.selectByRoleId(roleId)接口为:

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.RoleMenu;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface RoleMenuMapper  extends BaseMapper<RoleMenu> {
    //根据角色id查找菜单id
    @Select("select menu_Id from sys_role_menu where role_id=#{roleId}")
    List<Integer> selectByRoleId(@Param("roleId") Integer roleId);
}

(10)为当前角色roleId绑定菜单

/**
     * 绑定角色和菜单的关系
     * @param roleId 角色Id
     * @param menuIds 菜单列表
     * @return
     */
    @PostMapping("/roleMenu/{roleId}")
    public Result roleMenu(@PathVariable Integer roleId,@RequestBody List<Integer> menuIds){
        roleService.setRoleMenu(roleId,menuIds);
        return Result.success();
    }

(10-1)roleService.setRoleMenu(roleId,menuIds)的接口为:

package com.example.demo.service;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Menu;
import com.example.demo.entity.Role;
import com.example.demo.entity.RoleMenu;
import com.example.demo.entity.User;
import com.example.demo.mapper.RoleMapper;
import com.example.demo.mapper.RoleMenuMapper;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

@Service
public class RoleService extends ServiceImpl<RoleMapper, Role> {
    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private MenuService menuService;
    @Transactional
      public void setRoleMenu(Integer roleId, List<Integer> menuIds) {
          //先删除当前角色id所有的绑定关系
        roleMenuMapper.deleteByRoleId(roleId);

        //再把前端传递过来的菜单id数组绑定到角色id上
          for(Integer menuId:menuIds){
              Menu menu=menuService.getById(menuId);
              if(menu.getPid()!=null && !menuIds.contains(menu.getPid())){//二级菜单,并且传过来的menuId数组中没有父级id
                  RoleMenu roleMenu=new RoleMenu();
                  roleMenu.setRoleId(roleId);
                  roleMenu.setMenuId(menuId);
                  roleMenuMapper.insert(roleMenu);
              }
              RoleMenu roleMenu=new RoleMenu();
              roleMenu.setRoleId(roleId);
              roleMenu.setMenuId(menuId);
              roleMenuMapper.insert(roleMenu);
          }
    }
    //根据角色id查找相应的菜单id
    public List<Integer> getRoleMenu(Integer roleId) {
        return roleMenuMapper.selectByRoleId(roleId);
    }
}

(10-2)roleMenuMapper.deleteByRoleId(roleId)接口为:

package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.RoleMenu;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface RoleMenuMapper  extends BaseMapper<RoleMenu> {
    //根据角色id删除角色菜单数据
    @Delete("delete from sys_role_menu where role_id=#{roleId}")
    int deleteByRoleId(@Param("roleId") Integer roleId);

    //根据角色id查找菜单id
    @Select("select menu_Id from sys_role_menu where role_id=#{roleId}")
    List<Integer> selectByRoleId(@Param("roleId") Integer roleId);
}

(13)前面讲解逻辑的时候第二步中曾经用到的取出菜单表Menu中的所有ids,调用menuService.list().stream(),接口方法为:
在MenuController中添加findAllIds方法。
在这里插入图片描述

    //查询所有菜单id
    @GetMapping("/ids")
    public Result findAllIds(){
        return Result.success(menuService.list().stream().map(Menu::getId));
    }

这个方法前端页面需要调用。主要为了判断从角色权限表中获取的菜单是不是确实是菜单表中的数据(防止菜单篡改)。

三、前端Role.vue页面设计

前端Role.vue页面设计完整代码如下:

<template>
  <div>
    <div style="padding:10px">
        <el-input style="width:250px" suffix-icon="el-icon-search" placeholder="请输入名称搜索" v-model="name"></el-input>
        <el-button style="margin-left:5px" type="primary" @click="load">搜索</el-button>
        <el-button style="margin-left:5px" type="warning" @click="reset">重置</el-button>
      </div>
      <div style="margin:10px">
         <el-button type="primary" @click="handleAdd">新增<i class="el-icon-circle-plus"></i></el-button>
         <el-button type="danger" @click="delBatch">批量删除<i class="el-icon-remove"></i></el-button>
      </div>
        <el-table :data="tableData" @selection-change="handleSelectionChange">
          <el-table-column type="selection" width="55"></el-table-column>
          <el-table-column prop="id" label="ID " width="80">
          </el-table-column>
          <el-table-column prop="name" label="名称 " >
          </el-table-column>
          <el-table-column prop="flag" label="唯一标识 " >
          </el-table-column>
          <el-table-column prop="description" label="描述" >
          </el-table-column>          
          <el-table-column fixed="right" label="操作" width="280" >                         
            <template slot-scope="scope">
              <el-button type="info" slot="reference" size="small" icon="el-icon-menu" @click="selectMenu(scope.row)">分配菜单</el-button>
              <el-button type="success" size="small" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
              <el-popconfirm style="margin-left:5px"
                confirm-button-text='确定'
                cancel-button-text='再想想'
                icon="el-icon-info"
                icon-color="red"
                title="您确定删除吗?"
                @confirm="handleDelete(scope.row.id)"
              >
              <el-button type="danger" size="small" slot="reference" icon="el-icon-delete" >删除</el-button>
              </el-popconfirm>
            </template>
          </el-table-column>         
        </el-table>
        <div style="padding:10px">
         <el-pagination
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="pageNum"
          :page-sizes="[2, 5, 10, 20]"
          :page-size="pageSize"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total">
         </el-pagination>
       </div>

        <el-dialog title="角色信息" :visible.sync="dialogFormVisible" width="30%">
          <el-form label-width="80px" size="small">
            <el-form-item label="角色名">
              <el-input v-model="form.name" autocomplete="off"></el-input>
            </el-form-item>          
            <el-form-item label="描述">
              <el-input v-model="form.description" autocomplete="off"></el-input>
            </el-form-item>
            <el-form-item label="唯一标识">
              <el-input v-model="form.flag" autocomplete="off"></el-input>
            </el-form-item>                      
          </el-form>
          <div slot="footer" class="dialog-footer">
            <el-button @click="dialogFormVisible = false">取 消</el-button>
            <el-button type="primary" @click="save">确 定</el-button>
          </div>
        </el-dialog>

        <el-dialog title="菜单分配" :visible.sync="dialogMenu" width="40%">
          <el-tree
            :props="props"           
            :data="menuData"           
            node-key="id"
            ref="tree"            
            :default-expanded-keys="expands"
            :default-checked-keys="checks"
            show-checkbox>
            <span class="custom-tree-node" slot-scope="{data }">
               <span><i :class="data.icon"></i>{
   
   {data.name }}</span>
            </span>
          </el-tree>
          <div slot="footer" class="dialog-footer">
            <el-button @click="dialogMenu = false">取 消</el-button>
            <el-button type="primary" @click="saveRoleMenu">确 定</el-button>
          </div>
        </el-dialog>
  </div>
</template>
<script>
import store from "@/store";
import {resetRouter} from "@/router";
export default {
    name:"Role",
    data(){
        return{
            tableData:[],
            total:0,
            pageNum:1,
            pageSize:10,
            name:"",
            dialogFormVisible:false,
            dialogMenu:false,
            form:{},
            multipleSelection:[],
            menuData:[],
            props:{
               label: 'name'
            },
            roleId:0,
            roleFlag:'',
            expands:[],
            checks:[]
        }
    },
    created(){
     this.load();
    },
    methods:{
      //编辑按钮事件
      handleEdit(row){
        console.log(row);
        this.form=row;//把当前行的数据赋值给form
        this.dialogFormVisible=true;        
      },
      //表格多选选中事件
      handleSelectionChange(val){
        console.log(val);
        this.multipleSelection =val;
      },
      //批量删除按钮事件
      delBatch(){ 
         let ids=this.multipleSelection.map(v=>v.id);//map这个方法可以实现将multipleSelection中的对象扁平化处理。
         console.log(ids);
         this.request.post("http://localhost:8084/role/del/batch",ids).then(res=>{
         if(res.code=='200'){
            this.$message.success("批量删除成功");
            this.load();
          }else{
            this.$message.error("批量删除失败");
          } 
        })
      },
      //单条记录删除按钮事件
      handleDelete(id){
         this.request.delete("http://localhost:8084/role/"+id+"").then(res=>{
         if(res.code=='200'){
            this.$message.success("删除成功");
            this.load();
          }else{
            this.$message.error("删除失败");
          } 
        })
      },
      handleSizeChange(val) {/*传递过来当前是第几页*/
        console.log(`每页 ${val} 条`);
        this.pageSize=val;  //获取当前每页显示条数
        this.load();
      },
      handleCurrentChange(val) {/*传递过来当前是第几页*/
        console.log(`当前页: ${val}`);
        this.pageNum=val;   //获取当前第几页
        this.load();
      },     
      //将请求数据封装为一个方法
      load() {
          //请求分页查询数据
          //使用axios封装的request        
          this.request.get("http://localhost:8084/role/page",{
            params:{
            pageNum: this.pageNum,
            pageSize: this.pageSize,
            name:this.name           
            }
          }).then(res=>{
          this.tableData=res.data.records;
          this.total=res.data.total;        
          })
      },
      //新增角色按钮事件
      handleAdd(){
        this.dialogFormVisible = true;
        this.form={};//如果之前有填过值,可以置空
      },
      //分配菜单按钮事件 
      selectMenu(role){      
        this.dialogMenu=true;
        this.roleId=role.id;
        this.roleFlag=role.flag;
        //添加权限(树形结构)
        //拿到菜单列表
         this.request.get("http://localhost:8084/menu").then(res=>{          
            this.menuData=res.data
            console.log("所有菜单数据",this.menuData)
            this.expands=this.menuData.map(v=>v.id)
            console.log("expands",this.expands)
          })
          
          this.request.get("http://localhost:8084/role/roleMenu/" +this.roleId).then(res=>{//当前角色的权限菜单
          console.log("当前角色的菜单",res.data);
          this.checks=res.data;
          this.request.get("http://localhost:8084/menu/ids").then(r=>{//取出所有的菜单id
            const ids=r.data
            console.log("所有菜单id",ids)
            ids.forEach(id=>{
              if(!this.checks.includes(id)){ //和当前的权限菜单进行比较,没有就不显示
              //使用setChecked方法需要设置  node-key="id"  设置为false即为不渲染
                this.$refs.tree.setChecked(id,false)
              }
            })            
          })
          })

      },
      //保存角色按钮事件
      save(){
        this.request.post("http://localhost:8084/role",this.form).then(res=>{
          if(res.code=='200'){
            this.$message.success("保存成功");
            this.dialogFormVisible=false;
            this.load();
          }else{
            this.$message.error("保存失败");
          }
        })
      },
      //分配菜单对话框中的确定按钮事件
      saveRoleMenu(){
         this.request.post("http://localhost:8084/role/roleMenu/" + this.roleId,this.$refs.tree.getCheckedKeys().concat(this.$refs.tree.getHalfCheckedKeys())).then(res=>{
          if(res.code=='200'){
            this.$message.success("绑定成功");
            this.dialogMenu=false;
            //操作管理员角色权限后需要重新登录
            if(this.roleFlag=="ROLE_ADMIN")  {  
              //把原来的路由清除   
              resetRouter();        
              //this.$router.push("/login")
              //也可以调用store中的logout方法全局调用
              this.$store.commit("logout") 
            }            
          }else{
            this.$message.error(res.msg);
          }
         })
      },
      //搜索重置按钮事件
      reset(){
        this.name="";        
        this.load();
      }
    }   
}
</script>
<style scoped>

</style>

项目可以正常运行,以下给大家讲解一下。
特别说明: 这个前端页面有几个接口方法需要注意:

1.“分配菜单”按钮事件后端逻辑

首先需要打开菜单表,所以调用的是this.request.get(“http://localhost:8084/menu”),即menuService.findMenus(name)
在这里插入图片描述
然后树型结构显示的是当前行选中角色id的权限菜单(用以下三个顺序获取)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后 根据角色Id获取的菜单还需要与菜单表中的Id进行一次校验,确保确实是系统的菜单项。此时还需要获取菜单表中的id(这个比较简单)
在这里插入图片描述
特别注意:到任务十六做动态路由的时候,因为管理员重新分配了权限,就重新登录,那么,他的原先菜单就删除,重新获取路由,所以把原来的路由清除的 resetRouter(); 这个方法在见任务十六 动态路由设计。

修改路由文件index.js,添加resetRouter();
在这里插入图片描述
resetRouter()完整代码:

// 提供一个重置路由的方法
export const resetRouter=()=>{
  router.matcher=new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
  })
}

2.“分配菜单”按钮事件前端逻辑

在这里插入图片描述

3. 绑定角色id对应的菜单id

这个在绑定的过程中需要注意一级菜单与二级菜单都要保存到sys_role_menu表中。在这里插入图片描述

四、运行项目

1.新增一个一级菜单

在这里插入图片描述

2.新增两个二级菜单

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.为角色分配菜单

普通用户角色直接修改完成即可。
在这里插入图片描述
管理员角色分配权限菜单
在这里插入图片描述

任务总结

现在我们已经完成了角色与相应权限菜单的绑定。
下一个任务将实现动态路由。
在这里插入图片描述

任务十六 VUE权限菜单之动态路由

猜你喜欢

转载自blog.csdn.net/wdyan297/article/details/128759644