import type { MenuRecord } from '@/types/auth'; import { resolveAntdIconName } from '@/components/Icon/iconMap'; export interface BackendMenuVO { id: number; parentId: number; name: string; path: string; component: string | null; componentName: string | null; icon: string | null; visible: boolean; keepAlive: boolean; type?: number; sort?: number; alwaysShow?: boolean; activeMenu?: string; canTo?: boolean; children?: BackendMenuVO[] | null; } function parseComponent(component: string | null): { componentPath: string; queryParams: Record; isExternal: boolean; } { if (!component) return { componentPath: '', queryParams: {}, isExternal: false }; const isExternal = component.startsWith('http://') || component.startsWith('https://'); if (isExternal) return { componentPath: component, queryParams: {}, isExternal: true }; const [path, queryString] = component.split('?'); const queryParams: Record = {}; if (queryString) { for (const pair of queryString.split('&')) { const eqIndex = pair.indexOf('='); if (eqIndex > 0) { queryParams[pair.slice(0, eqIndex)] = pair.slice(eqIndex + 1); } else if (eqIndex === -1) { queryParams[pair] = ''; } } } return { componentPath: path ?? '', queryParams, isExternal: false }; } const pages = import.meta.glob('@/pages/**/index.{tsx,jsx}'); const validComponentKeys = new Set(Object.keys(pages)); function validateComponentPath(componentPath: string): boolean { if (!componentPath) return false; const tsxKey = `/src/pages/${componentPath}.tsx`; const jsxKey = `/src/pages/${componentPath}.jsx`; return validComponentKeys.has(tsxKey) || validComponentKeys.has(jsxKey); } function resolvePath(parentPath: string, childPath: string): string { if (!childPath) return parentPath; if (childPath.startsWith('/')) return childPath; if (childPath.startsWith('http://') || childPath.startsWith('https://')) return childPath; const base = parentPath.endsWith('/') ? parentPath : parentPath + '/'; return base + childPath; } function normalizeComponentPath(componentPath: string): string { if (!componentPath) return componentPath; let normalized = componentPath.replace(/\.vue$/i, ''); normalized = normalized .split('/') .map((seg) => seg.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()) .join('/'); return normalized; } function convertMenuTree(menus: BackendMenuVO[], parentPath: string, sortBase: number): MenuRecord[] { return menus.map((m, index) => { const fullPath = resolvePath(parentPath, m.path); const hasChildren = Array.isArray(m.children) && m.children.length > 0; const isDir = hasChildren && !m.component; const { componentPath: rawComponentPath, queryParams, isExternal } = parseComponent(m.component); const componentPath = isExternal ? rawComponentPath : normalizeComponentPath(rawComponentPath); const record: MenuRecord = { id: m.id, parentId: m.parentId, name: m.name, path: fullPath, component: componentPath, componentName: m.componentName || undefined, icon: resolveAntdIconName(m.icon), type: m.type ?? (isDir ? 1 : 2), sort: sortBase + index, status: 0, visible: m.visible, keepAlive: m.keepAlive, alwaysShow: m.alwaysShow, activeMenu: m.activeMenu, canTo: m.canTo, queryParams, isExternal, }; if (!isExternal && componentPath && !validateComponentPath(componentPath)) { console.warn(`[menuConverter] Component not found: "${componentPath}" for menu "${m.name}" (id: ${m.id})`); } if (hasChildren) { record.children = convertMenuTree(m.children!, fullPath, 0); } return record; }); } export function convertBackendMenus(backendMenus: unknown[]): MenuRecord[] { return convertMenuTree(backendMenus as BackendMenuVO[], '', 0); }