1. 菜单管理需求 37
1.1 需求描述 37
不同角色的用户登录后台管理系统拥有不同的菜单权限与功能权限,我们前端是基于:vue-admin-template这个模块开发的,因此我们菜单表设计也必须基于前端模板进行设计。
前端框架vue-admin-template菜单其实就是我们配置的路由:
{
path: '/system',
component: Layout,
meta: {
title: '系统管理',
icon: 'el-icon-s-tools'
},
alwaysShow: true,
children: [
{
name: 'sysUser',
path: 'sysUser',
component: () => import('@/views/system/sysUser/list'),
meta: {
title: '用户管理',
icon: 'el-icon-s-custom'
},
},
{
path: 'sysRole',
component: () => import('@/views/system/sysRole/list'),
meta: {
title: '角色管理',
icon: 'el-icon-s-help'
},
},
{
name: 'sysMenu',
path: 'sysMenu',
component: () => import('@/views/system/sysMenu/list'),
meta: {
title: '菜单管理',
icon: 'el-icon-s-unfold'
},
},
{
path: 'assignAuth',
component: () => import('@/views/system/sysRole/assignAuth'),
meta: {
activeMenu: '/system/sysRole',
title: '角色授权'
},
hidden: true,
}
]
}
因此,菜单表的设计必须满足路由配置的必要信息
1.2 菜单表的设计
1.2.1 创建表 37
重点字段说明:
type:菜单类型,分为:目录、菜单与按钮
目录:一个分类(可理解为一级菜单)、目录下级节点可以为目录与菜单
菜单:一个具体页面,菜单的下级节点只能是按钮
按钮:页面上的功能
path:对应路由里面的路由地址path
component:对应路由里面的组件component
perms:对应菜单的功能权限标识
icom:对应路由的菜单图标
菜单表
CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属上级',
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '名称',
`type` tinyint(3) NOT NULL DEFAULT '0' COMMENT '类型(0:目录,1:菜单,2:按钮)',
`path` varchar(100) DEFAULT NULL COMMENT '路由地址',
`component` varchar(100) DEFAULT NULL COMMENT '组件路径',
`perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) DEFAULT NULL COMMENT '图标',
`sort_value` int(11) DEFAULT NULL COMMENT '排序',
`status` tinyint(4) DEFAULT NULL COMMENT '状态(0:禁止,1:正常)',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=58 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
#
# Data for table "sys_menu"
#
INSERT INTO `sys_menu` VALUES (2,0,'系统管理',0,'system','Layout',NULL,'el-icon-s-tools',1,1,'2021-05-31 18:05:37','2022-06-09 09:23:24',0),(3,2,'用户管理',1,'sysUser','system/sysUser/list','','el-icon-s-custom',1,1,'2021-05-31 18:05:37','2022-06-09 09:22:47',0),(4,2,'角色管理',1,'sysRole','system/sysRole/list','','el-icon-user-solid',2,1,'2021-05-31 18:05:37','2022-06-09 09:37:18',0),(5,2,'菜单管理',1,'sysMenu','system/sysMenu/list','','el-icon-s-unfold',3,1,'2021-05-31 18:05:37','2022-06-09 09:37:21',0),(6,3,'查看',2,NULL,NULL,'bnt.sysUser.list',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(7,3,'添加',2,NULL,NULL,'bnt.sysUser.add',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(8,3,'修改',2,NULL,NULL,'bnt.sysUser.update',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(9,3,'删除',2,NULL,NULL,'bnt.sysUser.remove',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(10,4,'查看',2,NULL,NULL,'bnt.sysRole.list',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(11,4,'添加',2,NULL,NULL,'bnt.sysRole.add',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(12,4,'修改',2,NULL,NULL,'bnt.sysRole.update',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(13,4,'删除',2,NULL,NULL,'bnt.sysRole.remove',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(14,5,'查看',2,NULL,NULL,'bnt.sysMenu.list',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(15,5,'添加',2,NULL,NULL,'bnt.sysMenu.add',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(16,5,'修改',2,NULL,NULL,'bnt.sysMenu.update',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(17,5,'删除',2,NULL,NULL,'bnt.sysMenu.remove',NULL,1,1,'2021-05-31 18:05:37','2022-06-09 09:22:38',0),(18,3,'分配角色',2,NULL,NULL,'bnt.sysUser.assignRole',NULL,1,1,'2022-05-23 17:14:32','2022-06-09 09:22:38',0),(19,4,'分配权限',2,'assignAuth','system/sysRole/assignAuth','bnt.sysRole.assignAuth',NULL,1,1,'2022-05-23 17:18:14','2022-06-09 09:22:38',0),(20,2,'部门管理',1,'sysDept','system/sysDept/list','','el-icon-s-operation',4,1,'2022-05-24 10:07:05','2022-06-09 09:38:12',0),(21,20,'查看',2,NULL,NULL,'bnt.sysDept.list',NULL,1,1,'2022-05-24 10:07:44','2022-06-09 09:22:38',0),(22,2,'岗位管理',1,'sysPost','system/sysPost/list','','el-icon-more-outline',5,1,'2022-05-24 10:25:30','2022-06-09 09:38:13',0),(23,22,'查看',2,NULL,NULL,'bnt.sysPost.list',NULL,1,1,'2022-05-24 10:25:45','2022-06-09 09:22:38',0),(24,20,'添加',2,NULL,NULL,'bnt.sysDept.add',NULL,1,1,'2022-05-25 15:31:27','2022-06-09 09:22:38',0),(25,20,'修改',2,NULL,NULL,'bnt.sysDept.update',NULL,1,1,'2022-05-25 15:31:41','2022-06-09 09:22:38',0),(26,20,'删除',2,NULL,NULL,'bnt.sysDept.remove',NULL,1,1,'2022-05-25 15:31:59','2022-06-09 09:22:38',0),(27,22,'添加',2,NULL,NULL,'bnt.sysPost.add',NULL,1,1,'2022-05-25 15:32:44','2022-06-09 09:22:38',0),(28,22,'修改',2,NULL,NULL,'bnt.sysPost.update',NULL,1,1,'2022-05-25 15:32:58','2022-06-09 09:22:38',0),(29,22,'删除',2,NULL,NULL,'bnt.sysPost.remove',NULL,1,1,'2022-05-25 15:33:11','2022-06-09 09:22:38',0),(30,34,'操作日志',1,'sysOperLog','system/sysOperLog/list','','el-icon-document-remove',7,1,'2022-05-26 16:09:59','2022-06-09 09:39:23',0),(31,30,'查看',2,NULL,NULL,'bnt.sysOperLog.list',NULL,1,1,'2022-05-26 16:10:17','2022-06-09 09:22:38',0),(32,34,'登录日志',1,'sysLoginLog','system/sysLoginLog/list','','el-icon-s-goods',8,1,'2022-05-26 16:36:13','2022-06-09 09:39:24',0),(33,32,'查看',2,NULL,NULL,'bnt.sysLoginLog.list',NULL,1,1,'2022-05-26 16:36:31','2022-06-09 09:36:36',0),(34,2,'日志管理',0,'log','ParentView','','el-icon-tickets',6,1,'2022-05-31 13:23:07','2022-06-09 09:39:22',0),(35,0,'审批设置',0,'processSet','Layout','','el-icon-setting',1,1,'2022-12-01 09:32:46','2022-12-01 09:32:46',0),(36,35,'审批模板',1,'processTemplate','processSet/processTemplate/list','','el-icon-s-help',2,1,'2022-12-01 09:37:08','2022-12-19 14:10:48',0),(37,36,'查看',2,'','','bnt.processTemplate.list','',1,1,'2022-12-01 09:37:49','2022-12-01 09:37:49',0),(38,36,'审批模板设置',2,'templateSet','processSet/processTemplate/templateSet','bnt.processTemplate.templateSet','',1,1,'2022-12-01 14:52:08','2022-12-13 18:11:56',0),(39,35,'审批类型',1,'processType','processSet/processType/list','','el-icon-s-unfold',1,1,'2022-12-02 14:46:18','2022-12-13 18:12:24',0),(40,39,'查看',2,'','','bnt.processType.list','',1,1,'2022-12-02 14:46:41','2022-12-02 14:46:41',0),(41,0,'审批管理',0,'processMgr','Layout','','el-icon-more-outline',1,1,'2022-12-02 14:48:11','2022-12-20 09:29:30',0),(42,41,'审批列表',1,'process','processMgr/process/list','','el-icon-document-remove',1,1,'2022-12-02 14:49:06','2022-12-02 14:59:17',0),(43,42,'查看',2,'','','bnt.process.list','',1,1,'2022-12-02 14:49:24','2022-12-02 14:49:24',0),(44,36,'在线流程设置',2,'onlineProcessSet','processSet/processTemplate/onlineProcessSet','bnt.processTemplate.onlineProcessSet','',1,1,'2022-12-08 10:13:15','2022-12-19 18:57:35',0),(45,39,'添加',2,'','','bnt.processType.add','',1,1,'2022-12-09 09:14:53','2022-12-09 09:14:53',0),(46,39,'修改',2,'','','bnt.processType.update','',1,1,'2022-12-09 09:15:10','2022-12-09 09:15:10',0),(47,39,'删除',2,'','','bnt.processType.remove','',1,1,'2022-12-09 09:15:25','2022-12-09 09:15:25',0),(48,36,'删除',2,'','','bnt.processTemplate.remove','',1,1,'2022-12-09 09:22:29','2022-12-09 09:22:29',0),(49,36,'发布',2,'','','bnt.processTemplate.publish','',1,1,'2022-12-09 09:24:47','2022-12-09 09:24:47',0),(50,0,'公众号菜单',0,'wechat','Layout','','el-icon-s-operation',1,1,'2022-12-13 09:06:58','2022-12-21 11:20:55',0),(51,50,'菜单列表',1,'menu','wechat/menu/list','','el-icon-s-help',1,1,'2022-12-13 09:07:52','2022-12-13 09:09:49',0),(52,51,'查看',2,'','','bnt.menu.list','',1,1,'2022-12-13 09:08:48','2022-12-13 17:58:23',0),(53,51,'添加',2,'','','bnt.menu.add','',1,1,'2022-12-13 16:29:25','2022-12-13 17:58:34',0),(54,51,'修改',2,'','','bnt.menu.update','',1,1,'2022-12-13 16:29:41','2022-12-13 17:58:42',0),(55,51,'删除',2,'','','bnt.menu.remove','',1,1,'2022-12-13 16:29:59','2022-12-13 17:58:47',0),(56,51,'删除微信菜单',2,'','','bnt.menu.removeMenu','',1,1,'2022-12-13 16:30:36','2022-12-13 17:58:54',0),(57,51,'同步微信菜单',2,'','','bnt.menu.syncMenu','',1,1,'2022-12-13 16:31:00','2022-12-13 17:59:01',0);
角色菜单关系表
CREATE TABLE `sys_role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL DEFAULT '0',
`menu_id` bigint(11) NOT NULL DEFAULT '0',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
PRIMARY KEY (`id`),
KEY `idx_role_id` (`role_id`),
KEY `idx_menu_id` (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8 COMMENT='角色菜单';
1.2.2 页面效果 37
我们最终要做到这样的效果
2. 数据准备 37
依然使用代码生成器
再持久层,业务层重新引入相应的实体类
service-oa模块
SysMenuController
package com.atguigu.auth.controller;
import com.atguigu.auth.service.SysMenuService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 菜单管理控制层 38
*/
@Api(tags = "菜单管理接口")
@RestController
@RequestMapping("/admin/system/sysMenu")
public class SysMenuController {
@Autowired
private SysMenuService sysMenuService;
}
3. 功能实现 38
3.1 新增菜单 38
service-oa模块
SysMenuController
//新增菜单 38
@ApiOperation(value = "新增菜单")
@PostMapping("save")
public Result save(@RequestBody SysMenu sysMenu) {
sysMenuService.save(sysMenu);
return Result.ok();
}
3.2 修改菜单 38
service-oa模块
SysMenuController
//修改菜单 38
@ApiOperation(value = "修改菜单")
@PutMapping("update")
public Result updateById(@RequestBody SysMenu sysMenu) {
sysMenuService.updateById(sysMenu);
return Result.ok();
}
3.3 删除菜单 38
service-oa模块
SysMenuController
//删除菜单 38
@ApiOperation(value = "删除菜单")
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id) {
sysMenuService.removeById(id);
return Result.ok();
}
3.4 菜单列表 38
重点用于获取菜单信息 ,因为前端是需要类似于树型的数据,我们后端查询到返回的数据也要是树形的,不能简单的查询 返回
service-oa模块
菜单管理控制层SysMenuController
//菜单列表接口,用于获取菜单信息 38
@ApiOperation("菜单列表")
@GetMapping("findNodes")
public Result findNodes() {
List<SysMenu> list = sysMenuService.findNodes();
return Result.ok(list);
}
菜单管理业务层接口SysMenuService
package com.atguigu.auth.service;
import com.atguigu.model.system.SysMenu;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 菜单表 服务类
* </p>
*
* @author atguigu
* @since 2023-06-27
*/
public interface SysMenuService extends IService<SysMenu> {
//菜单列表接口,用于获取菜单信息 38
List<SysMenu> findNodes();
}
菜单管理业务层接口实现类SysMenuServiceImpl
package com.atguigu.auth.service.impl;
import com.atguigu.auth.mapper.SysMenuMapper;
import com.atguigu.auth.service.SysMenuService;
import com.atguigu.auth.utils.MenuHelper;
import com.atguigu.model.system.SysMenu;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* 菜单表 服务实现类
* </p>
*
* @author atguigu
* @since 2023-06-27
*/
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
//菜单列表接口,用于获取菜单信息 38
@Override
public List<SysMenu> findNodes() {
//1 查询所有菜单数据 38
//经过下面的处理将数处理成需要的树形
List<SysMenu> sysMenuList = baseMapper.selectList(null);
//2 构建树形结构
// {
// 第一层
// children:[
// {
// 第二层
// ....
// }
// ]
// }
//将形成树形结构的代码分装成工具类MenuHelper
List<SysMenu> resultList = MenuHelper.buildTree(sysMenuList);
return resultList;
}
}
service-oa模块
工具类MenuHelper
MenuHelper
package com.atguigu.auth.utils;
import com.atguigu.model.system.SysMenu;
import java.util.ArrayList;
import java.util.List;
//工具类 38
public class MenuHelper {
//菜单列表接口,用于获取菜单信息 38
//使用递归方法建菜单
public static List<SysMenu> buildTree(List<SysMenu> sysMenuList) {
//创建list集合,用于返回最终数据
List<SysMenu> trees = new ArrayList<>();
//把所有菜单数据进行遍历
for(SysMenu sysMenu:sysMenuList) {
//递归入口进入
//parentId=0是入口(父亲id=0即程序递归入口)
if(sysMenu.getParentId().longValue()==0) {
//找到父亲,把它放入树中
trees.add(getChildren(sysMenu,sysMenuList));
}
}
return trees;
}
public static SysMenu getChildren(SysMenu sysMenu,
List<SysMenu> sysMenuList) {
//父亲节点初始化 children 属性为空的 ArrayList 对象是为了确保递归过程中能
// 够正常操作和添加子对象,确保即使在没有子对象的情况下,该属性也不会为 null,
// 而是一个空的集合。避免可能的空指针异常
sysMenu.setChildren(new ArrayList<SysMenu>());
//遍历所有菜单数据,判断 id 和 parentId对应关系
for(SysMenu it: sysMenuList) {
//如果本对象的id和遍历到的对象的id一直,就证明遍历到的这个对象是本对象的孩子,可进入继续递归
if(sysMenu.getId().longValue() == it.getParentId().longValue()) {
//每个孩子节点初始化 children 属性为空的 ArrayList 对象是为了确保递归过程中能
// 够正常操作和添加子对象,确保即使在没有子对象的情况下,该属性也不会为 null,
// 而是一个空的集合。避免可能的空指针异常
if (sysMenu.getChildren() == null) {
sysMenu.setChildren(new ArrayList<>());
}
//找到孩子就放到sysMenu的一个叫做children的属性中(这个children是个list集合)
sysMenu.getChildren().add(getChildren(it,sysMenuList));
}
}
//等递归结束,孩子也都封装好了,我们将对象(这个对象就是传来的父亲)返回
//父亲孩子一层套一层,父亲有孩子,父亲的孩子有孩子,孩子有孩子,都被封装好了,最后将封装好的父亲返回
return sysMenu;
}
}
测试
浏览器输入http://localhost:8800/doc.html
3.5 完善删除 39
当删除一个节点时,应该判断器下面有没有子节点,有的话就不能删
service-oa模块
菜单管理控制层SysMenuController
//完善删除菜单 39
@ApiOperation(value = "删除菜单")
@DeleteMapping("remove/{id}")
public Result remove(@PathVariable Long id) {
sysMenuService.removeMenuById(id);
return Result.ok();
}
菜单管理业务层接口SysMenuService
//完善删除菜单 39
void removeMenuById(Long id);
菜单管理业务层接口实现类SysMenuServiceImpl
//完善删除菜单 39
@Override
public void removeMenuById(Long id) {
//判断当前菜单是否有下一层菜单
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
//解释SysMenu::getParentId起到了限定查询范围的作用,将id值作为比较条件,再该范围内比较查询
wrapper.eq(SysMenu::getParentId,id);
Integer count = baseMapper.selectCount(wrapper);
if(count > 0) {
throw new GuiguException(201,"菜单不能删除");
}
baseMapper.deleteById(id);
}
4. 前端 39
4.1 添加路由 39
修改 src/router/index.js 文件
index.js
//菜单管理路由
{
name: 'sysMenu',
path: 'sysMenu',
component: () => import('@/views/system/sysMenu/list'),
meta: {
title: '菜单管理',
icon: 'el-icon-s-unfold'
},
}
创建页面
4.2 定义基础api 39
创建文件 src/api/system/sysMenu.js
sysMenu.js
import request from '@/utils/request'
/*
菜单管理相关的API请求函数
*/
const api_name = '/admin/system/sysMenu'
export default {
/*
获取权限(菜单/功能)列表
*/
findNodes() {
return request({
url: `${api_name}/findNodes`,
method: 'get'
})
},
/*
删除一个权限项
*/
removeById(id) {
return request({
url: `${api_name}/remove/${id}`,
method: "delete"
})
},
/*
保存一个权限项
*/
save(sysMenu) {
return request({
url: `${api_name}/save`,
method: "post",
data: sysMenu
})
},
/*
更新一个权限项
*/
updateById(sysMenu) {
return request({
url: `${api_name}/update`,
method: "put",
data: sysMenu
})
}
}
4.3 实现功能页面
src/views/system/sysMenu/list.vue
list.vue
<template>
<div class="app-container">
<!-- 工具条 -->
<div class="tools-div">
<el-button type="success" icon="el-icon-plus" size="mini" @click="add()">添 加</el-button>
</div>
<el-table
:data="sysMenuList"
style="width: 100%;margin-bottom: 20px;margin-top: 10px;"
row-key="id"
border
:default-expand-all="false"
:tree-props="{children: 'children'}">
<el-table-column prop="name" label="菜单名称" width="160"/>
<el-table-column label="图标">
<template slot-scope="scope">
<i :class="scope.row.icon"></i>
</template>
</el-table-column>
<el-table-column prop="perms" label="权限标识" width="160"/>
<el-table-column prop="path" label="路由地址" width="120"/>
<el-table-column prop="component" label="组件路径" width="160"/>
<el-table-column prop="sortValue" label="排序" width="60"/>
<el-table-column label="状态" width="80">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status === 1" disabled="true">
</el-switch>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160"/>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="success" v-if="scope.row.type !== 2" icon="el-icon-plus" size="mini" @click="add(scope.row)" title="添加下级节点"/>
<el-button type="primary" icon="el-icon-edit" size="mini" @click="edit(scope.row)" title="修改"/>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeDataById(scope.row.id)" title="删除" :disabled="scope.row.children.length > 0"/>
</template>
</el-table-column>
</el-table>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="40%" >
<el-form ref="dataForm" :model="sysMenu" label-width="150px" size="small" style="padding-right: 40px;">
<el-form-item label="上级部门" v-if="sysMenu.id === ''">
<el-input v-model="sysMenu.parentName" disabled="true"/>
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="sysMenu.type" :disabled="typeDisabled">
<el-radio :label="0" :disabled="type0Disabled">目录</el-radio>
<el-radio :label="1" :disabled="type1Disabled">菜单</el-radio>
<el-radio :label="2" :disabled="type2Disabled">按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="sysMenu.name"/>
</el-form-item>
<el-form-item label="图标" prop="icon" v-if="sysMenu.type !== 2">
<el-select v-model="sysMenu.icon" clearable>
<el-option v-for="item in iconList" :key="item.class" :label="item.class" :value="item.class">
<span style="float: left;">
<i :class="item.class"></i> <!-- 如果动态显示图标,这里添加判断 -->
</span>
<span style="padding-left: 6px;">{{ item.class }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="sysMenu.sortValue" controls-position="right" :min="0" />
</el-form-item>
<el-form-item prop="path">
<span slot="label">
<el-tooltip content="访问的路由地址,如:`sysUser`" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
路由地址
</span>
<el-input v-model="sysMenu.path" placeholder="请输入路由地址" />
</el-form-item>
<el-form-item prop="component" v-if="sysMenu.type !== 0">
<span slot="label">
<el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
组件路径
</span>
<el-input v-model="sysMenu.component" placeholder="请输入组件路径" />
</el-form-item>
<el-form-item v-if="sysMenu.type === 2">
<el-input v-model="sysMenu.perms" placeholder="请输入权限标识" maxlength="100" />
<span slot="label">
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(hasAuthority('bnt.sysRole.list'))" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
权限字符
</span>
</el-form-item>
<el-form-item label="状态" prop="type">
<el-radio-group v-model="sysMenu.status">
<el-radio :label="1">正常</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small" icon="el-icon-refresh-right">取 消</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveOrUpdate()" size="small">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import api from '@/api/system/sysMenu'
const defaultForm = {
id: '',
parentId: '',
name: '',
type: 0,
path: '',
component: '',
perms: '',
icon: '',
sortValue: 1,
status: 1
}
export default {
// 定义数据
data() {
return {
sysMenuList: [],
expandKeys: [], // 需要自动展开的项
typeDisabled: false,
type0Disabled: false,
type1Disabled: false,
type2Disabled: false,
dialogTitle: '',
dialogVisible: false,
sysMenu: defaultForm,
saveBtnDisabled: false,
iconList: [
{
class: "el-icon-s-tools",
},
{
class: "el-icon-s-custom",
},
{
class: "el-icon-setting",
},
{
class: "el-icon-user-solid",
},
{
class: "el-icon-s-help",
},
{
class: "el-icon-phone",
},
{
class: "el-icon-s-unfold",
},
{
class: "el-icon-s-operation",
},
{
class: "el-icon-more-outline",
},
{
class: "el-icon-s-check",
},
{
class: "el-icon-tickets",
},
{
class: "el-icon-s-goods",
},
{
class: "el-icon-document-remove",
},
{
class: "el-icon-warning",
},
{
class: "el-icon-warning-outline",
},
{
class: "el-icon-question",
},
{
class: "el-icon-info",
}
]
}
},
// 当页面加载时获取数据
created() {
this.fetchData()
},
methods: {
// 调用api层获取数据库中的数据
fetchData() {
console.log('加载列表')
api.findNodes().then(response => {
this.sysMenuList = response.data
console.log(this.sysMenuList)
})
},
// 根据id删除数据
removeDataById(id) {
// debugger
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { // promise
// 点击确定,远程调用ajax
return api.removeById(id)
}).then((response) => {
this.fetchData()
this.$message({
type: 'success',
message: '删除成功!'
})
}).catch(() => {
this.$message.info('取消删除')
})
},
// -------------
add(row){
debugger
this.typeDisabled = false
this.dialogTitle = '添加下级节点'
this.dialogVisible = true
this.sysMenu = Object.assign({}, defaultForm)
this.sysMenu.id = ''
if(row) {
this.sysMenu.parentId = row.id
this.sysMenu.parentName = row.name
//this.sysMenu.component = 'ParentView'
if(row.type === 0) {
this.sysMenu.type = 1
this.typeDisabled = false
this.type0Disabled = false
this.type1Disabled = false
this.type2Disabled = true
} else if(row.type === 1) {
this.sysMenu.type = 2
this.typeDisabled = true
}
} else {
this.dialogTitle = '添加目录节点'
this.sysMenu.type = 0
this.sysMenu.component = 'Layout'
this.sysMenu.parentId = 0
this.typeDisabled = true
}
},
edit(row) {
debugger
this.dialogTitle = '修改节点'
this.dialogVisible = true
this.sysMenu = Object.assign({}, row)
this.typeDisabled = true
},
saveOrUpdate() {
if(this.sysMenu.type === 0 && this.sysMenu.parentId !== 0) {
this.sysMenu.component = 'ParentView'
}
this.$refs.dataForm.validate(valid => {
if (valid) {
this.saveBtnDisabled = true // 防止表单重复提交
if (!this.sysMenu.id) {
this.saveData()
} else {
this.updateData()
}
}
})
},
// 新增
saveData() {
api.save(this.sysMenu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData(this.page)
})
},
// 根据id更新记录
updateData() {
api.updateById(this.sysMenu).then(response => {
this.$message.success(response.message || '操作成功')
this.dialogVisible = false
this.fetchData()
})
}
}
}
</script>
4.4 测试 39
添加一个菜单
修改
删除
5. 给角色分配权限 40
5.1 接口功能实现 40
5.1.1 查询所有菜单和角色分配的菜单 40
用于数据回显
service-oa模块
SysMenuController
//查询所有菜单和角色分配的菜单 40
@ApiOperation("查询所有菜单和角色分配的菜单")
@GetMapping("toAssign/{roleId}")
public Result toAssign(@PathVariable Long roleId) {
List<SysMenu> list = sysMenuService.findMenuByRoleId(roleId);
return Result.ok(list);
}
业务层接口SysMenuService
//查询所有菜单和角色分配的菜单 40
List<SysMenu> findMenuByRoleId(Long roleId);
业务层接口实现类SysMenuServiceImpl 41
//查询所有菜单和角色分配的菜单 41
@Override
public List<SysMenu> findMenuByRoleId(Long roleId) {
//1 查询所有菜单- 添加条件 status=1
LambdaQueryWrapper<SysMenu> wrapperSysMenu = new LambdaQueryWrapper<>();
wrapperSysMenu.eq(SysMenu::getStatus,1);
List<SysMenu> allSysMenuList = baseMapper.selectList(wrapperSysMenu);
//2 根据角色id roleId查询 角色菜单关系表里面 角色id对应所有的菜单id
LambdaQueryWrapper<SysRoleMenu> wrapperSysRoleMenu = new LambdaQueryWrapper<>();
wrapperSysRoleMenu.eq(SysRoleMenu::getRoleId,roleId);
//得到所有的菜单角色关系对象集合
List<SysRoleMenu> sysRoleMenuList = sysRoleMenuService.list(wrapperSysRoleMenu);
//3 遍历菜单角色关系对象集合,得到所有相应的菜单id集合,用于获取对应菜单对象
List<Long> menuIdList = sysRoleMenuList.stream().map(c ->
c.getMenuId()).collect(Collectors.toList());
//3.1 拿着菜单id 和所有菜单集合里面id进行比较,如果相同封装
//注意这个setSelect属性很巧妙,通过他的true和false可以知道菜单是否被选中(即该角色是否有此菜单)
allSysMenuList.stream().forEach(item -> {
if(menuIdList.contains(item.getId())) {
item.setSelect(true);//表示该角色有此菜单
} else {
item.setSelect(false);
}
});
//4 返回规定树形显示格式菜单列表
List<SysMenu> sysMenuList = MenuHelper.buildTree(allSysMenuList);
return sysMenuList;
}
5.1.2 给角色分配菜单 40
数据传输对象AssginMenuVo
package com.atguigu.vo.system;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel(description = "分配菜单")
@Data
public class AssginMenuVo {
@ApiModelProperty(value = "角色id")
private Long roleId;
@ApiModelProperty(value = "菜单id列表")
private List<Long> menuIdList;
}
service-oa模块
SysMenuController
//给角色分配菜单 40
@ApiOperation("角色分配菜单")
@PostMapping("/doAssign")
public Result doAssign(@RequestBody AssginMenuVo assginMenuVo) {
sysMenuService.doAssign(assginMenuVo);
return Result.ok();
}
业务层接口SysMenuService
//给角色分配菜单 40
void doAssign(AssginMenuVo assginMenuVo);
业务层接口实现类SysMenuServiceImpl 41
//给角色分配菜单 41
@Override
public void doAssign(AssginMenuVo assginMenuVo) {
//1 根据角色id 删除菜单角色表 方便分配数据
LambdaQueryWrapper<SysRoleMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysRoleMenu::getRoleId,assginMenuVo.getRoleId());
sysRoleMenuService.remove(wrapper);
//2 从参数里面获取角色新分配菜单id列表,
// 进行遍历,把每个id数据添加菜单角色表
List<Long> menuIdList = assginMenuVo.getMenuIdList();
for(Long menuId:menuIdList) {
if(StringUtils.isEmpty(menuId)) {
continue;
}
SysRoleMenu sysRoleMenu = new SysRoleMenu();
sysRoleMenu.setMenuId(menuId);
sysRoleMenu.setRoleId(assginMenuVo.getRoleId());
sysRoleMenuService.save(sysRoleMenu);
}
}
5.2 前端 41
5.2.1 添加路由
修改 src/router/index.js 文件
index.js
hidden: true代表是个隐藏路由不进行显示
{
path: 'assignAuth',
component: () => import('@/views/system/sysRole/assignAuth'),
meta: {
activeMenu: '/system/sysRole',
title: '角色授权'
},
hidden: true,
}
5.2.2 角色列表添加按钮及方法 42
src/views/system/sysRole/list.vue
list.vue
<el-button type="warning" icon="el-icon-baseball" size="mini" @click="showAssignAuth(scope.row)" title="分配权限"/>
//给角色分配菜单,跳转到分配菜单的页面 41
showAssignAuth(row) {
this.$router.push('/system/assignAuth?id='+row.id+'&roleName='+row.roleName);
}
5.2.3 添加api 42
src/api/system/sysMenu.js
sysMenu.js
/*
查看某个角色的权限列表
*/
toAssign(roleId) {
return request({
url: `${api_name}/toAssign/${roleId}`,
method: 'get'
})
},
/*
给某个角色授权
*/
doAssign(assginMenuVo) {
return request({
url: `${api_name}/doAssign`,
method: "post",
data: assginMenuVo
})
}
5.2.4 实现页面功能 42
创建src/views/system/sysRole/assignAuth.vue
assignAuth.vue
<template>
<div class="app-container">
<div style="padding: 20px 20px 0 20px;">
授权角色:{{ $route.query.roleName }}
</div>
<el-tree
style="margin: 20px 0"
ref="tree"
:data="sysMenuList"
node-key="id"
show-checkbox
default-expand-all
:props="defaultProps"
/>
<div style="padding: 20px 20px;">
<el-button :loading="loading" type="primary" icon="el-icon-check" size="mini" @click="save">保存</el-button>
<el-button @click="$router.push('/system/sysRole')" size="mini" icon="el-icon-refresh-right">返回</el-button>
</div>
</div>
</template>
<script>
import api from '@/api/system/sysMenu'
export default {
name: 'roleAuth',
data() {
return {
loading: false, // 用来标识是否正在保存请求中的标识, 防止重复提交
sysMenuList: [], // 所有
defaultProps: {
children: 'children',
label: 'name'
},
};
},
created() {
this.fetchData()
},
methods: {
/*
初始化
*/
fetchData() {
const roleId = this.$route.query.id
api.toAssign(roleId).then(result => {
const sysMenuList = result.data
this.sysMenuList = sysMenuList
const checkedIds = this.getCheckedIds(sysMenuList)
console.log('getPermissions() checkedIds', checkedIds)
this.$refs.tree.setCheckedKeys(checkedIds)
})
},
/*
得到所有选中的id列表
*/
getCheckedIds (auths, initArr = []) {
return auths.reduce((pre, item) => {
if (item.select && item.children.length === 0) {
pre.push(item.id)
} else if (item.children) {
this.getCheckedIds(item.children, initArr)
}
return pre
}, initArr)
},
/*
保存权限列表
*/
save() {
debugger
//获取到当前子节点
//const checkedNodes = this.$refs.tree.getCheckedNodes()
//获取到当前子节点及父节点
const allCheckedNodes = this.$refs.tree.getCheckedNodes(false, true);
let idList = allCheckedNodes.map(node => node.id);
console.log(idList)
let assginMenuVo = {
roleId: this.$route.query.id,
menuIdList: idList
}
this.loading = true
api.doAssign(assginMenuVo).then(result => {
this.loading = false
this.$message.success(result.$message || '分配权限成功')
this.$router.push('/system/sysRole');
})
}
}
};
</script>
把项目中的警告去掉,这一步做不做都可以运行没影响
5.3 测试 42
浏览器输入 http://localhost:9528/
为管理员分配菜单
再次分配