zhou zhou
3 天以前 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
134
135
136
import { ComponentLoader } from './ComponentLoader.js'
import { RouteValidator } from './RouteValidator.js'
import { RouteTransformer } from './RouteTransformer.js'
const DEFAULT_WARMUP_LIMIT = 12
const HOME_COMPONENT_PATH = '/dashboard/console'
class RouteRegistry {
  constructor(router, options = {}) {
    this.router = router
    this.componentLoader = options.componentLoader || new ComponentLoader()
    this.validator = new RouteValidator()
    this.transformer = new RouteTransformer(this.componentLoader)
    this.removeRouteFns = []
    this.registered = false
  }
  /**
   * 注册动态路由
   */
  register(menuList) {
    if (this.registered) {
      console.warn('[RouteRegistry] 路由已注册,跳过重复注册')
      return
    }
    const validationResult = this.validator.validate(menuList)
    if (!validationResult.valid) {
      throw new Error(`路由配置验证失败: ${validationResult.errors.join(', ')}`)
    }
    const removeRouteFns = []
    const existingRoutePaths = new Set(
      (this.router.getRoutes?.() || []).map((route) => route?.path).filter(Boolean)
    )
    menuList.forEach((route) => {
      if (route?.meta?.staticRoute) {
        return
      }
 
      const routeConfig = this.transformer.transform(route)
      const routePath = routeConfig?.path
 
      if (routePath && existingRoutePaths.has(routePath)) {
        console.warn(`[RouteRegistry] 检测到重复路径,已跳过动态注册: ${routePath}`)
        return
      }
 
      if (route.name && this.router.hasRoute(route.name)) {
        console.warn(`[RouteRegistry] 检测到重复名称,已跳过动态注册: ${route.name}`)
        return
      }
 
      const removeRouteFn = this.router.addRoute(routeConfig)
      removeRouteFns.push(removeRouteFn)
      if (routePath) {
        existingRoutePaths.add(routePath)
      }
    })
    this.removeRouteFns = removeRouteFns
    this.registered = true
  }
  /**
   * 移除所有动态路由
   */
  unregister() {
    this.removeRouteFns.forEach((fn) => fn())
    this.removeRouteFns = []
    this.registered = false
  }
  /**
   * 检查是否已注册
   */
  isRegistered() {
    return this.registered
  }
  /**
   * 获取移除函数列表(用于 store 管理)
   */
  getRemoveRouteFns() {
    return this.removeRouteFns
  }
  /**
   * 标记为已注册(用于错误处理场景,避免重复请求)
   */
  markAsRegistered() {
    this.registered = true
  }
  /**
   * 空闲时预热高频页面,降低登录后首次切页的冷加载成本。
   */
  warm(menuList, options = {}) {
    const limit = Number.isFinite(options.limit) ? options.limit : DEFAULT_WARMUP_LIMIT
    const paths = collectWarmupPaths(menuList, limit)
    if (paths.length === 0) {
      return
    }
    const schedule = globalThis.requestIdleCallback
      ? (task) => globalThis.requestIdleCallback(task, { timeout: 1200 })
      : (task) => setTimeout(task, 80)
    schedule(() => {
      void warmSequentially(paths, this.componentLoader)
    })
  }
}
function collectWarmupPaths(menuList, limit) {
  const paths = []
  const visited = new Set()
  const walk = (items) => {
    if (!Array.isArray(items)) {
      return
    }
    for (const item of items) {
      if (paths.length >= limit) {
        return
      }
      const componentPath = typeof item?.component === 'string' ? item.component.trim() : ''
      if (
        componentPath &&
        componentPath !== '/' &&
        componentPath !== HOME_COMPONENT_PATH &&
        !visited.has(componentPath) &&
        !item?.meta?.isFirstLevel
      ) {
        visited.add(componentPath)
        paths.push(componentPath)
      }
      if (Array.isArray(item?.children) && item.children.length > 0) {
        walk(item.children)
      }
    }
  }
  walk(menuList)
  return paths
}
async function warmSequentially(paths, componentLoader) {
  for (const componentPath of paths) {
    await componentLoader.warm(componentPath)
  }
}
export { RouteRegistry }