Files
front/src/router/menu-data.ts

259 lines
7.7 KiB
TypeScript
Raw Normal View History

2026-03-07 20:11:25 +08:00
import { DEFAULT_LAYOUT } from './routes/base'
import type { AppRouteRecordRaw } from './routes/types'
2026-03-08 22:41:42 +08:00
import type { TreeNodeBase } from '@/utils/tree'
/**
*
*/
export interface ServerMenuItem extends TreeNodeBase {
id: number | string
parent_id: number | string | null
name?: string
title?: string // 菜单标题
title_en?: string // 英文标题
code?: string // 菜单编码
menu_path?: string // 菜单路径,如 '/overview'
component?: string // 组件路径,如 'ops/pages/overview'
icon?: string
locale?: string
sort_key?: number // 排序字段
order?: number
hideInMenu?: boolean
hideChildrenInMenu?: boolean
requiresAuth?: boolean
roles?: string[]
children?: ServerMenuItem[]
[key: string]: any
}
// 预定义的视图模块映射(用于 Vite 动态导入)
const viewModules = import.meta.glob('@/views/**/*.vue')
2026-03-14 18:55:23 +08:00
console.log('viewModules', viewModules)
2026-03-08 22:41:42 +08:00
/**
*
* @param componentPath 'ops/pages/overview' 'ops/pages/overview/index'
* @returns
*/
export function loadViewComponent(componentPath: string) {
// 将路径转换为完整的视图路径
2026-03-14 18:55:23 +08:00
// 如果路径不以 /index 结尾且不以 .vue 结尾,自动补全
let normalizedPath = componentPath
if (!normalizedPath.endsWith('/index') && !normalizedPath.endsWith('.vue')) {
normalizedPath = `${normalizedPath}/index`
2026-03-08 22:41:42 +08:00
}
// 构建完整的文件路径
2026-03-14 18:55:23 +08:00
const filePath = `/src/views/${normalizedPath}.vue`
2026-03-08 22:41:42 +08:00
// 从预加载的模块中查找
const modulePath = Object.keys(viewModules).find((path) => path.endsWith(filePath) || path === filePath)
2026-03-15 23:16:00 +08:00
2026-03-08 22:41:42 +08:00
if (modulePath && viewModules[modulePath]) {
return viewModules[modulePath]
}
2026-03-14 18:55:23 +08:00
// 如果找不到,尝试不带 /index 的路径
const directFilePath = `/src/views/${componentPath}.vue`
const directModulePath = Object.keys(viewModules).find((path) => path.endsWith(directFilePath) || path === directFilePath)
if (directModulePath && viewModules[directModulePath]) {
return viewModules[directModulePath]
}
// 如果都找不到,返回一个默认组件或抛出错误
console.warn(`View component not found: ${filePath} or ${directFilePath}`)
2026-03-08 22:41:42 +08:00
return () => import('@/views/redirect/index.vue')
}
/**
*
* @param menuItems
* @returns
*/
export function transformMenuToRoutes(menuItems: ServerMenuItem[]): AppRouteRecordRaw[] {
const routes: AppRouteRecordRaw[] = []
for (const item of menuItems) {
const route: AppRouteRecordRaw = {
path: item.menu_path || '',
name: item.title || item.name || `menu_${item.id}`,
meta: {
2026-03-14 18:55:23 +08:00
// ...item,
2026-03-08 22:41:42 +08:00
locale: item.locale || item.title,
requiresAuth: item.requiresAuth !== false,
2026-03-12 22:38:13 +08:00
icon: item.icon || item?.menu_icon,
2026-03-08 22:41:42 +08:00
order: item.sort_key ?? item.order,
hideInMenu: item.hideInMenu,
hideChildrenInMenu: item.hideChildrenInMenu,
roles: item.roles,
2026-03-12 22:38:13 +08:00
isNewTab: item.is_new_tab,
2026-03-08 22:41:42 +08:00
},
component: DEFAULT_LAYOUT,
}
// 处理子菜单
if (item.children && item.children.length > 0) {
// 传递父级的 component 和 path 给子路由处理函数
route.children = transformChildRoutes(item.children, item.component, item.menu_path)
} else if (item.component) {
// 一级菜单没有 children 但有 component创建一个空路径的子路由
const routeName = route.name
route.children = [
{
path: item.menu_path || '',
name: typeof routeName === 'string' ? `${routeName}Index` : `menu_${item.id}_index`,
component: loadViewComponent(item.component),
meta: {
locale: item.locale || item.title,
requiresAuth: item.requiresAuth !== false,
2026-03-14 18:55:23 +08:00
isNewTab: item.is_new_tab,
2026-03-08 22:41:42 +08:00
},
},
]
}
routes.push(route)
}
return routes
}
/**
* /
* @param path
* @returns
*/
function toRelativePath(path: string): string {
if (!path) return ''
// 去掉开头的 /
return path.startsWith('/') ? path.slice(1) : path
}
/**
*
* '/dashboard' '/dashboard/workplace' -> 'workplace'
* @param childPath
* @param parentPath
* @returns
*/
function extractRelativePath(childPath: string, parentPath: string): string {
if (!childPath) return ''
// 如果子路径以父路径开头,提取相对部分
if (parentPath && childPath.startsWith(parentPath)) {
let relativePath = childPath.slice(parentPath.length)
// 去掉开头的 /
if (relativePath.startsWith('/')) {
relativePath = relativePath.slice(1)
}
return relativePath
}
// 否则转换为相对路径
return toRelativePath(childPath)
}
/**
*
* @param children
* @param parentComponent component component
* @param parentPath
* @returns
*/
function transformChildRoutes(
children: ServerMenuItem[],
parentComponent?: string,
parentPath?: string
): AppRouteRecordRaw[] {
return children.map((child) => {
// 优先使用子菜单自己的 component否则继承父级的 component
const componentPath = child.component || parentComponent
// 计算子路由的相对路径
const childFullPath = child.menu_path || child.path || ''
const relativePath = extractRelativePath(childFullPath, parentPath || '')
const route: AppRouteRecordRaw = {
path: relativePath,
name: child.title || child.name || `menu_${child.id}`,
meta: {
2026-03-14 18:55:23 +08:00
...child,
2026-03-08 22:41:42 +08:00
locale: child.locale || child.title,
requiresAuth: child.requiresAuth !== false,
roles: child.roles,
},
component: componentPath
? loadViewComponent(componentPath)
: () => import('@/views/redirect/index.vue'),
}
// 递归处理子菜单的子菜单
if (child.children && child.children.length > 0) {
route.children = transformChildRoutes(
child.children,
child.component || parentComponent,
childFullPath // 传递当前子菜单的完整路径作为下一层的父路径
)
}
return route
})
}
2026-03-07 20:11:25 +08:00
// 本地菜单数据 - 接口未准备好时使用
export const localMenuData: AppRouteRecordRaw[] = [{
path: '/dashboard',
name: 'dashboard',
component: DEFAULT_LAYOUT,
meta: {
locale: '仪表盘',
requiresAuth: true,
icon: 'icon-dashboard',
order: 0,
},
children: [
{
path: 'workplace',
name: 'Workplace',
component: () => import('@/views/dashboard/workplace/index.vue'),
meta: {
locale: 'menu.dashboard.workplace',
requiresAuth: true,
},
},
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/dashboard/monitor/index.vue'),
meta: {
locale: 'menu.dashboard.monitor',
requiresAuth: true,
},
},
],
},
{
path: '/overview',
name: 'Overview',
component: DEFAULT_LAYOUT,
meta: {
locale: '系统概况',
requiresAuth: true,
icon: 'icon-home',
order: 1,
},
children: [
{
path: '',
name: 'OverviewIndex',
2026-03-08 22:41:42 +08:00
component: () => import('@/views/ops/pages/overview/index.vue'),
2026-03-07 20:11:25 +08:00
meta: {
locale: '系统概况',
requiresAuth: true,
},
},
],
},
]
export default localMenuData