系列文章目录

  1. 系统功能演示——基于SpringBoot和Vue的后台管理系统项目系列博客(一)
  2. Vue2安装并集成ElementUI——基于SpringBoot和Vue的后台管理系统项目系列博客(二)
  3. Vue2前端主体框架搭建——基于SpringBoot和Vue的后台管理系统项目系列博客(三)
  4. SpringBoot后端初始框架搭建——基于SpringBoot和Vue的后台管理系统项目系列博客(四)
  5. SpringBoot集成Mybatis——基于SpringBoot和Vue的后台管理系统项目系列博客(五)
  6. SpringBoot实现增删改查——基于SpringBoot和Vue的后台管理系统项目系列博客(六)
  7. SpringBoot实现分页查询——基于SpringBoot和Vue的后台管理系统项目系列博客(七)
  8. SpringBoot实现集成Mybatis-Plus和SwaggerUI——基于SpringBoot和Vue的后台管理系统项目系列博客(八)
  9. Vue实现增删改查——基于SpringBoot和Vue的后台管理系统项目系列博客(九)
  10. SpringBoot实现代码生成器——基于SpringBoot和Vue的后台管理系统项目系列博客(十)
  11. Vue使用路由——基于SpringBoot和Vue的后台管理系统项目系列博客(十一)
  12. SpringBoot和Vue实现导入导出——基于SpringBoot和Vue的后台管理系统项目系列博客(十二)
  13. SpringBoot和Vue实现用户登录注册与异常处理——基于SpringBoot和Vue的后台管理系统项目系列博客(十三)
  14. SpringBoot和Vue实现用户个人信息展示与保存与集成JWT——基于SpringBoot和Vue的后台管理系统项目系列博客(十四)
  15. SpringBoot和Vue实现文件上传与下载——基于SpringBoot和Vue的后台管理系统项目系列博客(十五)
  16. SpringBoot和Vue整合ECharts——基于SpringBoot和Vue的后台管理系统项目系列博客(十六)
  17. SpringBoot和Vue实现权限菜单功能——基于SpringBoot和Vue的后台管理系统项目系列博客(十七)
  18. SpringBoot实现1对1、1对多、多对多关联查询——基于SpringBoot和Vue的后台管理系统项目系列博客(十八)
  19. 用户前台页面设计与实现——基于SpringBoot和Vue的后台管理系统项目系列博客(十九)
  20. SpringBoot集成Redis实现缓存——基于SpringBoot和Vue的后台管理系统项目系列博客(二十)
  21. SpringBoot和Vue集成高德地图——基于SpringBoot和Vue的后台管理系统项目系列博客(二十一)
  22. SpringBoot和Vue集成视频播放组件——基于SpringBoot和Vue的后台管理系统项目系列博客(二十二)
  23. SpringBoot和Vue集成Markdown和多级评论——基于SpringBoot和Vue的后台管理系统项目系列博客(二十三)

项目资源下载

  1. GitHub下载地址
  2. Gitee下载地址
  3. 项目MySql数据库文件


前言

  今天的主要内容包括:新建角色数据表、使用代码生成器生成角色代码、测试生成的角色代码、新建菜单数据表、使用代码生成器生成菜单代码、测试生成的菜单代码、分配权限菜单基础功能实现、分配权限菜单图表显示与编辑功能、分配权限菜单的授权保存功能、赋予用户以角色、实现用户角色的动态菜单、实现用户菜单的动态路由、完成404提示界面、配置管理员修改用户权限后重新登录、父级菜单不显示的Bug解决、选择部分子菜单后子菜单全部选中的Bug解决、随机访问某一界面却进入404界面Bug的解决、访问修改密码界面却进入404界面Bug的解决、提交新增一级菜单信息没反应Bug的解决、新增菜单页面后授权给用户重新登录再点击新增菜单页面却出现404页面Bug的解决。可以看到今天的内容非常多,也有一定的难度,请各位读者一定要仔细跟着我做。废话不多少,下面就开始今天的学习!


一、新建角色数据表

  1. 在Mysql中新建数据表,添加如下字段
    在这里插入图片描述
  2. 表名如下
    在这里插入图片描述

二、使用代码生成器生成角色代码

  1. 在CodeGenerator.java中生成加入刚创建的表名
    在这里插入图片描述
  2. 在controller.java.vm中修改此部分为entity参数
    在这里插入图片描述
  3. 然后右键运行CodeGenerator.java,生成代码即可,最后我们发现已经成功生成了所需代码,非常方便
    在这里插入图片描述
  4. 然后加入如下两处代码,为后面的搜索做准备
    在这里插入图片描述

三、测试生成的角色代码

  1. 然后在前端中新建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="200" align="center">
                <template slot-scope="scope">
                    <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>
    </div>
</template>

<!--页面数据与动作Js代码-->
<script>
    export default {
        name: "Role",
        data() {
            return {
                tableData: [],
                total: 0,
                pageNum: 1,
                pageSize: 10,
                name: "",
                form: {},
                dialogFormVisible: false,
                multipleSelection: []
            }
        },
        // 请求分页查询数据
        created() {
            this.load()
        },
        methods: {
            // 将数据库查询操作封装
            load() {
                this.request.get("/role/page", {
                    params: {
                        pageNum: this.pageNum,
                        pageSize: this.pageSize,
                        name: this.name,
                    }
                }).then(res => {
                    console.log(res)
                    this.tableData = res.records
                    this.total = res.total

                })
            },
            save() {
                this.request.post("/role", this.form).then(res => {
                    if (res) {
                        this.$message.success("保存成功")
                        this.dialogFormVisible = false
                        this.load()
                    } else {
                        this.$message.error("保存失败")
                    }
                })
            },
            handleAdd() {
                this.dialogFormVisible = true
                this.form = {}
            },
            handleEdit(row) {
                this.form = row
                this.dialogFormVisible = true
            },
            del(id) {
                this.request.delete("/role/" + id).then(res => {
                    if (res) {
                        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) {
                        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()
            },
            exp() {
                window.open("http://localhost:9090/role/export")
            },
            handleExcelImportSuccess(){
                this.$message.success("文件导入成功!")
                this.load()
            }
        }
    }
</script>

<!--表格头部样式-->
<style>
    .headerBg {
        background: #eee !important;
    }
</style>
  1. 然后在index.js中加入路由
    在这里插入图片描述
  2. 然后在Aside.vue中加入角色管理侧边栏
    在这里插入图片描述
  3. 最后在前端测试一下,发现功能均完好
    在这里插入图片描述

四、新建菜单数据表

  1. 在Mysql中新建数据表,添加如下字段
    在这里插入图片描述
  2. 表名如下
    在这里插入图片描述

五、使用代码生成器生成菜单代码

  1. 在CodeGenerator.java中设置刚才的数据库名称
    在这里插入图片描述
  2. 然后运行代码生成器,我们发现已经成功生成所需代码
    在这里插入图片描述
  3. 在生成的MenuController.java中加入如下两行代码,为了实现后面的搜索功能
    在这里插入图片描述

六、测试生成的菜单代码

  1. 在Menu.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="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="200" align="center">
                <template slot-scope="scope">
                    <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.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>

<!--页面数据与动作Js代码-->
<script>
    export default {
        name: "Role",
        data() {
            return {
                tableData: [],
                total: 0,
                pageNum: 1,
                pageSize: 10,
                name: "",
                form: {},
                dialogFormVisible: false,
                multipleSelection: []
            }
        },
        // 请求分页查询数据
        created() {
            this.load()
        },
        methods: {
            // 将数据库查询操作封装
            load() {
                this.request.get("/menu/page", {
                    params: {
                        pageNum: this.pageNum,
                        pageSize: this.pageSize,
                        name: this.name,
                    }
                }).then(res => {
                    console.log(res)
                    this.tableData = res.records
                    this.total = res.total

                })
            },
            save() {
                this.request.post("/menu", this.form).then(res => {
                    if (res) {
                        this.$message.success("保存成功")
                        this.dialogFormVisible = false
                        this.load()
                    } else {
                        this.$message.error("保存失败")
                    }
                })
            },
            handleAdd() {
                this.dialogFormVisible = true
                this.form = {}
            },
            handleEdit(row) {
                this.form = row
                this.dialogFormVisible = true
            },
            del(id) {
                this.request.delete("/menu/" + id).then(res => {
                    if (res) {
                        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) {
                        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()
            },
            exp() {
                window.open("http://localhost:9090/menu/export")
            },
            handleExcelImportSuccess(){
                this.$message.success("文件导入成功!")
                this.load()
            }
        }
    }
</script>

<!--表格头部样式-->
<style>
    .headerBg {
        background: #eee !important;
    }
</style>
  1. 在index.js中设置路由
    在这里插入图片描述
  2. 在Aside.vue中加入菜单管理侧边栏
    在这里插入图片描述
  3. 最后测试一下,发现所有功能均完好
    在这里插入图片描述

七、分配权限菜单基础功能实现

  1. 在Role.vue中加入分配权限菜单功能的按钮
    在这里插入图片描述
  2. 效果如下图所示
    在这里插入图片描述
  3. 在Menu.vue中加入如下代码,方便后面的权限分配
    在这里插入图片描述
  4. 在Menu.java中加入如下字段
    在这里插入图片描述
  5. 在sys_menu表中加入如下父级字段
    在这里插入图片描述
  6. 并加入内容
    在这里插入图片描述
  7. 修改MenuController.java中的内容,为了后面的父子级菜单功能
    在这里插入图片描述
  8. 来到前端测试,发现成功显示父子目录
    在这里插入图片描述
  9. 加入新增子菜单按钮
    在这里插入图片描述
  10. 然后完善此函数功能
    在这里插入图片描述
  11. 最终效果如下图所示
    在这里插入图片描述
  12. 最后将其修改为如下形式,需要注意的是以上操作都在Menu.vue中进行
    在这里插入图片描述
  13. 然后在Role.vue中加入菜单分配输入框
    在这里插入图片描述
  14. 然后加入相关信息
    在这里插入图片描述
  15. 然后新增请求菜单数据的函数
    在这里插入图片描述
  16. 最终效果如下图所示,已经成功请求到动态数据了,需要注意以上操作都在Role.vue中完成
    在这里插入图片描述

八、分配权限菜单图标显示与编辑功能

  1. 新建数据表
    在这里插入图片描述
  2. 设置表名为sys_dict
    在这里插入图片描述
  3. 在其中输入如下内容
    在这里插入图片描述
  4. 在entity下新建Dict实体类
    在这里插入图片描述
  5. 在Dict.java中输入如下内容
    在这里插入图片描述
  6. 在mapper下新建DictMapper,方便后面的增删改查
    在这里插入图片描述
  7. 在其中输入如下内容
    在这里插入图片描述
  8. 在MenuController.java中引入DictMapper,方便后续操作
    在这里插入图片描述
  9. 然后在MenuController.java中增加查找图标的函数
    在这里插入图片描述
  10. 在Constants.java中加入图标类型变量
    在这里插入图片描述
  11. 在MenuController.java中加入查询图标类型的代码
    在这里插入图片描述
  12. 在Menu.vue中加入如下三处代码,为了选择图标
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  13. 然后我们来到前台测试,发现已经成功显示图标下拉菜单了
    在这里插入图片描述
  14. 然后点击保存,发现已经成功保存图标的代码
    在这里插入图片描述
  15. 但是现在只能显示图标代码,为了显示真正的图标,我们在Menu.vue中加入如下两处代码
    在这里插入图片描述
    在这里插入图片描述
  16. 然后来到前端测试,我们发现已经成功显示图标样式了
    在这里插入图片描述
  17. 但是角色管理那里还没有显示图标也没有默认展开,为了显示图标和默认展开我们同样在Role.vue中修改如下三处代码
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  18. 然后我们来到角色管理页面,我们发现已经成功显示图标了,而且默认展开
    在这里插入图片描述

九、分配权限菜单的授权保存功能

  1. 新建表输入如下字段内容
    在这里插入图片描述
  2. 将此表命名为sys_role_menu
    在这里插入图片描述
  3. 在entity中新建RoleMenu.java
    在这里插入图片描述
  4. 在RoleMenu.java中输入如下内容
    在这里插入图片描述
  5. 然后在mapper中新建RoleMenuMapper.java
    在这里插入图片描述
  6. 在RoleMenuMapper.java中输入如下代码
    在这里插入图片描述
  7. 在RoleMenuMapper.java中输入如下内容
    在这里插入图片描述
  8. 将IRoleService.java替换为如下内容,注意自己的路径
package com.ironmanjay.springboot.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ironmanjay.springboot.entity.Menu;
import com.ironmanjay.springboot.entity.Role;
import com.ironmanjay.springboot.entity.RoleMenu;
import com.ironmanjay.springboot.mapper.RoleMapper;
import com.ironmanjay.springboot.mapper.RoleMenuMapper;
import com.ironmanjay.springboot.service.IMenuService;
import com.ironmanjay.springboot.service.IRoleService;
import org.springframework.stereotype.Service;

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

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author IronmanJay
 * @since 2022-09-14
 */
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {

    @Resource
    private RoleMenuMapper roleMenuMapper;

    @Resource
    private IMenuService menuService;

    @Override
    public void setRoleMenu(Integer roleId, List<Integer> menuIds) {
        // 先删除当前角色id所有的绑定关系
        roleMenuMapper.deleteByRoleId(roleId);
        // 再把前端传过来的菜单id数组绑定到当前的这个角色id上去
        for (Integer menuId : menuIds) {
            RoleMenu roleMenu = new RoleMenu();
            roleMenu.setRoleId(roleId);
            roleMenu.setMenuId(menuId);
            roleMenuMapper.insert(roleMenu);
        }
    }

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

}
  1. 在RoleController.java中输入如下内容
    在这里插入图片描述
  2. 然后将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%">
            <el-tree
                    :props="props"
                    :data="menuData"
                    show-checkbox
                    node-key="id"
                    ref="tree"
                    :default-expanded-keys="expends"
                    :default-checked-keys="checks">
                <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="dialogFormVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveRoleMenu">确 定</el-button>
            </div>
        </el-dialog>
    </div>
</template>

<!--页面数据与动作Js代码-->
<script>
    export default {
        name: "Role",
        data() {
            return {
                tableData: [],
                total: 0,
                pageNum: 1,
                pageSize: 10,
                name: "",
                form: {},
                dialogFormVisible: false,
                menuDialogVis: false,
                multipleSelection: [],
                menuData: [],
                props: {
                    label: 'name',
                },
                expends: [],
                checks: [],
                roleId: 0
            }
        },
        // 请求分页查询数据
        created() {
            this.load()
        },
        methods: {
            // 将数据库查询操作封装
            load() {
                this.request.get("/role/page", {
                    params: {
                        pageNum: this.pageNum,
                        pageSize: this.pageSize,
                        name: this.name,
                    }
                }).then(res => {
                    console.log(res)
                    this.tableData = res.records
                    this.total = res.total

                });
            },
            save() {
                this.request.post("/role", this.form).then(res => {
                    if (res) {
                        this.$message.success("保存成功")
                        this.dialogFormVisible = false
                        this.load()
                    } else {
                        this.$message.error("保存失败")
                    }
                })
            },
            saveRoleMenu() {
                this.request.post("/role/roleMenu/" + this.roleId, this.$refs.tree.getCheckedKeys()).then(res => {
                    if (res.code === '200') {
                        this.$message.success("绑定成功")
                        this.menuDialogVis = false
                    } else {
                        this.$message.error(res.msg)
                    }
                })
            },
            handleAdd() {
                this.dialogFormVisible = true
                this.form = {}
            },
            handleEdit(row) {
                this.form = row
                this.dialogFormVisible = true
            },
            del(id) {
                this.request.delete("/role/" + id).then(res => {
                    if (res) {
                        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) {
                        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.roleId = roleId;
                // 请求菜单数据
                this.request.get("/menu", {}).then(res => {
                    console.log(res);
                    this.menuData = res.data;
                    // 把后台返回的菜单数据处理成id数组
                    this.expends = this.menuData.map(v => v.id)
                })
                this.request.get("/role/roleMenu/" + this.roleId).then(res => {
                    this.checks = res.data;
                })
            },
        }
    }
</script>

<!--表格头部样式-->
<style>
    .headerBg {
        background: #eee !important;
    }
</style>
  1. 来到前端测试授权功能
    在这里插入图片描述
  2. 最后发现已经将授权信息成功保存到数据库中,这样我们现阶段任务已经完成了
    在这里插入图片描述

十、赋予用户以角色

  1. 在sys_user中新增role字段
    在这里插入图片描述
  2. 在sys_role中新增flag字段
    在这里插入图片描述
  3. 并在sys_role中输入数据
    在这里插入图片描述
  4. 在User.java中加入角色字段
    在这里插入图片描述
  5. 在Role.java中加入唯一标识字段
    在这里插入图片描述
  6. 在Role.vue中添加如下两处代码
    在这里插入图片描述
    在这里插入图片描述
  7. 在User.vue中添加如下四处代码
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  8. 然后来到前端测试
    在这里插入图片描述
  9. 发现已经成功保存了用户的角色信息
    在这里插入图片描述

十一、实现用户角色的动态菜单

  1. 在UserDTO中加入两个新的字段
    在这里插入图片描述
  2. 在common中新建枚举类型RoleEnum.java
    在这里插入图片描述
  3. 在其中输入如下内容
    在这里插入图片描述
  4. 在RoleMapper.java中加入如下内容
    在这里插入图片描述
  5. 在MenuServiceImpl.java中输入如下内容
    在这里插入图片描述
  6. 在IMenuService.java中输入以下内容
    在这里插入图片描述
  7. 然后在MenuController.java中将此方法封装
    在这里插入图片描述
  8. 在UserServiceImpl.java中引入IMenuService
    在这里插入图片描述
  9. 在UserServiceImpl.java中加入如下函数,获取当前角色的菜单信息
    在这里插入图片描述
  10. 在UserServiceImpl.java中修改login函数,在登陆的时候设置用户的菜单列表
    在这里插入图片描述
  11. 在UserController.java中的login方法中增加如下代码
    在这里插入图片描述
  12. 然后来到前端测试,发现可以针对不同的用户显示不同的菜单
    在这里插入图片描述
  13. 在Login.vue中加入如下内容
    在这里插入图片描述
  14. 然后将Aside.vue全部替换为以下内容
<!--  侧边栏内容 -->
<template>
    <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
    >
        <div style="height: 60px; line-height: 60px; text-align: center">
            <img src="../assets/logo.png" alt="" style="width: 20px; position: relative; top: 5px; right: 5px">
            <b style="color: white" v-show="logoTextShow">后台管理系统</b>
        </div>
        <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>
    </el-menu>
</template>

<script>
    export default {
        name: "Aside",
        props: {
            isCollapse: Boolean,
            logoTextShow: Boolean,
        },
        data() {
            return {
                menus: localStorage.getItem("menus") ? JSON.parse(localStorage.getItem("menus")) : [],
                opens: localStorage.getItem("menus") ? JSON.parse(localStorage.getItem("menus")).map(v => v.id + '') : [],
            }
        }
    }
</script>

<style scoped>

</style>
  1. 然后来到前端发现,不同的用户角色会显示不同的菜单,这样就实现了我们的功能
    ①:普通用户
    在这里插入图片描述
    ②:超级用户
    在这里插入图片描述

十二、实现用户菜单的动态路由

  1. 在sys_menu中加入新的字段,用来保存页面路径
    在这里插入图片描述
  2. 在Menu.java中加入新的对应字段
    在这里插入图片描述
  3. 在Menu.vue中加入两处对应字段
    在这里插入图片描述
    在这里插入图片描述
  4. 然后来到前端测试,给每个页面输入对应的页面路径
    在这里插入图片描述
  5. 然后将其保存成如下形式即可
    在这里插入图片描述
  6. 将router/index.js中的全部内容替换为如下内容
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from "@/store/store";

Vue.use(VueRouter)

const routes = [
    // 用户登陆页面
    {
        path: '/login',
        name: 'Login',
        component: () => import('../views/Login.vue')
    },
    // 用户注册页面
    {
        path: '/register',
        name: 'Register',
        component: () => import('../views/Register.vue')
    },
    {
        path: '/404',
        name: '404',
        component: () => import('../views/404.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 => {
            if (item.path) { // 当且仅当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)
        }
    }
}

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

// 路由守卫
router.beforeEach((to, from, next) => {
    localStorage.setItem("currentPathName", to.name)  // 设置当前的路由名称,为了在Header组件中去使用
    store.commit("setPath")  // 触发store的数据更新
    // 未找到路由的情况
    if (!to.matched.length) {
        const storeMenus = localStorage.getItem("menus")
        if (storeMenus) {
            next("/404")
        } else {
            // 跳回登录页面
            next("/login")
        }
    }
    next()  // 放行路由
})

export default router
  1. 然后在Login.vue中添加如下两处代码
    在这里插入图片描述
    在这里插入图片描述
  2. 然后来到前端发现,已经可以针对不同的用户角色,产生不同的动态菜单路由了
    在这里插入图片描述

十三、完成404提示界面

  1. 在此目录下新建404.vue
    在这里插入图片描述
  2. 在router/index.js中写入固定路由404
    在这里插入图片描述
  3. 然后上网上随便找一张404的图片,放在此目录下
    在这里插入图片描述
  4. 然后将404.vue中全部替换为如下内容
<template>
    <div style="overflow: hidden; height: 100vh">
        <img src="../assets/404.png" alt="" style="width: 100%; height: 100%">
    </div>
</template>

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

<style>
    .bgImg {
        background: url("../assets/404.png") no-repeat;
        background-size: 100% 100vh;
    }
</style>
  1. 然后来到前端发现,访问其他非法界面已经可以显示404的提示了,说明成功了
    在这里插入图片描述

十四、配置管理员修改用户权限后重新登陆

  1. 在store/store.js中加入如下两处代码
    在这里插入图片描述
  2. 然后在Header.vue中加入如下代码
    在这里插入图片描述
  3. 在Role.vue中修改如下四处代码
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  4. 然后来到前端测试,发现权限修改后已经成功登录了
    在这里插入图片描述

十五、父级菜单不显示的Bug解决

  1. 当我们给普通用户分配子菜单后
    在这里插入图片描述
  2. 但是当我们登录普通用户时,明明已经给这个用户分配了子菜单,但是并没有显示,如下图所示,我们要解决这个问题
    在这里插入图片描述
  3. 我们只需要在RoleServiceImpl.java中加入如下代码即可
    在这里插入图片描述
  4. 然后我们来到前端测试,发现已经成功针对不同的用户显示不同的菜单了
    在这里插入图片描述

十六、选择部分子菜单后子菜单全部选中的Bug解决

  1. 当我们只给某个角色赋予部分子菜单后,发现显示的时候展示了全部子菜单内容,这不是我们想要的,我们就要解决这个问题
    在这里插入图片描述
  2. 首先在MenuController.java中加入如下内容
    在这里插入图片描述
  3. 然后在Role.vue中加入如下内容
    在这里插入图片描述
  4. 当我们来到前端测试后发现,当我们选中子菜单后,全部的子菜单也并没有选中,而且侧边栏也没有选中,这样就解决了我们的Bug
    在这里插入图片描述

十七、随机访问某一界面却进入404界面Bug的解决

  1. 目前有一个Bug,就是当我们点击某个界面的时候,却是打开了404的界面,我们要解决这个问题
    在这里插入图片描述
  2. 首先在index.js中修改如下一处代码
    在这里插入图片描述
  3. 然后再在index.js中修改如下内容
    在这里插入图片描述
  4. 然后来到前端测试发现问题已经解决
    在这里插入图片描述

十八、访问修改密码界面却进入404界面Bug的解决

  1. 首先在如下位置新建UserPasswordDTO.java,并在其中输入如下内容
    在这里插入图片描述
  2. 然后在UserMapper.java中输入如下内容
    在这里插入图片描述
  3. 然后在mapper中新建UserMapper.java,然后在其中输入如下内容
    在这里插入图片描述
  4. 然后在impl中的UserServiceImpl.java中引入此mapper
    在这里插入图片描述
  5. 然后还是在UserServiceImpl.java中新建修改密码的函数
    在这里插入图片描述
  6. 然后在IUserService.java中新建修改密码的接口
    在这里插入图片描述
  7. 然后在UserController.java中新建修改密码的函数
    在这里插入图片描述
  8. 然后在views中新建Password.vue
    在这里插入图片描述
  9. 然后将Password.vue全部替换为如下内容
<template>
  <el-card style="width: 500px;">
    <el-form label-width="120px" size="small" :model="form" :rules="rules" ref="pass">
      <el-form-item label="原密码" prop="password">
        <el-input v-model="form.password" autocomplete="off" show-password></el-input>
      </el-form-item>
      <el-form-item label="新密码" prop="newPassword">
        <el-input v-model="form.newPassword" autocomplete="off" show-password></el-input>
      </el-form-item>
      <el-form-item label="确认新密码" prop="confirmPassword">
        <el-input v-model="form.confirmPassword" autocomplete="off" show-password></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="save">确 定</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>

<script>
export default {
  name: "Password",
  data() {
    return {
      form: {},
      user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {},
      rules: {
        password: [
          { required: true, message: '请输入原密码', trigger: 'blur' },
          { min: 3, message: '长度不少于3位', trigger: 'blur' }
        ],
        newPassword: [
          { required: true, message: '请输入新密码', trigger: 'blur' },
          { min: 3, message: '长度不少于3位', trigger: 'blur' }
        ],
        confirmPassword: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 3, message: '长度不少于3位', trigger: 'blur' }
        ],
      }
    }
  },
  created() {
    this.form.username = this.user.username
  },
  methods: {
    save() {
      this.$refs.pass.validate((valid) => {
        if (valid) {
          if (this.form.newPassword !== this.form.confirmPassword) {
            this.$message.error("2次输入的新密码不相同")
            return false
          }
          this.request.post("/user/password", this.form).then(res => {
            if (res.code === '200') {
              this.$message.success("修改成功")
              this.$store.commit("logout")
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      })
    },
  }
}
</script>

<style>
.avatar-uploader {
  text-align: center;
  padding-bottom: 10px;
}
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.avatar-uploader .el-upload:hover {
  border-color: #409EFF;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 138px;
  height: 138px;
  line-height: 138px;
  text-align: center;
}
.avatar {
  width: 138px;
  height: 138px;
  display: block;
}
</style>
  1. 然后在index.js中加入如下路由内容
    在这里插入图片描述
  2. 然后来到前端测试发现已经可以成功修改密码
    在这里插入图片描述
  3. 修改密码后回要求重新登陆
    在这里插入图片描述

十九、提交新增一级菜单信息没反应Bug的解决

  1. 当我们在菜单管理中,新增一级菜单,点击提交后发现没反应
    在这里插入图片描述
  2. 此时我们修改Menu.vue中此部分内容即可
    在这里插入图片描述
  3. 然后来到前端测试发现已经成功了
    在这里插入图片描述

二十、新增菜单页面后授权给用户重新登陆再点击新增的菜单页面却出现404页面Bug的解决

  1. 当我们新增菜单后,将此菜单的权限授予用户,重新登陆后,点击新增的菜单页面却产生了404错误,我们要解决这个问题
    在这里插入图片描述
    在这里插入图片描述
  2. 为了解决这个问题,我们首先在index.js中新建一个重置路由的方法
    在这里插入图片描述
  3. 然后在store.js中引用这个方法用来重置路由
    在这里插入图片描述
  4. 再来到前端用同样的方法测试,发现问题已经成功解决了,不会出现404异常界面了(Ps:我使用的文件管理的界面进行测试的,读者也可以新建测试页面)
    在这里插入图片描述

总结

  以上就是今天学习的全部内容了,可以看到内容非常多,而且难度确实不小,但是只要跟着我一步一步做肯定是没问题的,今天就暂时到这里。明天给大家带来关于SpringBoot实现1对1、1对多、多对多关联查询的相关内容。明天见!

Logo

前往低代码交流专区

更多推荐