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 }
|