SpringBoot2+Vue2实战(十)权限管理之动态菜单、动态路由生成

news2024/12/23 2:26:06

一、父子菜单实现

新建数据库表

sys_menu

 sys_role

 实体类

Role

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 *
 * </p>
 *
 */
@Getter
@Setter
@TableName("sys_role")
public class Role implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;


}

Menu 


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 java.io.Serializable;
import java.util.List;

import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 *
 * </p>
 *
 * 
 */
@Getter
@Setter
@TableName("sys_menu")
public class Menu implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 名称
     */
    private String name;

    /**
     * 路径
     */
    private String path;

    /**
     * 图片
     */
    private String icon;

    /**
     * 描述
     */
    private String description;


    @TableField(exist = false)
    private List<Menu> children;


    private Integer pid;


}

RoleController

package com.example.springboot.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springboot.common.Result;
import com.example.springboot.entity.Role;
import com.example.springboot.service.RoleService;
import org.springframework.web.bind.annotation.*;

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

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

    @Resource
    private RoleService roleService;


    //修改或增加
    @PostMapping("/saveRole")
    public Result saveRole(@RequestBody Role role) {
        //新增或修改
        return Result.success(roleService.saveOrUpdate(role));
    }

    @GetMapping("/findAll")
    public Result findAll() {
        return Result.success(roleService.list());
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        return Result.success(roleService.removeById(id));
    }

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

    //分页查询 mybatis-plus方式
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "") String name,
                             @RequestParam Integer pageSize,
                             @RequestParam Integer pageNum) {

        IPage<Role> page = new Page<>(pageNum, pageSize);
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("id");
        if (!"".equals(name)) {
            queryWrapper.like("name", name);
        }
        return Result.success(roleService.page(page, queryWrapper));
    }

}

MneuController


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springboot.common.Result;
import com.example.springboot.entity.Menu;
import com.example.springboot.service.MenuService;
import org.springframework.web.bind.annotation.*;

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

/**
 * <p>
 * 前端控制器
 * </p>
 *
 */
@RestController
@RequestMapping("/menu")
public class MenuController {

    @Resource
    private MenuService menuService;


    //修改或增加
    @PostMapping("/saveMenu")
    public Result saveRole(@RequestBody Menu menu) {
        //新增或修改
        return Result.success(menuService.saveOrUpdate(menu));
    }

    @GetMapping("/findAll")
    public Result findAll(@RequestParam(defaultValue = "") String name) {
        QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("id");
        if (!"".equals(name)) {
            queryWrapper.like("name", name);
        }
        //查询所有数据
        List<Menu> list = menuService.list(queryWrapper);
        List<Menu> parentNode = list.stream().filter(menu -> menu.getPid() == null).collect(Collectors.toList());
        //找出一级菜单的子菜单
        for (Menu menu:parentNode){
            //筛选所有数据中pid等于父级id的数据就是二级菜单
            menu.setChildren(list.stream().filter(m -> menu.getId().equals(m.getPid())).collect(Collectors.toList()));
        }
        return Result.success(parentNode);
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        return Result.success(menuService.removeById(id));
    }

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

    //分页查询 mybatis-plus方式
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "") String name,
                             @RequestParam Integer pageSize,
                             @RequestParam Integer pageNum) {

        IPage<Menu> page = new Page<>(pageNum, pageSize);
        QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("id");
        if (!"".equals(name)) {
            queryWrapper.like("name", name);
        }
        return Result.success(menuService.page(page, queryWrapper));
    }
}

router/index.js

{
        path: '/',
        component: () => import('../views/Manage.vue'),
        redirect: "/home",
        children: [
            {path: 'user', name: '用户管理', component: () => import('../views/User.vue'),},
            {path: 'home', name: '首页', component: () => import('../views/Home.vue'),},
            {path: 'role', name: '角色管理', component: () => import('../views/Role.vue'),},
            {path: 'menu', name: '菜单管理', component: () => import('../views/Menu.vue'),},
            {path: 'person', name: '个人信息', component: () => import('../views/Person.vue'),},
            {path: 'file', name: '文件管理', component: () => import('../views/Files.vue'),}
        ]
    },

Role.vue

<template>
  <div>
    <div style="margin: 10px 0">
      <el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search"
                v-model="name"></el-input>
      <el-button class="ml-5" type="primary" @click="load">搜索</el-button>
      <el-button type="warning" @click="reset">重置</el-button>
    </div>

    <div style="margin: 10px 0">
      <el-button type="primary" @click="handleAdd">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
      <el-popconfirm
          class="ml-5"
          confirm-button-text='确定'
          cancel-button-text='我再想想'
          icon="el-icon-info"
          icon-color="red"
          title="您确定批量删除这些数据吗?"
          @confirm="delBatch"
      >
        <el-button type="danger" slot="reference">批量删除 <i class="el-icon-remove-outline"></i></el-button>
      </el-popconfirm>
    </div>

    <el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'"
              @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="description" label="描述"></el-table-column>
      <el-table-column label="操作" width="280" align="center">
        <template slot-scope="scope">
          <el-button type="info" @click="selectMenu(scope.row.id)">分配菜单<i class="el-icon-menu"></i></el-button>
          <el-button type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
          <el-popconfirm
              class="ml-5"
              confirm-button-text='确定'
              cancel-button-text='我再想想'
              icon="el-icon-info"
              icon-color="red"
              title="您确定删除吗?"
              @confirm="del(scope.row.id)"
          >
            <el-button type="danger" slot="reference">删除<i class="el-icon-remove-outline"></i></el-button>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>
    <div style="padding: 10px 0">
      <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>
      <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="menuDialogVis" width="30%" style="padding: 0 50px">
      <el-tree
          //使用props进行数据绑定
          :props="props"
          :data="menuData"
          :default-expanded-keys="[1]"
          :default-checked-keys="[4]"
          node-key="id"
          show-checkbox
          @check-change="handleCheckChange">
      </el-tree>
      <div slot="footer" class="dialog-footer">
        <el-button @click="menuDialogVis = false">取 消</el-button>
        <el-button type="primary" @click="save">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "Role",
  data() {
    return {
      tableData: [],
      total: 0,
      pageNum: 1,
      pageSize: 2,
      name: "",
      form: {},
      dialogFormVisible: false,
      menuDialogVis: false,
      multipleSelection: [],
      menuData: [],
      props:{
        label:'name'
      }
    }
  },
  created() {
    this.load()
  },
  methods: {
    load() {
      this.request.get("/role/selectPage", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name,
        }
      }).then(res => {
        //注意data
        this.tableData = res.data.records
        this.total = res.data.total
      })

    },


    save() {
      this.request.post("/role/saveRole", this.form).then(res => {
        if (res.code === '200') {
          this.$message.success("保存成功")
          this.dialogFormVisible = false
          this.load()
        } else {
          this.$message.error("保存失败")
        }
      })
    },
    handleAdd() {
      this.dialogFormVisible = true
      this.form = {}
    },
    handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible = true
    },
    del(id) {
      this.request.delete("/role/" + id).then(res => {
        if (res.code === '200') {
          this.$message.success("删除成功")
          this.load()
        } else {
          this.$message.error("删除失败")
        }
      })
    },
    handleSelectionChange(val) {
      console.log(val)
      this.multipleSelection = val
    },
    delBatch() {
      let ids = this.multipleSelection.map(v => v.id)  // [{}, {}, {}] => [1,2,3]
      this.request.post("/role/del/batch", ids).then(res => {
        if (res.code === '200') {
          this.$message.success("批量删除成功")
          this.load()
        } else {
          this.$message.error("批量删除失败")
        }
      })
    },
    reset() {
      this.name = ""
      this.load()
    },
    handleSizeChange(pageSize) {
      console.log(pageSize)
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum) {
      console.log(pageNum)
      this.pageNum = pageNum
      this.load()
    },
    //分配菜单
    selectMenu(roleId) {
      this.menuDialogVis = true

      //请求菜单数据
      this.request.get("/menu/findAll").then(res => {
        //注意data
        this.menuData = res.data
      })

    },
    handleCheckChange(data, checked, indeterminate) {
      console.log(data, checked, indeterminate);
    },
  },

}
</script>


<style>
.headerBg {
  background: #eee !important;
}
</style>

Mneu.vue

<template>
  <div>
    <div style="margin: 10px 0">
      <el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search"
                v-model="name"></el-input>
      <el-button class="ml-5" type="primary" @click="load">搜索</el-button>
      <el-button type="warning" @click="reset">重置</el-button>
    </div>

    <div style="margin: 10px 0">
      <el-button type="primary" @click="handleAdd()">新增<i class="el-icon-circle-plus-outline"></i></el-button>
      <el-popconfirm
          class="ml-5"
          confirm-button-text='确定'
          cancel-button-text='我再想想'
          icon="el-icon-info"
          icon-color="red"
          title="您确定批量删除这些数据吗?"
          @confirm="delBatch"
      >
        <el-button type="danger" slot="reference">批量删除 <i class="el-icon-remove-outline"></i></el-button>
      </el-popconfirm>
    </div>

    <el-table :data="tableData" row-key="id" default-expand-all border stripe :header-cell-class-name="'headerBg'"
              @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="path" label="路径"></el-table-column>
      <el-table-column prop="icon" label="图片"></el-table-column>
      <el-table-column prop="description" label="描述"></el-table-column>
      <el-table-column label="操作" width="280" align="center">
        <template slot-scope="scope">
          <el-button type="primary" @click="handleAdd(scope.row.id)" v-if="!scope.row.pid && !scope.row.path">新增子菜单<i class="el-icon-plus"></i></el-button>
          <el-button type="success" @click="handleEdit(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
          <el-popconfirm
              class="ml-5"
              confirm-button-text='确定'
              cancel-button-text='我再想想'
              icon="el-icon-info"
              icon-color="red"
              title="您确定删除吗?"
              @confirm="del(scope.row.id)"
          >
            <el-button type="danger" slot="reference">删除<i class="el-icon-remove-outline"></i></el-button>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

    <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.path" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图片">
          <el-input v-model="form.icon" 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>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="save">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "Menu",
  data() {
    return {
      tableData: [],
      total: 0,
      pageNum: 1,
      pageSize: 2,
      name: "",
      form: {},
      dialogFormVisible: false,
      multipleSelection: []
    }
  },
  created() {
    this.load()
  },
  methods: {
    load() {
      this.request.get("/menu/findAll", {
        params: {
          name: this.name,
        }
      }).then(res => {
        //注意data
        this.tableData = res.data
      })
    },


    save() {
      this.request.post("/menu/saveMenu", this.form).then(res => {
        if (res.code === '200') {
          this.$message.success("保存成功")
          this.dialogFormVisible = false
          this.load()
        } else {
          this.$message.error("保存失败")
        }
      })
    },
    handleAdd(pid) {
      this.dialogFormVisible = true
      this.form = {}
      if (pid) {
        this.form.pid = pid
      }
    },
    handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible = true
    },
    del(id) {
      this.request.delete("/menu/" + id).then(res => {
        if (res.code === '200') {
          this.$message.success("删除成功")
          this.load()
        } else {
          this.$message.error("删除失败")
        }
      })
    },
    handleSelectionChange(val) {
      console.log(val)
      this.multipleSelection = val
    },
    delBatch() {
      let ids = this.multipleSelection.map(v => v.id)  // [{}, {}, {}] => [1,2,3]
      this.request.post("/menu/del/batch", ids).then(res => {
        if (res.code === '200') {
          this.$message.success("批量删除成功")
          this.load()
        } else {
          this.$message.error("批量删除失败")
        }
      })
    },
    reset() {
      this.name = ""
      this.load()
    },
    handleSizeChange(pageSize) {
      console.log(pageSize)
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum) {
      console.log(pageNum)
      this.pageNum = pageNum
      this.load()
    }
  }
}
</script>


<style>
.headerBg {
  background: #eee !important;
}
</style>

二、图标功能

新建数sys_dict

 Dict


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

import java.io.Serializable;

/**
 * <p>
 *
 * </p>
 *
 * @author 
 * @since 2023-07-05
 */
@Data
@TableName("sys_dict")
public class Dict implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 名称
     */
    private String name;

    /**
     * 内容
     */
    private String value;

    /**
     * 类型
     */
    private String type;


}

MenuController

@Resource
    private DictMapper dictMapper;




 @GetMapping("/icons")
    public Result getIcons(){
        QueryWrapper<Dict> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", Constants.DICT_TYPE_ICON);
        return Result.success(dictMapper.selectList(queryWrapper));
    }

Menu.vue


//修改表格图标显示
<el-table-column label="图标" class-name="fontSize18" align="center" label-class-name="fontSize12">
        <template slot-scope="scope">
          <i :class="scope.row.icon"></i>
        </template>
      </el-table-column>


//修改弹窗图标选择显示
<el-form-item label="图标">
          <template slot-scope="scope">
            <el-select clearable v-model="form.icon" placeholder="请选择" style="width: 80%">
              <el-option v-for="item in options" :key="item.name" :label="item.name" :value="item.value">
                <i :class="item.value"/>{{ item.name }}
              </el-option>
            </el-select>
          </template>
        </el-form-item>

//data中添加参数

options: []


//修改方法

handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible = true

      //请求图标的数据
      this.request.get("/menu/icons").then(res => {
        this.options = res.data
      })

    },

 查询菜单时展开功能实现

Role

<el-tree
          :props="props"
          :data="menuData"
          :default-expanded-keys="expends"
          :default-checked-keys="checks"
          node-key="id"
          show-checkbox
          @check-change="handleCheckChange">
         <span class="custom-tree-node" slot-scope="{ node, data }">
           <span><i :class="data.icon"></i>{{ data.name }}</span>
         </span>
      </el-tree>

//data添加变量
 expends: [],
      checks: [],



//分配菜单
    selectMenu(roleId) {
      this.menuDialogVis = true

      //请求菜单数据
      this.request.get("/menu/findAll").then(res => {
        //注意data
        this.menuData = res.data

        //把后台返回的菜单数据处理成id数组
        this.expends = this.menuData.map(v => v.id);
      })

    },




 三、菜单分配功能实现

1.添加功能

新建数据库表

sys_role_menu

 RoleMenu

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

@Data
@TableName("sys_role_menu")
public class RoleMenu {

    private Integer roleId;

    private Integer menuId;
}

RoleController

@PostMapping("/setRoleMenu/{roleId}")
    public Result setRoleMenu(@PathVariable Integer roleId, @RequestBody List<Integer> menuIds) {
        roleService.setRoleMenu(roleId, menuIds);
        return Result.success();
    }

RoleService

void setRoleMenu(Integer roleId, List<Integer> menuIds);

RoleServiceImpl

@Resource
    private RoleMenuMapper roleMenuMapper;

    @Override
    @Transactional
    public void setRoleMenu(Integer roleId, List<Integer> menuIds) {
        /**
         * 第一种方法
         * */
        /*QueryWrapper<RoleMenu> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_id",roleId);
        roleMenuMapper.delete(queryWrapper);*/

        /**
         * 第二种方法
         * */
        //先删除当前角色id所有的绑定关系
        roleMenuMapper.deleteMenuByRoleId(roleId);

        //再把前端传过来的菜单id数组绑定到当前这个角色id上
        for (Integer menuId: menuIds){
            RoleMenu roleMenu = new RoleMenu();
            roleMenu.setRoleId(roleId);
            roleMenu.setMenuId(menuId);
            roleMenuMapper.insert(roleMenu);
        }
    }

RoleMenuMapper

 @Delete("delete from sys_role_menu where role_id = #{roleId}")
    Integer deleteMenuByRoleId(@Param("roleId") Integer roleId);

Role.vue

<el-button type="info" @click="selectMenu(scope.row.id)">分配菜单<i class="el-icon-menu"></i></el-button>


<el-dialog title="菜单分配" :visible.sync="menuDialogVis" width="30%" style="padding: 0 50px">
      <el-tree
          :props="props"
          :data="menuData"
          :default-expanded-keys="expends"
          :default-checked-keys="checks"
          node-key="id"
          show-checkbox
          ref="tree"
      >
         <span class="custom-tree-node" slot-scope="{ node, data }">
           <span><i :class="data.icon"></i>{{ data.name }}</span>
         </span>
      </el-tree>
      <div slot="footer" class="dialog-footer">
        <el-button @click="menuDialogVis = false">取 消</el-button>
        <el-button type="primary" @click="saveRoleMenu">确 定</el-button>
      </div>
    </el-dialog>






saveRoleMenu() {
      this.request.post("/role/setRoleMenu/" + this.roleId, this.$refs.tree.getCheckedKeys()).then(res => {
        if (res.code === '200') {
          this.$message.success("绑定成功")
          this.menuDialogVis = false
        } else {
          this.$message.error(res.msg)
        }
      })
    },

2.点击菜单分配时的查询已有权限菜单功能

RoleController

@GetMapping("/getRoleMenu/{roleId}")
    public Result getRoleMenu(@PathVariable Integer roleId) {
        return Result.success(roleService.getRoleMenu(roleId));
    }

RoleService

List<Integer> getRoleMenu(Integer roleId);

RoleServiceImpl

@Override
    public List<Integer> getRoleMenu(Integer roleId) {
        return roleMenuMapper.selectByRoleId(roleId);
    }

RoleMenuMapper

 @Select("select menu_id from sys_role_menu where role_id = #{roleId}")
    List<Integer> selectByRoleId(@Param("roleId") Integer roleId);

Role.vue

roleId: 0





//分配菜单
    selectMenu(roleId) {
      this.menuDialogVis = true
      this.roleId = roleId
      //请求菜单数据
      this.request.get("/menu/findAll").then(res => {
        //注意data
        this.menuData = res.data

        //把后台返回的菜单数据处理成id数组
        this.expends = this.menuData.map(v => v.id);
      })

      this.request.get("/role/getRoleMenu/" + roleId).then(res => {
        this.checks = res.data
      })

    },

四、动态菜单生成

修改数据库表

sys_role

 RoleEnum

public enum RoleEnum {
    ROLE_ADMIN,ROLE_USER;
}

Role

@Getter
@Setter
@TableName("sys_role")
public class Role implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    //唯一标识
    private String flag;

}

MenuController

@GetMapping("/findAll")
    public Result findAll(@RequestParam(defaultValue = "") String name) {
        return Result.success(menuService.findMenus(name));
    }

MenuService

 List<Menu> findMenus(String name);

MenuServiceImpl

@Override
    public List<Menu> findMenus(String name) {
        QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("id");
        if (!"".equals(name)) {
            queryWrapper.like("name", name);
        }
        //查询所有数据
        List<Menu> list = list(queryWrapper);
        List<Menu> parentNode = list.stream().filter(menu -> menu.getPid() == null).collect(Collectors.toList());
        //找出一级菜单的子菜单
        for (Menu menu:parentNode){
            //筛选所有数据中pid等于父级id的数据就是二级菜单
            menu.setChildren(list.stream().filter(m -> menu.getId().equals(m.getPid())).collect(Collectors.toList()));
        }
        return parentNode;
    }

UserServiceImpl

@Override
    public UserDto login(UserDto userDto) {
        User one = getUserInfo(userDto);
        if (one != null) {
            BeanUtil.copyProperties(one, userDto, true);
            //设置token
            String token = TokenUtils.getToken(one.getId().toString(), one.getPassword());
            userDto.setToken(token);
            //ROLE_ADMIN
            String role = one.getRole();
            //设置用户的菜单列表
            List<Menu> roleMenus = getRoleMenu(role);
            userDto.setMenus(roleMenus);
            return userDto;
        } else {
            throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
        }
    }





private List<Menu> getRoleMenu(String roleFlag) {

        Integer roleId = roleMapper.selectByFlag(roleFlag);
        //当前角色的所有菜单的id集合
        List<Integer> menuIds = roleMenuMapper.selectByRoleId(roleId);

        //查出所有的菜单
        List<Menu> menus = menuService.findMenus("");
        //new 一个最后筛选完成之后的list
        List<Menu> roleMenus = new ArrayList<>();
        //筛选当前用户角色
        for (Menu menu : menus) {
            if (menuIds.contains(menu.getId())) {
                roleMenus.add(menu);
            }
            List<Menu> children = menu.getChildren();
            //removeIf 移除children不在menuIds集合中的元素
            children.removeIf(child -> !menuIds.contains(child.getId()));
        }
        return roleMenus;
    }

Login.vue

login() {
      this.$refs['userForm'].validate((valid) => {
        if (valid) {  // 表单校验合法
          this.request.post("/user/login", this.user).then(res => {
            if (res.code === '200'){
              //存储用户信息到浏览器
              localStorage.setItem("user",JSON.stringify(res.data))
                //获取菜单信息
              localStorage.setItem("menus",JSON.stringify(res.data.menus))
              this.$router.push("/")
              this.$message.success("登录成功")
            }else {
              this.$message.error(res.msg)
            }
          })
        }
      });
    }

Aside.vue

<div v-for="item in menus" :key="item.id">
      <div v-if="item.path">
        <el-menu-item :index="item.path">
          <i :class="item.icon"></i>
          <span slot="title">{{ item.name }}</span>
        </el-menu-item>
      </div>
      <div v-else>
        <el-submenu :index="item.id +''">
          <template slot="title">
            <i :class="item.icon"></i>
            <span slot="title">{{ item.name }}</span>
          </template>
          <div v-for="subItem in item.children" :key="subItem.id">
            <el-menu-item :index="subItem.path">
              <i :class="subItem.icon"></i>
              <span slot="title">{{ subItem.name }}</span>
            </el-menu-item>
          </div>
        </el-submenu>
      </div>
    </div>

菜单默认展开效果

Aside.vue

<el-menu :default-openeds="opens" style="min-height: 100%; overflow-x: hidden"
           background-color="rgb(48, 65, 86)"
           text-color="#fff"
           active-text-color="#ffd04b"
           :collapse-transition="false"
           :collapse="isCollapse"
           router
  >



//data中添加

opens: localStorage.getItem("menus") ? JSON.parse(localStorage.getItem("menus")).map(v => v.id + '') : []

 

 

 五、动态路由生成

router/index.js

import Vue from "vue"
import VueRouter from "vue-router"
import store from "@/store";
import Manage from "@/views/Manage.vue";

Vue.use(VueRouter)

const routes = [

    {
        path: '/login',
        name: 'Login',
        component: () => import('../views/Login.vue')
    },
    {
        path: '/register',
        name: 'Register',
        component: () => import('../views/Register.vue')
    },
]

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

//注意刷新页面会导致重置路由
export const setRoutes = () => {
    const storeMenus = localStorage.getItem("menus");
    if (storeMenus) {
        //拼装动态路由
        const manageRoute = {
            path: '/',
            name: 'Manage',
            component: () => import('../views/Manage.vue'),
            redirect: "/home",
            children: []
        }

        const menus = JSON.parse(storeMenus)
        menus.forEach(item => {
            //当且仅当path不为空的时候才去设置路由
            if (item.path) {
                let itemMenu = {
                    path: item.path.replace("/", ""),
                    name: item.name,
                    component: () => import('../views/' + item.pagePath + '.vue')
                }
                manageRoute.children.push(itemMenu)
            } else if (item.children.length) {
                item.children.forEach(item => {
                    if (item.path) {
                        let itemMenu = {
                            path: item.path.replace("/", ""),
                            name: item.name,
                            component: () => import('../views/' + item.pagePath + '.vue')
                        }
                        manageRoute.children.push(itemMenu)
                    }
                })
            }
        })
        //获取当前的路由对象名称数组
        const currentRouteNames = router.getRoutes().map(v => v.name)
        if (!currentRouteNames.includes('Manage')) {
            //动态添加到现在的路由对象中去
            router.addRoute(manageRoute)
        }
    }
}

//重置我就再刷新一次路由
setRoutes()

// 路由守卫
router.beforeEach((to, from, next) => {
    localStorage.setItem("currentPathName", to.name)  // 设置当前的路由名称,为了在Header组件中去使用
    store.commit("setPath")  // 触发store的数据更新
    next()
})

export default router

Login.vue

login() {
      this.$refs['userForm'].validate((valid) => {
        if (valid) {  // 表单校验合法
          this.request.post("/user/login", this.user).then(res => {
            if (res.code === '200'){
              //存储用户信息到浏览器
              localStorage.setItem("user",JSON.stringify(res.data))
              localStorage.setItem("menus",JSON.stringify(res.data.menus))

              //动态设置当前用户的路由
              setRoutes()



              this.$router.push("/")
              this.$message.success("登录成功")
            }else {
              this.$message.error(res.msg)
            }
          })
        }
      });
    }

404页面编写

404.vue

<template>
<div style="overflow: hidden;height: 100%">
  <img src="../assets/404.png" alt="" style="width: 100%;height: 100%">
</div>
</template>

<script>
export default {
  name: "NotFound"
}
</script>

<style>


</style>

router/index.js

{
        path: '*',
        name: '404',
        component: () => import('../views/404.vue')
    },

六、权限菜单实现

RoleServiceImpl

@Override
    @Transactional
    public void setRoleMenu(Integer roleId, List<Integer> menuIds) {
        /**
         * 第一种方法
         * */
        /*QueryWrapper<RoleMenu> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_id",roleId);
        roleMenuMapper.delete(queryWrapper);*/

        /**
         * 第二种方法
         * */
        //先删除当前角色id所有的绑定关系
        roleMenuMapper.deleteMenuByRoleId(roleId);

        //再把前端传过来的菜单id数组绑定到当前这个角色id上
        List<Integer> menuIdsCopy = CollUtil.newArrayList(menuIds);
        for (Integer menuId : menuIds) {
            Menu menu = menuService.getById(menuId);
            //表示二级菜单 并且传过来的menuId数组里面没有它的父级id
            if (menu.getPid() != null && !menuIds.contains(menu.getPid())) {
                //那么我们就得补上这个父级id
                RoleMenu roleMenu = new RoleMenu();
                roleMenu.setRoleId(roleId);
                roleMenu.setMenuId(menuId);
                roleMenuMapper.insert(roleMenu);
                menuIdsCopy.add(menu.getPid());
            }
            RoleMenu roleMenu = new RoleMenu();
            roleMenu.setRoleId(roleId);
            roleMenu.setMenuId(menuId);
            roleMenuMapper.insert(roleMenu);
        }
    }

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import router from "@/router";

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        currentPathName: ''
    },
    mutations: {
        setPath (state) {
            state.currentPathName = localStorage.getItem("currentPathName")
        },
        logout(){
            localStorage.removeItem("user")
            localStorage.removeItem("menus")
            router.push("/login")
        }
    }
})

export default store

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import './assets/gloable.css'
import request from "@/utils/request";
import store from './store'

Vue.config.productionTip = false

Vue.use(ElementUI, {size: "mini"});

Vue.prototype.request = request
Vue.prototype.$store = store

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')

Header.vue

logout() {
      this.$store.commit("logout")
      this.$message.success("退出成功")
    }

Role.vue

           <el-button type="info" @click="selectMenu(scope.row)">分配菜单<i class="el-icon-menu"></i></el-button>




<el-dialog title="菜单分配" :visible.sync="menuDialogVis" width="30%" style="padding: 0 50px">
      <el-tree
          :check-strictly="true"
          :props="props"
          :data="menuData"
          :default-expanded-keys="expends"
          :default-checked-keys="checks"
          node-key="id"
          show-checkbox
          ref="tree"
      >
         <span class="custom-tree-node" slot-scope="{ node, data }">
           <span><i :class="data.icon"></i>{{ data.name }}</span>
         </span>
      </el-tree>
      <div slot="footer" class="dialog-footer">
        <el-button @click="menuDialogVis = false">取 消</el-button>
        <el-button type="primary" @click="saveRoleMenu">确 定</el-button>
      </div>
    </el-dialog>



roleFlag: ''




saveRoleMenu() {
      this.request.post("/role/setRoleMenu/" + this.roleId, this.$refs.tree.getCheckedKeys()).then(res => {
        if (res.code === '200') {
          this.$message.success("绑定成功")
          this.menuDialogVis = false


          //操作管理员角色后需要重新登陆
          if (this.roleFlag === 'ROLE_ADMIN') {
            this.$store.commit("logout")
          }

        } else {
          this.$message.error(res.msg)
        }
      })
    },





//分配菜单
    selectMenu(role) {
      this.menuDialogVis = true
      this.roleId = role.id
      this.roleFlag = role.flag
      //请求菜单数据
      this.request.get("/menu/findAll").then(res => {
        //注意data
        this.menuData = res.data

        //把后台返回的菜单数据处理成id数组
        this.expends = this.menuData.map(v => v.id);
      })

      this.request.get("/role/getRoleMenu/" + this.roleId).then(res => {
        this.checks = res.data

      })

    },






本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/723769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Numpy速通笔记

Numpy可以高效处理大数组的数据&#xff0c;因为&#xff1a; Numpy在一个连续的内存块中存储数据&#xff0c;独立于其他Python内置对象。Numpy是C写的&#xff0c;有优化&#xff0c;比Python内置序列使用的内存少可以在整个数组上进行复杂运算&#xff0c;不需要for循环 下…

table的tr动态增加(含html示例)

html页面table的tr动态增加&#xff08;含示例&#xff09; 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>简单示例</title><script type"text/javascript">function …

陶建辉在“2023 可信数据库发展大会”发表演讲,TDengine 入选中国数据库产业图谱

当前&#xff0c;全球数字经济加速发展&#xff0c;数据正在成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量。数据库作为存储与处理数据的关键技术&#xff0c;在数字经济大浪潮下&#xff0c;全球数据库产业中新技术、新业态、新模式不断涌现。 7 月 4…

mysql创建表练习

CREATE TABLE student ( Id int(10) primary key auto_increment comment "学号", Name varchar(20) not null comment "姓名", Sex enum(M,F) default M comment "性别", Birth year(4) comment "出生年份", Department varchar(20)…

-XX:+PrintCommandLineFlags

-XX:PrintCommandLineFlags把传递给虚拟机的参数输出&#xff08;隐式传递显式传递&#xff09;控制台打印信息-XX:ConcGCThreads3 -XX:G1ConcRefinementThreads13 -XX:GCDrainStackTargetSize64 -XX:InitialHeapSize254884992 -XX:MarkStackSize4194304 -XX:MaxHeapSize407815…

从混沌到秩序的蜕变,SRE解码云计算运维奥秘

什么是SRE SRE&#xff08;Site Reliability Engineering&#xff09;即站点可靠性工程&#xff0c;最初由Google公司提出&#xff0c;通过将开发、运维等多方面进行整合&#xff0c;协同推进系统可靠性&#xff0c;从而确保业务服务能够持久运行。 这是一种新的模式&#xff0…

7.6机试练习

1. 2105 IP Address 描述 Suppose you are reading byte streams from any device, representing IP addresses. Your task is to convert a 32 characters long sequence of ‘1s’ and ‘0s’ (bits) to a dotted decimal format. A dotted decimal format for an IP addres…

视频关键帧AI化的多种方法

视频关键帧AI化的逻辑是将视频切分成一帧帧的画面&#xff0c;然后使用SD绘画固定风格&#xff0c;最后统一在拼接在一起成为一个新的视频。 不管是Mov2Mov还是Multi Frame都能制作这种视频。但是这些操作起来比较麻烦&#xff0c;经过尝试处理较稳定的方法是可以通过img2im的…

Win10电脑开机PIN码怎么取消?

有的用户稀里糊涂的设置了PIN码之后&#xff0c;在开机时发现多了个PIN码&#xff0c;但又不知道电脑PIN码是什么意思&#xff0c;也不清楚开机PIN码怎么取消。您可以通过阅读以下内容&#xff0c;以了解什么是PIN以及如何取消PIN码。 PIN码是一种快捷登录密码方式&#xff0c;…

第九章、vim程序编辑器

9.1 vi与vim 9.1.1 为何要学vim 所有的 Unix Like 系统都会内置 vi 文书编辑器&#xff0c;其他的文书编辑器则不一定会存在&#xff1b; 很多个别软件的编辑接口都会主动调用 vi &#xff08;例如未来会谈到的 crontab, visudo, edquota等指令&#xff09;&#xff1b; vi…

最新版Flink CDC MySQL同步MySQL(一)

1.概述 Flink CDC 是Apache Flink 的一组源连接器&#xff0c;使用变更数据捕获 (CDC) 从不同数据库中获取变更。Apache Flink 的 CDC Connectors集成 Debezium 作为捕获数据更改的引擎。所以它可以充分发挥 Debezium 的能力。 2.支持的连接器 连接器数据库驱动mongodb-cdc…

深度学习神经网络学习笔记-论文研读-transformer及代码复现参考

摘要 优势序列转导模型基于复杂的循环或包括一个编码器和一个解码器的卷积神经网络。最好的表现良好的模型还通过attention 连接编码器和解码器机制。我们提出了一种新的简单的网络架构&#xff0c;Transformer&#xff0c; 完全基于注意力机制&#xff0c;省去了递归和卷积完…

在一个呼号前+B1/是啥意思?有人知道吗?

电台呼号有什么意义&#xff1f;呼号指配意义在于&#xff0c;识别各个不同的具体的电台或同一固定电台内使用2个以上频率时&#xff08;包括两个频率&#xff09;来识别每个不同频率&#xff0c;另外电台的类别和性质也取决于其呼号的组成形式&#xff0c;所以呼号一经确定&am…

Mycat2 使用教程(三)原始数据导入分库分表【MySQL分库分库分表】

Mycat2 使用教程&#xff08;三&#xff09;原始数据导入分库分表【MySQL分库分库分表】 本文主要描述mycat2完成分库分别数据源配置后&#xff0c;将数据导入的过程mysql 分库分表如果是新项目&#xff0c;则不用考虑本文内容mycat2如何配置分库分表&#xff1f;见上文 1.计…

排序链表问题

给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5] 示例 3&#xff1a;…

Hadoop下载安装(物理机)

1、下载Hadoop安装包## http://archive.apache.org/dist/hadoop/common 2、解压安装Hadoop 将hadoop-2-7.4.tar.gz包上传到/root/export/software目录 cd /root/export/software mkdir /root/exprot/servers tar -zxvf hadoop-2.7.4.tar.gz -C /root/export/servers/3、配置…

第三届DeepModeling黑客松竞赛

今年的Hackathon难度梯度设置很广&#xff0c;有偏向硬核开发的&#xff0c;有偏向应用的&#xff0c;还有面向初学者的教学布道赛道&#xff01;欢迎大家来围观&#xff01; 参赛链接

【抽奖实现源码】原生js实现简单九方格抽奖实现(附源码下载)

文章目录 写在前面涉及知识效果图1、搭建抽奖页面2、设置抽奖样式1&#xff09;奖项区块颜色2&#xff09;开始按钮背景色3&#xff09;启动初始块颜色 3、编写抽奖功能4、源码下载1&#xff09; 百度网盘2&#xff09;123云盘 总结 写在前面 之前在一次线下活动大屏上看到一个…

招商银行、江苏银行争相入局AIGC,“老银行”能否讲出“新故事”?

文 | 新熔财经 作者 | 和花 由ChatGPT引发的“大语言模型热潮”还没有过去。 六月&#xff0c;A股市场ChatGPT概念指数入选后股价涨幅超过20%的就超过30支&#xff0c;涨幅超过50%也有将近20支&#xff0c;像昆仑万维、万兴科技、神州泰岳、汤姆猫等公司&#xff0c;更是借着…

Qt扫盲-QMouseEvent 鼠标事件

QMouseEvent 鼠标事件理论 一、概述二、鼠标事件的传递三、组合修饰符四、鼠标坐标位置五、使用方式 一、概述 当在QWidget窗口内的鼠标按钮被按下或释放&#xff0c;或者鼠标光标被移动时&#xff0c;就会发生鼠标事件。 鼠标按下释放没有什么特殊的&#xff0c;但是鼠标移动…