Files
CN_Tool_client/frontend/src/routers/modules/dynamicRouter.ts

188 lines
6.1 KiB
TypeScript
Raw Normal View History

2026-04-13 17:32:58 +08:00
import router from '@/routers'
import { LOGIN_URL } from '@/config'
import { type RouteRecordRaw } from 'vue-router'
import { ElNotification } from 'element-plus'
import { useUserStore } from '@/stores/modules/user'
import { useAuthStore } from '@/stores/modules/auth'
const modules = import.meta.glob('@/views/**/*.vue')
const VIEWS_ALIAS_PREFIX = '@/views'
const VIEWS_SRC_PREFIX = '/src/views'
const COMPONENT_PATH_ALIASES: Record<string, string> = {
// 后端菜单沿用 event-list 模块名时,前端实际页面目录为 eventList。
'/event/event-list': '/event/eventList',
'/event/event-list/index': '/event/eventList/index'
}
const STATIC_ROUTE_NAMES = new Set([
'layout',
'login',
'home',
'tools',
'toolWaveform',
'toolMmsMapping',
'toolAddData',
'toolAddLedger',
'eventList',
'systemMonitor',
'diskMonitor',
'403',
'404',
'500'
])
2026-04-13 17:32:58 +08:00
let isInitializing = false
/**
*
2026-04-13 17:32:58 +08:00
*/
const clearDynamicRoutes = () => {
const routes = router.getRoutes()
routes.forEach(route => {
if (route.name && !STATIC_ROUTE_NAMES.has(String(route.name))) {
router.removeRoute(route.name)
}
})
}
/**
* component views
2026-04-13 17:32:58 +08:00
*/
const normalizeComponentPath = (path: string) => {
let normalizedPath = path.trim().replace(/\\/g, '/')
if (normalizedPath.startsWith(VIEWS_ALIAS_PREFIX)) {
normalizedPath = normalizedPath.slice(VIEWS_ALIAS_PREFIX.length)
} else if (normalizedPath.startsWith(VIEWS_SRC_PREFIX)) {
normalizedPath = normalizedPath.slice(VIEWS_SRC_PREFIX.length)
} else if (normalizedPath.startsWith('src/views')) {
normalizedPath = normalizedPath.slice('src/views'.length)
}
2026-04-13 17:32:58 +08:00
if (!normalizedPath.startsWith('/')) {
normalizedPath = '/' + normalizedPath
}
if (normalizedPath.endsWith('.vue')) {
normalizedPath = normalizedPath.slice(0, -4)
}
2026-04-16 08:11:38 +08:00
if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
normalizedPath = normalizedPath.slice(0, -1)
}
return COMPONENT_PATH_ALIASES[normalizedPath] ?? normalizedPath
}
/**
* component
* index
*/
const resolveComponentModule = (path: string) => {
const normalizedPath = normalizeComponentPath(path)
const viewPath = normalizedPath.endsWith('/index') ? normalizedPath.slice(0, -'/index'.length) : normalizedPath
const candidatePaths = Array.from(
new Set([
`${VIEWS_ALIAS_PREFIX}${normalizedPath}.vue`,
`${VIEWS_ALIAS_PREFIX}${normalizedPath}/index.vue`,
`${VIEWS_ALIAS_PREFIX}${viewPath}/index.vue`,
`${VIEWS_SRC_PREFIX}${normalizedPath}.vue`,
`${VIEWS_SRC_PREFIX}${normalizedPath}/index.vue`,
`${VIEWS_SRC_PREFIX}${viewPath}/index.vue`
])
)
2026-04-13 17:32:58 +08:00
2026-04-16 08:11:38 +08:00
for (const candidatePath of candidatePaths) {
if (candidatePath in modules) {
2026-04-16 08:11:38 +08:00
return {
moduleLoader: modules[candidatePath],
2026-04-16 08:11:38 +08:00
resolvedPath: candidatePath
}
}
}
return {
moduleLoader: undefined,
resolvedPath: candidatePaths
}
2026-04-13 17:32:58 +08:00
}
/**
*
2026-04-13 17:32:58 +08:00
*/
export const initDynamicRouter = async () => {
if (isInitializing) return Promise.reject(new Error('Dynamic router initialization in progress'))
isInitializing = true
const userStore = useUserStore()
const authStore = useAuthStore()
2026-04-16 08:11:38 +08:00
const unresolvedRoutes: Array<{ name?: string; path?: string; component?: string; candidates: string[] }> = []
2026-04-13 17:32:58 +08:00
try {
await authStore.getAuthMenuList()
await authStore.getAuthButtonList()
if (!authStore.authMenuListGet.length) {
ElNotification({
title: '无权限访问',
message: '当前账号无任何菜单权限,请联系系统管理员',
2026-04-13 17:32:58 +08:00
type: 'warning',
duration: 3000
})
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setExp(0)
await router.replace(LOGIN_URL)
return Promise.reject('No permission')
}
clearDynamicRoutes()
for (const item of authStore.flatMenuListGet) {
if (item.children) delete item.children
if (item.name && STATIC_ROUTE_NAMES.has(String(item.name))) {
continue
}
2026-04-13 17:32:58 +08:00
// 动态菜单组件必须先映射成真实页面模块,否则 addRoute 后会直接落到 404。
2026-04-13 17:32:58 +08:00
if (item.component && typeof item.component === 'string') {
2026-04-16 08:11:38 +08:00
const { moduleLoader, resolvedPath } = resolveComponentModule(item.component)
2026-04-13 17:32:58 +08:00
if (moduleLoader) {
item.component = moduleLoader
} else {
2026-04-16 08:11:38 +08:00
unresolvedRoutes.push({
name: item.name,
path: item.path,
component: item.component,
candidates: resolvedPath
})
2026-04-13 17:32:58 +08:00
continue
}
}
if (
typeof item.path === 'string' &&
(typeof item.component === 'function' || typeof item.redirect === 'string')
) {
const routeItem = item as unknown as RouteRecordRaw
2026-04-16 08:11:38 +08:00
if (item.meta?.isFull) {
2026-04-13 17:32:58 +08:00
router.addRoute(routeItem)
} else {
router.addRoute('layout', routeItem)
}
} else {
console.warn('Invalid route item:', item)
}
}
2026-04-16 08:11:38 +08:00
if (unresolvedRoutes.length) {
console.error('[dynamic-router] unresolved route components', unresolvedRoutes)
}
2026-04-13 17:32:58 +08:00
} catch (error) {
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setExp(0)
await router.replace(LOGIN_URL)
return Promise.reject(error)
} finally {
isInitializing = false
}
}