数据库
新建sys_role角色表 与sys_menu菜单表
CREATE TABLE `sys_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=931254274 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `sys_menu` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
`path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '路径',
`icon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '图标',
`description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
后台代码
controller.java.vm
修改resource/template/controller.java.vm 里的User 为${entity}【之前写死了,导致生成器报错】,同时将返回格式统一为Result。
package ${package.Controller};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
/**
* <p>
* $!{table.comment} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
/**
* <p>
* $!{table.comment} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end
#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end
@Resource
private ${table.serviceName} ${table.entityPath}Service;
// 新增或者更新
@PostMapping
public Result save(@RequestBody ${entity} ${table.entityPath}) {
return Result.success(${table.entityPath}Service.saveOrUpdate(${table.entityPath}));
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
return Result.success(${table.entityPath}Service.removeById(id));
}
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List<Integer> ids) {
return Result.success(${table.entityPath}Service.removeByIds(ids));
}
@GetMapping
public Result findAll() {
return Result.success(${table.entityPath}Service.list());
}
@GetMapping("/{id}")
public Result findOne(@PathVariable Integer id) {
return Result.success(${table.entityPath}Service.getById(id));
}
@GetMapping("/page")
public Result findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
QueryWrapper<${entity}> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
return Result.success(${table.entityPath}Service.page(new Page<>(pageNum, pageSize), queryWrapper));
}
}
#end
CodeGenerator.java
在CodeGenerator里把表名改成sys_role以及sys_menu,分别右键run一下,生成代码
RoleController.java
Page加入名称参数
package com.zj.demo.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zj.demo.common.Result;
import com.zj.demo.entity.Role;
import com.zj.demo.service.IRoleService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* <p>
* 前端控制器
* </p>
*
* @author Joyce
* @since 2023-01-10
*/
@RestController
@RequestMapping("/role")
public class RoleController {
@Resource
private IRoleService roleService;
// 新增或者更新
@PostMapping
public Result save(@RequestBody Role role) {
return Result.success(roleService.saveOrUpdate(role));
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
return Result.success(roleService.removeById(id));
}
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List<Integer> ids) {
return Result.success(roleService.removeByIds(ids));
}
@GetMapping
public Result findAll() {
return Result.success(roleService.list());
}
@GetMapping("/{id}")
public Result findOne(@PathVariable Integer id) {
return Result.success(roleService.getById(id));
}
@GetMapping("/page")
public Result findPage(
@RequestParam(defaultValue ="") String name,
@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
if (!"".equals(name)) {
queryWrapper.like("name", name);
}
queryWrapper.orderByDesc("id");
return Result.success(roleService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
}
MenuController.java
把findall改为获取菜单信息,用以返回给前端树级结构数据
package com.zj.demo.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zj.demo.common.Result;
import com.zj.demo.entity.Menu;
import com.zj.demo.service.IMenuService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 前端控制器
* </p>
*
* @author Joyce
* @since 2023-01-10
*/
@RestController
@RequestMapping("/menu")
public class MenuController {
@Resource
private IMenuService menuService;
// 新增或者更新
@PostMapping
public Result save(@RequestBody Menu menu) {
return Result.success(menuService.saveOrUpdate(menu));
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
return Result.success(menuService.removeById(id));
}
@PostMapping("/del/batch")
public Result deleteBatch(@RequestBody List<Integer> ids) {
return Result.success(menuService.removeByIds(ids));
}
@GetMapping
public Result findAll(@RequestParam(defaultValue ="") String name) {
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
if (!"".equals(name)) {
queryWrapper.like("name", name);
}
//查询所有数据
List<Menu> list = menuService.list(queryWrapper);
//找出pid为null的一级菜单
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);
}
@GetMapping("/{id}")
public Result findOne(@PathVariable Integer id) {
return Result.success(menuService.getById(id));
}
@GetMapping("/page")
public Result findPage(@RequestParam(defaultValue ="") String name,
@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
if (!"".equals(name)) {
queryWrapper.like("name", name);
}
queryWrapper.orderByDesc("id");
return Result.success(menuService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
}
Menu.java
生成的菜单实体类添加pid和children属性,后者不用存在数据库里,仅用于返回前端子菜单List数据
package com.zj.demo.entity;
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 io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
/**
* <p>
*
* </p>
*
* @author Joyce
* @since 2023-01-10
*/
@Getter
@Setter
@TableName("sys_menu")
@ApiModel(value = "Menu对象", description = "")
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("id")
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("路径")
private String path;
@ApiModelProperty("图标")
private String icon;
@ApiModelProperty("描述")
private String description;
@TableField(exist = false)
private List<Menu> children;
@ApiModelProperty("父级id")
private Integer pid;
}
前台代码
新建Role以及Menu页面
Role.vue
新建view/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>
<!-- <el-upload action="http://localhost:9090/role/import" :show-file-list="false" accept="xlsx" :on-success="handleExcelImportSuccess" style="display: inline-block">-->
<!-- <el-button type="primary" class="ml-5">导入 <i class="el-icon-bottom"></i></el-button>-->
<!-- </el-upload>-->
<!-- <el-button type="primary" @click="exp" class="ml-5">导出 <i class="el-icon-top"></i></el-button>-->
</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"></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"
:default-expanded-keys="[1]"
:default-checked-keys="[4]"
@check-change="handleCheckChange">
</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>
</div>
</template>
<script>
export default {
name: "Role",
data() {
return {
tableData: [],
total: 0,
pageNum: 1,
pageSize: 10,
name: "",
description: "",
form: {},
dialogFormVisible: false,
menuDialogVis: false,
multipleSelection: [],
menuData: [],
props:{
label:'name',
}
}
},
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.data.records
this.total = res.data.total
})
},
save() {
this.request.post("/role", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
this.dialogFormVisible = false
this.load()
} else {
this.$message.error("保存失败")
}
})
},
del(id) {
this.request.delete("/role/" + id).then(res => {
if (res.code === '200') {
this.$message.success("删除成功")
this.load()
} else {
this.$message.error("删除失败")
}
})
},
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("批量删除失败")
}
})
},
handleAdd() {
this.dialogFormVisible = true
this.form = {}
},
handleEdit(row) {
this.form = row
this.dialogFormVisible = true
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
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()
},
saveRoleMenu() {
this.request.post("/role/roleMenu/" + 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(roleId) {
this.menuDialogVis = true
//请求菜单数据
this.request.get("/menu", {
params: {
name:"",
}
}).then(res => {
console.log(res)
this.menuData = res.data
})
},
handleCheckChange(data, checked, indeterminate) {
console.log(data, checked, indeterminate);
},
}
}
</script>
<style>
.headerBg {
background: #eee!important;
}
</style>
Menu.vue
新建view/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="handleAdd1">新增 <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'"
row-key="id" default-expand-all @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID"></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="300" 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: 10,
name: "",
path: "",
icon: "",
description: "",
form: {},
dialogFormVisible: false,
multipleSelection: []
}
},
created() {
this.load()
},
methods: {
load() {
this.request.get("/menu", {
params: {
name: this.name,
}
}).then(res => {
this.tableData = res.data
})
},
save() {
this.request.post("/menu", this.form).then(res => {
if (res.code === '200') {
this.$message.success("保存成功")
this.dialogFormVisible = false
this.load()
} else {
this.$message.error("保存失败")
}
})
},
del(id) {
this.request.delete("/menu/" + id).then(res => {
if (res.code === '200') {
this.$message.success("删除成功")
this.load()
} else {
this.$message.error("删除失败")
}
})
},
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("批量删除失败")
}
})
},
handleAdd1() {
this.dialogFormVisible = true
this.form = {}
},
handleAdd(pid) {
this.dialogFormVisible = true
this.form = {}
if(pid)
{
this.form.pid = pid
}
},
handleEdit(row) {
this.form = row
this.dialogFormVisible = true
},
handleSelectionChange(val) {
console.log(val)
this.multipleSelection = val
},
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>
router/index.js
加入Rolel路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from "@/store";
//import store from "../store"
Vue.use(VueRouter)
const routes = [
{
path: '/',
component: () => import('../views/Manage.vue'),
redirect: "/home",
children: [
{ path: 'home', name: '首页', component: () => import('../views/Home.vue')},
{ path: 'user', name: '用户管理', component: () => import('../views/User.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/File.vue')},
]
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
},
{
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
})
// 路由守卫
router.beforeEach((to, from, next) => {
localStorage.setItem("currentPathName", to.name) // 设置当前的路由名称,为了在Header组件中去使用
store.commit("setPath") // 触发store的数据更新
next() // 放行路由
})
export default router
Aside.vue
加入Role
<template>
<el-menu :default-openeds="['1', '3']" 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>
<el-menu-item index="/home">
<template slot="title">
<i class="el-icon-house"></i>
<span slot="title">主页</span>
</template>
</el-menu-item>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-menu"></i>
<span slot="title">系统管理</span>
</template>
<el-menu-item index="/user">
<i class="el-icon-s-custom"></i>
<span slot="title">用户管理</span>
</el-menu-item>
<el-menu-item index="/role">
<i class="el-icon-s-custom"></i>
<span slot="title">角色管理</span>
</el-menu-item>
<el-menu-item index="/role">
<i class="el-icon-s-custom"></i>
<span slot="title">菜单管理</span>
</el-menu-item>
<el-menu-item index="/file">
<i class="el-icon-document"></i>
<span slot="title">文件管理</span>
</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
export default {
name: "Aside",
props: {
isCollapse: Boolean,
logoTextShow: Boolean
}
}
</script>
<style scoped>
</style>
Postman测试过程
测试menu子级菜单获取功能
需要测试的方法
先获取一个token,将token代入请求进行测试