一、父子菜单实现
新建数据库表
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
})
},