高效菜单管理页面:一键增删改查
<template><div class="app-container"><el-card><!-- 搜索和操作区域 --><div class="filter-container"><el-button type="primary" icon="el-icon-plus" size="mini" @click="handleCreate">新增菜单</el-button><el-button icon="el-icon-refresh" size="mini" @click="getMenuList">刷新</el-button></div><!-- 菜单表格 --><el-tableheight="500":data="menuList"row-key="id"borderstripedefault-expand-all:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"v-loading="loading"><el-table-column prop="menuTitle" label="菜单名称" width="200" fixed="left" /><el-table-column prop="menuName" label="路由名称" width="150" /><el-table-column prop="menuPath" label="路由路径" width="150" /><el-table-column prop="component" label="组件路径" width="200" /><el-table-column prop="perm" label="权限标识" width="150" /><el-table-column prop="redirect" label="重定向路径" width="200" /><el-table-column label="图标" width="100"><template slot-scope="scope"><i :class="scope.row.iconClass" v-if="scope.row.iconClass"></i><span v-else>/</span></template></el-table-column><el-table-column prop="orderNum" label="排序" width="80" align="center" /><el-table-column label="是否可见" width="100" align="center"><template slot-scope="scope"><el-tag :type="scope.row.visible ? 'success' : 'danger'">{{ scope.row.visible ? '是' : '否' }}</el-tag></template></el-table-column><el-table-column label="操作" width="200" fixed="right"><template slot-scope="scope"><el-buttontype="text"icon="el-icon-edit"@click="handleEdit(scope.row)" >编辑</el-button><el-buttontype="text"icon="el-icon-delete"@click="handleDelete(scope.row)" >删除</el-button></template></el-table-column></el-table></el-card><!-- 新增/编辑对话框 --><el-dialog:title="dialogTitle":visible.sync="dialogVisible"width="600px"@close="resetForm"><el-formref="menuForm":model="form":rules="rules"label-width="100px"label-position="right"><el-form-item label="父级菜单" prop="parentId"><el-selectfilterablev-model="form.parentId"placeholder="选择父级菜单"style="width: 100%" ><el-optionv-for="item in menuTreeOptions":key="item.id":value="item.id":label="item.menuTitle"/></el-select></el-form-item><el-form-item label="菜单类型" prop="types"><el-radio-group v-model="form.types"><el-radio :label="0">目录</el-radio><el-radio :label="1">菜单</el-radio><el-radio :label="2">按钮</el-radio></el-radio-group></el-form-item><el-form-item label="菜单名称" prop="menuTitle"><el-input v-model="form.menuTitle" placeholder="请输入菜单名称" /><div class="form-tip">显示在侧边栏的名称</div></el-form-item><el-form-item label="路由名称" prop="menuName"><el-input v-model="form.menuName" placeholder="请输入路由名称" /><div class="form-tip">菜单的唯一标识名</div></el-form-item><el-form-item label="路由路径" prop="menuPath"><el-input v-model="form.menuPath" placeholder="请输入路由路径" /></el-form-item><el-form-itemlabel="组件路径"prop="component"v-if="form.types !== 2"><el-input v-model="form.component" placeholder="请输入组件路径" /><div class="form-tip">例如:@/views/system/user/index</div></el-form-item><el-form-itemlabel="重定向路径"><el-input v-model="form.redirect" placeholder="请输入重定向路径" /><div class="form-tip">例如:/user/index</div></el-form-item><el-form-item label="权限标识"><el-input v-model="form.perm" placeholder="请输入权限标识" /></el-form-item><el-form-item label="图标" prop="iconClass"><el-input v-model="form.iconClass" placeholder="请输入图标类名"><template slot="prepend"><i :class="form.iconClass" v-if="form.iconClass"></i><i class="el-icon-picture" v-else></i></template></el-input><div class="form-tip">Element UI图标类名,如:el-icon-user</div></el-form-item><el-form-item label="排序" prop="orderNum"><el-input-number v-model="form.orderNum" :min="0" :max="10000" /></el-form-item><el-form-item label="是否可见" prop="visible"><el-switch v-model="form.visible" :active-value="1" :inactive-value="0" /></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button type="primary" @click="handleSubmit">确定</el-button><el-button @click="dialogVisible = false">取消</el-button></span></el-dialog></div>
</template><script>
import { reqMenuList, addMenu, updateMenu, deleteMenu } from '@/api/menu'
import { generatorTree, buildTreeOptions } from '@/utils/myUtils.js'export default {name: 'MenuManagement',data() {return {loading: false,menuList: [],menuTreeOptions: [],dialogVisible: false,dialogTitle: '新增菜单',form: {id: null,parentId: 0,types: 1,menuTitle: '',menuName: '',menuPath: '',component: '',perm: '',iconClass: '',orderNum: 0,visible: 1,redirect: '',},rules: {menuTitle: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],menuName: [{ required: true, message: '请输入路由名称', trigger: 'blur' }],menuPath: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],component: [{ required: true, message: '请输入组件路径', trigger: 'blur' }]}}},mounted() {this.getMenuList()},methods: {// 获取菜单列表async getMenuList() {this.loading = truetry {const res = await reqMenuList() // 获取菜单this.menuList = generatorTree(res.data, 0) // 菜单列表this.menuTreeOptions = buildTreeOptions(res.data) } catch (error) {console.error('获取菜单列表失败:', error)} finally {this.loading = false}},// 新增菜单handleCreate() {this.dialogTitle = '新增菜单'this.dialogVisible = true},// 编辑菜单handleEdit(row) {this.dialogTitle = '编辑菜单'this.form = { ...row }this.dialogVisible = true},// 删除菜单handleDelete(row) {this.$confirm(`确定删除菜单 "${row.menuTitle}" 吗?`, '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(async() => {const {code, message} = await deleteMenu(row.id)if (code == 200) {this.$message.success(message)this.getMenuList()} else {this.$message.error(message)}})},// 提交表单handleSubmit() {this.$refs.menuForm.validate(async(valid) => {if (valid) {if (this.form.id) {const { code, message } = await updateMenu(this.form)if (code == 200) {this.$message.success(message)} else {this.$message.error(message)}} else {const { code, message } = await addMenu(this.form)if (code == 200) {this.$message.success(message)} else {this.$message.error(message)}}this.dialogVisible = falsethis.getMenuList()}})},// 重置表单resetForm() {this.$refs.menuForm.resetFields()this.form = {id: null,parentId: 0,types: 1,menuTitle: '',menuName: '',menuPath: '',component: '',iconClass: '',perm: '',orderNum: 0,visible: 1,redirect: '',}}}
}
</script><style scoped>
.filter-container {margin-bottom: 20px;
}.form-tip {font-size: 12px;color: #909399;margin-top: 4px;
}.app-container {padding: 20px;
}
</style>
工具类,生成树形结构及筛选出父级目录
// 筛选出父级目录
export function buildTreeOptions(data) {const options = [{ id: 0, menuTitle: '根目录' }]data.forEach(item => {if (item.types === 0 || item.types == 1) { // 只显示目录类型或菜单类型options.push({ id: item.id, menuTitle: item.menuTitle})}})return options
}// 扁平化变成结构数据
export function generatorTree(list, rootValue) {const arr = []list.forEach(item => {if (item.parentId == rootValue) {// 找到了匹配的节点arr.push(item)// 当前节点的id 和 当前节点的子节点的pid是想等的const children = generatorTree(list, item.id) // 找到的节点的子节点if (children.length) { item.children = children } // 将子节点赋值给当前节点}})return arr
}
前端访问后端接口
// 获取菜单列表
export function reqMenuList() {return request({url: '/menu/getDataList',method: 'get'})
}// 新增菜单
export function addMenu(data) {return request({url: '/menu/addMenu',method: 'post',data})
}// 更新菜单
export function updateMenu(data) {return request({url: '/menu/updateMenu',method: 'put',data})
}// 删除菜单
export function deleteMenu(id) {return request({url: `/menu/deleteMenuById/${id}`,method: 'delete'})
}
后端接口
// 获取菜单和操作权限@GetMapping("/getDataList")public ResultBean selectMenuList() {return ResultBean.success("获取成功", menuService.selectMenuList());}// 添加菜单@PostMapping("/addMenu")@ResponseBodypublic ResultBean addMenu(@RequestBody Menu menu) {menuService.addMenu(menu);return ResultBean.success("添加成功");}// 根据id删除菜单@DeleteMapping("/deleteMenuById/{id}")public ResultBean deleteMenuById(@PathVariable Integer id) {menuService.deleteMenuById(id);return ResultBean.success("删除成功");}// 根据id修改菜单@PutMapping("/updateMenu")public ResultBean updateMenu(@RequestBody Menu menu) {menuService.updateMenu(menu);return ResultBean.success("修改成功");}
业务逻辑
// 添加菜单@Overridepublic void addMenu(Menu menu) {menuMapper.addMenu(menu);}// 根据 id 删除菜单@Transactional@Overridepublic void deleteMenuById(Integer id) {menuMapper.deleteMenuById(id);menuMapper.delPermByMenuId(id);}// 修改菜单@Transactional@Overridepublic void updateMenu(Menu menu) {menuMapper.updateMenu(menu);}
数据库设计
效果如下