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