项目实战第十三记
- 写在前面
- 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 {@Resourceprivate IDictService dictService;// 新增或者更新@PostMappingpublic 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();}@GetMappingpublic 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-inputstyle="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-columnprop="name"label="菜单名称"></el-table-column><el-table-columnprop="path"label="菜单路径"></el-table-column><el-table-columnprop="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-buttontype="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){ // 新建子菜单时,设置父idthis.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 {@Resourceprivate IRoleService roleService;@Autowiredprivate IRoleMenuService roleMenuService;// 新增或者更新@PostMappingpublic 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();}@GetMappingpublic 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 {@Autowiredprivate RoleMenuMapper roleMenuMapper;@Overridepublic 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);}}@Overridepublic 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-inputstyle="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-buttontype="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-checkboxnode-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,感谢!!不懂的可以在评论区评论,有空会及时回复。
文章会一直更新