import { RoutesAlias } from '../routesAlias' class RouteValidator { constructor() { this.warnedRoutes = new Set() } /** * 验证路由配置 */ validate(routes) { const errors = [] const warnings = [] this.checkDuplicates(routes, errors, warnings) this.checkComponents(routes, errors, warnings) this.checkNestedIndexComponent(routes) return { valid: errors.length === 0, errors, warnings } } /** * 检测重复路由 */ checkDuplicates(routes, errors, warnings, parentPath = '') { const routeNameMap = /* @__PURE__ */ new Map() const componentPathMap = /* @__PURE__ */ new Map() const checkRoutes = (routes2, parentPath2 = '') => { routes2.forEach((route) => { const currentPath = route.path || '' const fullPath = this.resolvePath(parentPath2, currentPath) if (route.name) { const routeName = String(route.name) if (routeNameMap.has(routeName)) { warnings.push(`路由名称重复: "${routeName}" (${fullPath})`) } else { routeNameMap.set(routeName, fullPath) } } if (route.component && typeof route.component === 'string') { const componentPath = route.component if (componentPath !== RoutesAlias.Layout) { const componentKey = `${parentPath2}:${componentPath}` if (componentPathMap.has(componentKey)) { warnings.push(`组件路径重复: "${componentPath}" (${fullPath})`) } else { componentPathMap.set(componentKey, fullPath) } } } if (route.children?.length) { checkRoutes(route.children, fullPath) } }) } checkRoutes(routes, parentPath) } /** * 检测组件配置 */ checkComponents(routes, errors, warnings, parentPath = '') { routes.forEach((route) => { const hasExternalLink = !!route.meta?.link?.trim() const hasChildren = Array.isArray(route.children) && route.children.length > 0 const routePath = route.path || '[未定义路径]' const isIframe = route.meta?.isIframe if (route.component) { if (route.children?.length) { const fullPath = this.resolvePath(parentPath, route.path || '') this.checkComponents(route.children, errors, warnings, fullPath) } return } if (parentPath === '' && !hasExternalLink && !isIframe) { errors.push(`一级菜单(${routePath}) 缺少 component,必须指向 ${RoutesAlias.Layout}`) return } if (!hasExternalLink && !isIframe && !hasChildren) { errors.push(`路由(${routePath}) 缺少 component 配置`) } if (route.children?.length) { const fullPath = this.resolvePath(parentPath, route.path || '') this.checkComponents(route.children, errors, warnings, fullPath) } }) } /** * 检测嵌套菜单的 Layout 组件配置 * 只有一级菜单才能使用 Layout,二级及以下菜单不能使用 */ checkNestedIndexComponent(routes, level = 1) { routes.forEach((route) => { if (level > 1 && route.component === RoutesAlias.Layout) { this.logLayoutError(route, level) } if (route.children?.length) { this.checkNestedIndexComponent(route.children, level + 1) } }) } /** * 输出 Layout 组件配置错误日志 */ logLayoutError(route, level) { const routeName = String(route.name || route.path || '未知路由') const routeKey = `${routeName}_${route.path}` if (this.warnedRoutes.has(routeKey)) return this.warnedRoutes.add(routeKey) const menuTitle = route.meta?.title || routeName const routePath = route.path || '/' console.error( `[路由配置错误] 菜单 "${menuTitle}" (name: ${routeName}, path: ${routePath}) 配置错误 问题: ${level}级菜单不能使用 ${RoutesAlias.Layout} 作为 component 说明: 只有一级菜单才能使用 ${RoutesAlias.Layout},二级及以下菜单应该指向具体的组件路径 当前配置: component: '${RoutesAlias.Layout}' 应该改为: component: '/your/component/path' 或留空 ''(如果是目录菜单)` ) } /** * 路径解析 */ resolvePath(parent, child) { return [parent.replace(/\/$/, ''), child.replace(/^\//, '')].filter(Boolean).join('/') } } export { RouteValidator }