zhou zhou
9 小时以前 fec285d150b377d004e47f0973d298b92fe4c711
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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 }