项目实战第十三记
- 写在前面
- 1. 建立字典表
- 2. 后端DictController
- 3. Menu.vue
- 4. 建立sys_role_menu中间表
- 5.分配菜单接口
- 6. 前端Role.vue改动
- 总结
- 写在最后
写在前面
本篇主要讲解动态分配菜单第二章节
-
菜单页面优化
引入图标 -
角色界面优化
角色自主分配菜单,并保存至数据库
1. 建立字典表
DROP TABLE IF EXISTS `sys_dict`;
CREATE TABLE `sys_dict` (
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名称',
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '内容',
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '类型'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_dict
-- ----------------------------
INSERT INTO `sys_dict` VALUES ('user', 'el-icon-user', 'icon');
INSERT INTO `sys_dict` VALUES ('user-solid', 'el-icon-user-solid', 'icon');
INSERT INTO `sys_dict` VALUES ('s-home', 'el-icon-s-home', 'icon');
INSERT INTO `sys_dict` VALUES ('menu', 'el-icon-menu', 'icon');
INSERT INTO `sys_dict` VALUES ('document', 'el-icon-document', 'icon');
INSERT INTO `sys_dict` VALUES ('map-location', 'el-icon-map-location', 'icon');
2. 后端DictController
package com.ppj.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Arrays;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ppj.common.Result;
import com.ppj.service.IDictService;
import com.ppj.entity.Dict;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author ppj
* @since 2024-05-30
*/
@RestController
@RequestMapping("/dict")
public class DictController {
@Resource
private IDictService dictService;
// 新增或者更新
@PostMapping
public Result save(@RequestBody Dict dict) {
dictService.saveOrUpdate(dict);
return Result.success();
}
@DeleteMapping("/{dictIds}")
public Result delete(@PathVariable Integer[] dictIds) {
dictService.removeByIds(Arrays.asList(dictIds));
return Result.success();
}
@GetMapping
public Result findAll() {
return Result.success(dictService.list());
}
@GetMapping("/page")
public Result findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
QueryWrapper<Dict> queryWrapper = new QueryWrapper<>();
return Result.success(dictService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
}
3. Menu.vue
<template>
<div>
<!-- 设计的查询 -->
<div style="margin: 10px 0">
<el-input
style="width: 200px"
placeholder="请输入名称"
suffix-icon="el-icon-search"
v-model="name"
/>
<el-button type="primary" icon="el-icon-search" class="ml-5" @click="getList"
>搜索</el-button>
<el-button type="warning" icon="el-icon-reset" @click="resetQuery"
>重置</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-button type="warning" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
</div>
<el-table :data="tableData"
row-key="id"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<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="菜单图标"
>
<template v-slot="scope">
<span :class="scope.row.icon" style="font-size: 20px;text-align: center"></span>
</template>
</el-table-column>
<el-table-column prop="description" label="描述"></el-table-column>
<el-table-column label="操作" width="300px">
<template v-slot="scope">
<el-button
type="primary"
@click="handleAdd(scope.row.id)"
v-if="!scope.row.pid && !scope.row.path"
>新增子菜单</el-button>
<el-button type="success" @click="handleUpdate(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
<el-button type="danger" @click="handleDelete(scope.row)">删除 <i class="el-icon-remove-outline"></i></el-button>
</template>
</el-table-column>
</el-table>
<!-- <div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[5, 10, 15]"
: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 :model="form">
<el-form-item label="菜单名称" :label-width="formLabelWidth">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="菜单路径" :label-width="formLabelWidth">
<el-input v-model="form.path" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标" :label-width="formLabelWidth">
<el-select clearable v-model="form.icon" placeholder="请选择" style="width: 100%">
<el-option v-for="item in icons" :key="item.name" :label="item.name" :value="item.value">
<i :class="item.value" /> {{ item.name }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="描述" :label-width="formLabelWidth">
<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 {
name: "",
tableData: [],
total: 0,
pageSize: 5,
pageNum: 1,
dialogFormVisible: false,
formLabelWidth: "80px",
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
form: {
id: '',
name: "",
path: "",
icon: "",
description: "",
},
// 图标集合
icons: []
};
},
//页面一创建成功
created() {
//请求分页查询数据
this.getList();
this.getIcons();
},
methods: {
getList() {
this.request.get("/menu", {
params: {
name: this.name,
},
})
.then((res) => {
if(res.code === "200"){
this.tableData = res.data;
}else{
this.$message.error(res.msg);
}
});
},
resetQuery() {
this.name = "";
this.pageNum = 1;
this.pageSize = 5;
this.getList();
},
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.pageNum = val;
this.getList();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.single = selection.length != 1;
this.multiple = !selection.length;
},
// 新增
handleAdd(id){
this.reset();
this.dialogFormVisible = true;
if(id){ // 新建子菜单时,设置父id
this.form.pid = id;
}
},
save(){
this.request.post("/menu",this.form).then(res => {
if(res.code === "200" || res.code === 200){
this.$message.success("操作成功")
}else {
this.$message.error("操作失败")
}
this.dialogFormVisible = false;
this.getList();
})
},
// 修改
handleUpdate(row){
// 表单置空
this.reset();
// 重新查询数据
const menuId = row.id || this.ids;
this.request.get('/menu/'+menuId).then(response => {
this.form = response.data;
this.dialogFormVisible = true;
});
},
reset(){
this.form.path = undefined;
this.form.name = undefined;
this.form.icon = undefined;
this.form.description = undefined;
},
// 删除
handleDelete(row){
let _this = this;
const menuIds = row.id || this.ids;
this.$confirm('是否确认删除菜单编号为"' + menuIds + '"的数据项?', '删除菜单', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
_this.request.delete("/menu/"+menuIds).then(res=>{
if(res.code === "200" || res.code === 200){
_this.$message.success("删除成功")
}else {
_this.$message.error("删除失败")
}
this.getList();
})
}).catch(() => {
});
},
getIcons(){
this.request.get("/dict").then(res => {
if(res.code === "200"){
this.icons = res.data
}
})
}
},
};
</script>
4. 建立sys_role_menu中间表
// 多对多 建立中间表,角色id和菜单id作为联合主键
CREATE TABLE `sys_role_menu` (
`role_id` int(11) NOT NULL COMMENT '角色id',
`menu_id` int(11) NOT NULL COMMENT '菜单id',
PRIMARY KEY (`role_id`,`menu_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='角色菜单关系表'
5.分配菜单接口
RoleController
package com.ppj.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ppj.service.IRoleMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ppj.common.Result;
import com.ppj.service.IRoleService;
import com.ppj.entity.Role;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 前端控制器
* </p>
*
* @author ppj
* @since 2024-05-29
*/
@RestController
@RequestMapping("/role")
public class RoleController {
@Resource
private IRoleService roleService;
@Autowired
private IRoleMenuService roleMenuService;
// 新增或者更新
@PostMapping
public Result save(@RequestBody Role role) {
roleService.saveOrUpdate(role);
return Result.success();
}
@DeleteMapping("/{roleIds}")
public Result delete(@PathVariable Integer[] roleIds) {
roleService.removeByIds(Arrays.asList(roleIds));
return Result.success();
}
@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 Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(defaultValue = "") String name) {
QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name",name);
return Result.success(roleService.page(new Page<>(pageNum, pageSize), queryWrapper));
}
@PostMapping("/roleMenu/{roleId}")
public Result saveRoleMenu(@PathVariable("roleId") Integer roleId, @RequestBody Integer[] menuIds){
roleMenuService.saveRoleMenu(roleId,menuIds);
return Result.success();
}
@GetMapping("/roleMenu/{roleId}")
public Result getRoleMenu(@PathVariable("roleId") Integer roleId){
return Result.success(roleMenuService.getRoleMenu(roleId));
}
}
RoleMenuServiceImpl
package com.ppj.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ppj.entity.RoleMenu;
import com.ppj.mapper.RoleMenuMapper;
import com.ppj.service.IRoleMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 服务实现类
* </p>
*
* @author ppj
* @since 2024-05-31
*/
@Service
@Transactional
public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements IRoleMenuService {
@Autowired
private RoleMenuMapper roleMenuMapper;
@Override
public void saveRoleMenu(Integer roleId, Integer[] menuIds) {
// 先删除该角色所有的权限
QueryWrapper<RoleMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role_id",roleId);
roleMenuMapper.delete(queryWrapper);
// 在添加新的权限
RoleMenu roleMenu = new RoleMenu();
for (Integer menuId : menuIds) {
roleMenu.setRoleId(roleId);
roleMenu.setMenuId(menuId);
save(roleMenu);
}
}
@Override
public List<Integer> getRoleMenu(Integer roleId) {
QueryWrapper<RoleMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role_id",roleId);
List<RoleMenu> roleMenus = roleMenuMapper.selectList(queryWrapper);
List<Integer> menuIds = roleMenus.stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
return menuIds;
}
}
6. 前端Role.vue改动
<template>
<div>
<!-- 设计的查询 -->
<div style="margin: 10px 0">
<el-input
style="width: 200px"
placeholder="请输入名称"
suffix-icon="el-icon-search"
v-model="name"
/>
<el-button type="primary" icon="el-icon-search" class="ml-5" @click="getList"
>搜索</el-button>
<el-button type="warning" icon="el-icon-reset" @click="resetQuery"
>重置</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-button type="warning" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
<el-button type="danger" :disabled="multiple" @click="handleDelete">删除 <i class="el-icon-remove-outline"></i></el-button>
</div>
<el-table :data="tableData" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="角色ID" width="80"></el-table-column>
<el-table-column prop="roleKey" label="唯一标识"></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="操作">
<template v-slot="scope">
<el-button
type="info"
icon="el-icon-menu"
@click="openMenuAllocDialog(scope.row.id)"
>分配菜单</el-button>
<el-button type="success" @click="handleUpdate(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
<el-button type="danger" @click="handleDelete(scope.row)">删除 <i class="el-icon-remove-outline"></i></el-button>
</template>
</el-table-column>
</el-table>
<div style="padding: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[5, 10, 15]"
: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 :model="form">
<el-form-item label="唯一标识" :label-width="formLabelWidth">
<el-input v-model="form.roleKey" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="角色名称" :label-width="formLabelWidth">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="描述" :label-width="formLabelWidth">
<el-input v-model="form.description" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
<!-- 分配菜单 -->
<el-dialog title="菜单分配" :visible.sync="menuDialogVis" width="30%">
<el-tree
:props="props"
:data="menuData"
show-checkbox
node-key="id"
ref="tree"
:default-expanded-keys="expends"
:default-checked-keys="checks">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span><i :class="data.icon"></i> {{ data.name }}</span>
</span>
</el-tree>
<div slot="footer" class="dialog-footer">
<el-button @click="menuDialogVis = false">取 消</el-button>
<el-button type="primary" @click="saveRoleMenu">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: "Role",
data() {
return {
name: "",
tableData: [],
total: 0,
pageSize: 5,
pageNum: 1,
dialogFormVisible: false,
menuDialogVis: false,
formLabelWidth: "80px",
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
form: {
id: "",
name: "",
description: "",
},
menuData: [],
props: {
label: 'name',
},
expends: [],
checks: [],
roleId: undefined,
};
},
//页面一创建成功
created() {
//请求分页查询数据
this.getList();
},
methods: {
getList() {
this.request
.get("/role/page", {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize,
name: this.name,
},
})
.then((res) => {
if(res.code === "200"){
this.tableData = res.data.records;
this.total = res.data.total;
}else{
this.$message.error(res.msg);
}
});
},
//分配菜单
openMenuAllocDialog(id){
this.menuDialogVis = true;
// 角色id赋值
this.roleId = id;
//请求菜单数据
this.request.get("/menu",{
params: {
name: ""
}
}).then(res => {
this.menuData = res.data;
//展开菜单数据
this.expends = this.menuData.map(v => v.id);
})
// 展开已拥有的菜单
this.request.get("/role/roleMenu/"+id).then(res => {
this.checks = res.data;
})
},
//保存角色下的菜单
saveRoleMenu(){
this.request.post("/role/roleMenu/"+ this.roleId, this.$refs.tree.getCheckedKeys()).then(res => {
if(res.code === "200"){
this.$message.success("保存成功");
this.menuDialogVis = false;
}else {
this.$message.error("保存失败");
}
})
},
// 重置按钮
resetQuery(){
this.username = "";
this.pageNum = 1;
this.pageSize = 5;
this.getList();
},
handleSizeChange(val) {
this.pageSize = val;
},
handleCurrentChange(val) {
this.pageNum = val;
this.getList();
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.id);
this.single = selection.length != 1;
this.multiple = !selection.length;
},
// 新增
handleAdd(){
this.dialogFormVisible = true;
this.form = {};
},
save(){
this.request.post("/role",this.form).then(res => {
if(res.code === "200" || res.code === 200){
this.$message.success("操作成功")
}else {
this.$message.error("操作失败")
}
this.dialogFormVisible = false;
this.getList();
})
},
// 修改
handleUpdate(row){
// 表单置空
this.reset();
// 重新查询数据
const roleId = row.id || this.ids;
this.request.get('/role/'+roleId).then(response => {
this.form = response.data;
this.dialogFormVisible = true;
});
},
reset(){
this.form.roleKey = undefined;
this.form.name = undefined;
this.form.description = undefined;
},
// 删除
handleDelete(row){
let _this = this;
const roleIds = row.id || this.ids;
this.$confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?', '删除角色', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
_this.request.delete("/role/"+roleIds).then(res=>{
if(res.code === "200" || res.code === 200){
_this.$message.success("删除成功")
}else {
_this.$message.error("删除失败")
}
this.getList();
})
}).catch(() => {
});
}
},
};
</script>
总结
- 本篇主要优化菜单页面和角色页面
写在最后
如果此文对您有所帮助,请帅戈靓女们务必不要吝啬你们的Zan,感谢!!不懂的可以在评论区评论,有空会及时回复。
文章会一直更新