zhou zhou
14 小时以前 0a1d91e42e6c5af96e1108e9ebcc37e99eb3b22c
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
126
127
128
129
130
131
132
133
import { RoutesAlias } from '../routesAlias.js'
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 routePathMap = /* @__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 (fullPath) {
          if (routePathMap.has(fullPath)) {
            warnings.push(`路由路径重复: "${fullPath}"`)
          } else {
            routePathMap.set(fullPath, String(route.name || fullPath))
          }
        }
        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 }