菜单管理API模块负责处理系统菜单的增删改查、菜单层级管理、动态路由生成等功能。
文件位置: src/api/menu.js
功能: 获取菜单列表数据,支持树形结构
请求方式: GET
接口地址: /menu/list
参数:
{
page: number, // 页码(可选,不传则返回全部)
pageSize: number, // 每页数量
keyword: string, // 搜索关键词(可选)
parentId: number, // 父菜单ID(可选)
status: number, // 状态筛选(可选)
type: string // 菜单类型筛选(可选)'menu'|'button'
}返回数据:
{
status: boolean,
msg: string,
data: {
list: [
{
id: number, // 菜单ID
name: string, // 菜单名称
title: string, // 菜单标题
path: string, // 路由路径
component: string, // 组件路径
redirect: string, // 重定向路径
icon: string, // 菜单图标
parentId: number, // 父菜单ID
parentName: string, // 父菜单名称
level: number, // 菜单层级
sort: number, // 排序权重
type: string, // 类型 'menu'-菜单 'button'-按钮
status: number, // 状态 1-启用 0-禁用
visible: number, // 是否显示 1-显示 0-隐藏
cache: number, // 是否缓存 1-缓存 0-不缓存
breadcrumb: number, // 是否显示面包屑 1-显示 0-隐藏
affix: number, // 是否固定标签 1-固定 0-不固定
permission: string, // 权限标识
roles: string, // 角色权限(JSON字符串)
meta: { // 路由元信息
title: string,
icon: string,
roles: Array,
noCache: boolean,
breadcrumb: boolean,
affix: boolean
},
createTime: string, // 创建时间
updateTime: string, // 更新时间
children: Array // 子菜单列表
}
],
total: number,
tree: Array // 树形结构数据
}
}使用示例:
import { getMenuList } from '@/api/menu'
const loadMenus = async () => {
try {
const params = {
status: 1,
type: 'menu'
}
const result = await getMenuList(params)
if (result.status) {
console.log('菜单列表:', result.data.list)
console.log('菜单树:', result.data.tree)
}
} catch (error) {
console.error('获取菜单列表失败:', error)
}
}功能: 根据菜单ID获取菜单详细信息
请求方式: GET
接口地址: /menu/detail/{id}
参数:
{
id: number // 菜单ID
}返回数据:
{
status: boolean,
data: {
// 菜单详细信息,格式同列表接口中的单条记录
}
}功能: 创建新菜单
请求方式: POST
接口地址: /menu/create
参数:
{
name: string, // 菜单名称(必填)
title: string, // 菜单标题(必填)
path: string, // 路由路径(必填)
component: string, // 组件路径(可选)
redirect: string, // 重定向路径(可选)
icon: string, // 菜单图标(可选)
parentId: number, // 父菜单ID(可选)
sort: number, // 排序权重(可选)
type: string, // 类型(必填)'menu'|'button'
status: number, // 状态(可选,默认1)
visible: number, // 是否显示(可选,默认1)
cache: number, // 是否缓存(可选,默认0)
breadcrumb: number, // 是否显示面包屑(可选,默认1)
affix: number, // 是否固定标签(可选,默认0)
permission: string, // 权限标识(可选)
roles: Array // 角色权限(可选)
}返回数据:
{
status: boolean,
msg: string,
data: {
id: number // 新创建的菜单ID
}
}功能: 更新菜单信息
请求方式: PUT
接口地址: /menu/update/{id}
参数:
{
id: number, // 菜单ID(必填)
name: string, // 菜单名称
title: string, // 菜单标题
path: string, // 路由路径
component: string, // 组件路径
redirect: string, // 重定向路径
icon: string, // 菜单图标
parentId: number, // 父菜单ID
sort: number, // 排序权重
type: string, // 类型
status: number, // 状态
visible: number, // 是否显示
cache: number, // 是否缓存
breadcrumb: number, // 是否显示面包屑
affix: number, // 是否固定标签
permission: string, // 权限标识
roles: Array // 角色权限
}功能: 删除菜单(软删除)
请求方式: DELETE
接口地址: /menu/delete/{id}
参数:
{
id: number // 菜单ID
}返回数据:
{
status: boolean,
msg: string
}功能: 获取菜单的树形结构,用于下拉选择
请求方式: GET
接口地址: /menu/tree
参数:
{
type: string, // 菜单类型筛选(可选)
status: number, // 状态筛选(可选)
excludeId: number // 排除的菜单ID(可选)
}返回数据:
{
status: boolean,
data: [
{
id: number,
name: string,
title: string,
level: number,
children: [
// 子菜单数据
]
}
]
}功能: 移动菜单到新的父菜单下
请求方式: PUT
接口地址: /menu/move
参数:
{
id: number, // 要移动的菜单ID
targetParentId: number, // 目标父菜单ID
targetSort: number // 目标排序位置
}功能: 批量启用或禁用菜单
请求方式: PUT
接口地址: /menu/batch-status
参数:
{
ids: Array, // 菜单ID数组
status: number // 目标状态 1-启用 0-禁用
}功能: 根据用户权限获取可访问的菜单
请求方式: GET
接口地址: /menu/user-menus
参数: 无(从token中获取用户信息)
返回数据:
{
status: boolean,
data: [
{
id: number,
name: string,
title: string,
path: string,
component: string,
redirect: string,
icon: string,
meta: {
title: string,
icon: string,
roles: Array,
noCache: boolean,
breadcrumb: boolean,
affix: boolean
},
children: Array
}
]
}功能: 刷新菜单缓存,重新生成路由
请求方式: POST
接口地址: /menu/refresh-cache
参数: 无
返回数据:
{
status: boolean,
msg: string
}{
id: number, // 菜单ID
name: string, // 菜单名称(路由name)
title: string, // 菜单标题(显示名称)
path: string, // 路由路径
component: string, // 组件路径
redirect: string, // 重定向路径
icon: string, // 菜单图标
parentId: number, // 父菜单ID
parentName: string, // 父菜单名称
level: number, // 菜单层级(1为顶级)
sort: number, // 排序权重
type: string, // 类型 'menu'-菜单 'button'-按钮
status: number, // 状态 1-启用 0-禁用
visible: number, // 是否显示 1-显示 0-隐藏
cache: number, // 是否缓存 1-缓存 0-不缓存
breadcrumb: number, // 是否显示面包屑 1-显示 0-隐藏
affix: number, // 是否固定标签 1-固定 0-不固定
permission: string, // 权限标识
roles: string, // 角色权限(JSON字符串)
meta: { // 路由元信息
title: string,
icon: string,
roles: Array,
noCache: boolean,
breadcrumb: boolean,
affix: boolean
},
createTime: string, // 创建时间
updateTime: string, // 更新时间
children: Array // 子菜单列表
}{
title: string, // 菜单标题
icon: string, // 菜单图标
roles: Array, // 允许访问的角色
noCache: boolean, // 是否不缓存
breadcrumb: boolean, // 是否显示面包屑
affix: boolean, // 是否固定在标签栏
hidden: boolean, // 是否隐藏菜单
alwaysShow: boolean, // 是否总是显示根菜单
activeMenu: string // 高亮的菜单路径
}- 支持多级菜单结构
- 建议最大层级不超过3级
- 删除父菜单时需要先处理子菜单
- 菜单路径必须以
/开头 - 子菜单路径可以是相对路径
- 组件路径相对于
src/views目录
- 菜单权限基于角色控制
- 支持按钮级别的权限控制
- 无权限的菜单不会显示
- 页面组件可以设置是否缓存
- 缓存基于路由name实现
- 动态路由支持缓存
import { createMenu } from '@/api/menu'
const addMenu = async () => {
const menuData = {
name: 'UserManagement',
title: '用户管理',
path: '/user',
component: 'user/index',
icon: 'user',
parentId: 0,
sort: 1,
type: 'menu',
status: 1,
visible: 1,
permission: 'user:view',
roles: ['admin', 'manager']
}
try {
const result = await createMenu(menuData)
if (result.status) {
ElMessage.success('菜单创建成功')
// 刷新菜单列表
loadMenus()
}
} catch (error) {
ElMessage.error('菜单创建失败')
}
}import { getUserMenus } from '@/api/menu'
import { generateRoutes } from '@/utils/dynamicRoutes'
const setupUserMenus = async () => {
try {
const result = await getUserMenus()
if (result.status) {
const menus = result.data
// 生成动态路由
const routes = generateRoutes(menus)
// 添加到路由器
routes.forEach(route => {
router.addRoute(route)
})
// 保存到状态管理
userStore.setMenus(menus)
}
} catch (error) {
console.error('获取用户菜单失败:', error)
}
}<template>
<el-tree
:data="menuTree"
:props="treeProps"
node-key="id"
:expand-on-click-node="false"
:default-expand-all="true"
>
<template #default="{ node, data }">
<div class="menu-tree-node">
<el-icon v-if="data.icon">
<component :is="data.icon" />
</el-icon>
<span>{{ data.title }}</span>
<el-tag v-if="data.type === 'button'" size="small" type="info">
按钮
</el-tag>
<el-tag v-if="data.status === 0" size="small" type="danger">
禁用
</el-tag>
</div>
</template>
</el-tree>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getMenuList } from '@/api/menu'
const menuTree = ref([])
const treeProps = {
children: 'children',
label: 'title'
}
const loadMenuTree = async () => {
try {
const result = await getMenuList()
if (result.status) {
menuTree.value = result.data.tree
}
} catch (error) {
console.error('加载菜单树失败:', error)
}
}
onMounted(() => {
loadMenuTree()
})
</script>30001: 菜单名称已存在30002: 菜单路径已存在30003: 父菜单不存在30004: 不能将菜单移动到自己的子菜单下30005: 菜单下还有子菜单,不能删除
try {
const result = await createMenu(menuData)
if (!result.status) {
switch (result.code) {
case 30001:
ElMessage.error('菜单名称已存在')
break
case 30002:
ElMessage.error('菜单路径已存在')
break
default:
ElMessage.error(result.msg || '操作失败')
}
return
}
// 处理成功逻辑
} catch (error) {
console.error('菜单操作失败:', error)
ElMessage.error('网络请求失败')
}// src/mock/api.js
export const mockMenuList = {
url: '/menu/list',
method: 'get',
response: {
status: true,
data: {
list: [
{
id: 1,
name: 'Dashboard',
title: '首页',
path: '/dashboard',
component: 'dashboard/index',
icon: 'dashboard',
parentId: 0,
level: 1,
sort: 1,
type: 'menu',
status: 1,
visible: 1,
meta: {
title: '首页',
icon: 'dashboard',
affix: true
},
children: []
}
],
tree: [
// 树形结构数据
]
}
}
}- 路由同步: 菜单变更后需要刷新路由缓存
- 权限验证: 菜单权限要与后端接口权限保持一致
- 组件懒加载: 动态路由组件建议使用懒加载
- 缓存管理: 合理设置页面缓存策略
- 图标管理: 统一管理菜单图标资源
最后更新时间:2025-09-19