<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-table
height="500"
:data="menuList"
row-key="id"
border
stripe
default-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-button
type="text"
icon="el-icon-edit"
@click="handleEdit(scope.row)" >编辑</el-button>
<el-button
type="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-form
ref="menuForm"
:model="form"
:rules="rules"
label-width="100px"
label-position="right">
<el-form-item label="父级菜单" prop="parentId">
<el-select
filterable
v-model="form.parentId"
placeholder="选择父级菜单"
style="width: 100%" >
<el-option
v-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-item
label="组件路径"
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-item
label="重定向路径">
<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 = true
try {
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 = false
this.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")
@ResponseBody
public 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("修改成功");
}
业务逻辑
// 添加菜单
@Override
public void addMenu(Menu menu) {
menuMapper.addMenu(menu);
}
// 根据 id 删除菜单
@Transactional
@Override
public void deleteMenuById(Integer id) {
menuMapper.deleteMenuById(id);
menuMapper.delPermByMenuId(id);
}
// 修改菜单
@Transactional
@Override
public void updateMenu(Menu menu) {
menuMapper.updateMenu(menu);
}
数据库设计
效果如下