Files
CN_Tool_client/frontend/src/routers/modules/dynamicRouter.ts
yexb 7c8e7d1b7e refactor(steady-checksquare): 更新稳态校验功能的数据结构和接口规范
- 移除 lineId 字段,统一使用 lineIds 数组字段
- 更新创建任务请求体构建逻辑,不再包含 lineId 属性
- 修改任务详情和检测项明细的接口契约验证
- 更新稳态校验 API 文档说明,移除对 lineId 的支持
- 重构稳态校验表单项验证规则
- 移除磁盘监控作业详情抽屉组件
- 移除磁盘监控作业表格组件
- 更新路由配置以支持新的系统监控路径
- 添加测试报告相关字典代码常量
- 更新 ICD 路径表单和结果面板功能
2026-07-01 13:18:17 +08:00

262 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 ROUTER_PERF_DEBUG = import.meta.env.DEV
const perfNow = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())
const logRouterPerf = (label: string, start: number, extra?: Record<string, unknown>) => {
if (!ROUTER_PERF_DEBUG) return
const duration = Math.round((perfNow() - start) * 100) / 100
console.info(`[router-perf] ${label}`, { duration, ...extra })
}
const COMPONENT_PATH_ALIASES: Record<string, string> = {
// 后端菜单沿用 event-list 模块名时,前端实际页面目录为 eventList。
'/event/event-list': '/event/eventList',
'/event/event-list/index': '/event/eventList/index',
// 后端菜单可能使用短横线模块名,前端页面目录统一为 steadyDataView。
'/steady/steady-data-view': '/steady/steadyDataView',
'/steady/steady-data-view/index': '/steady/steadyDataView/index',
'/steady/steady-trend': '/steady/steadyTrend',
'/steady/steady-trend/index': '/steady/steadyTrend/index',
'/steady/check-square': '/steady/checksquare',
'/steady/check-square/index': '/steady/checksquare/index',
// 数据库监控菜单统一落到 system-ops/dbms 页面,兼容后端菜单常见 component 写法。
'/system-monitor/dbms': '/system-ops/dbms',
'/system-monitor/dbms/index': '/system-ops/dbms/index',
'/system-monitor/databaseMonitor': '/system-ops/dbms',
'/system-monitor/databaseMonitor/index': '/system-ops/dbms/index',
'/system-monitor/database-monitor': '/system-ops/dbms',
'/system-monitor/database-monitor/index': '/system-ops/dbms/index',
'/systemMonitor/dbms': '/system-ops/dbms',
'/systemMonitor/dbms/index': '/system-ops/dbms/index',
'/systemMonitor/databaseMonitor': '/system-ops/dbms',
'/systemMonitor/databaseMonitor/index': '/system-ops/dbms/index',
'/systemMonitor/database-monitor': '/system-ops/dbms',
'/systemMonitor/database-monitor/index': '/system-ops/dbms/index',
'/system-ops/database-monitor': '/system-ops/dbms',
'/system-ops/database-monitor/index': '/system-ops/dbms/index',
// 报告菜单后端使用 aiReport前端目录沿用既有 aireport 命名。
'/aiReport/reportTask': '/aireport/testReport',
'/aiReport/reportTask/index': '/aireport/testReport/index',
'/aiReport/testReport': '/aireport/testReport',
'/aiReport/testReport/index': '/aireport/testReport/index',
'/aiReport/reportModel': '/aireport/reportModel',
'/aiReport/reportModel/index': '/aireport/reportModel/index',
'/aiReport/clientUnit': '/aireport/clientUnit',
'/aiReport/clientUnit/index': '/aireport/clientUnit/index',
'/aiReport/testDevice': '/aireport/testDevice',
'/aiReport/testDevice/index': '/aireport/testDevice/index',
'/aiReport/reportApproval': '/aireport/reportApproval',
'/aiReport/reportApproval/index': '/aireport/reportApproval/index'
}
const STATIC_ROUTE_NAMES = new Set([
'layout',
'login',
'home',
'tools',
'toolWaveform',
'toolMmsMapping',
'toolParsePqdif',
'deviceTypes',
'toolAddData',
'toolAddLedger',
'eventList',
'steadyDataView',
'steadyTrend',
'checksquare',
'system-monitor',
'systemMonitor',
'diskMonitor',
'systemOpsDbms',
'403',
'404',
'500'
])
let isInitializing = false
/**
* 清除已有的动态路由,避免重复注入。
*/
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 目录。
*/
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)
}
if (!normalizedPath.startsWith('/')) {
normalizedPath = '/' + normalizedPath
}
if (normalizedPath.endsWith('.vue')) {
normalizedPath = normalizedPath.slice(0, -4)
}
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`
])
)
for (const candidatePath of candidatePaths) {
if (candidatePath in modules) {
return {
moduleLoader: modules[candidatePath],
resolvedPath: candidatePath
}
}
}
return {
moduleLoader: undefined,
resolvedPath: candidatePaths
}
}
/**
* 初始化动态路由。
*/
export const initDynamicRouter = async () => {
if (isInitializing) return Promise.reject(new Error('Dynamic router initialization in progress'))
isInitializing = true
const initStart = perfNow()
const userStore = useUserStore()
const authStore = useAuthStore()
const unresolvedRoutes: Array<{ name?: string; path?: string; component?: string; candidates: string[] }> = []
try {
const menuStart = perfNow()
await authStore.getAuthMenuList()
logRouterPerf('dynamic-router-load-menus-api', menuStart)
const flattenMenuStart = perfNow()
const flatMenuList = authStore.flatMenuListGet
logRouterPerf('dynamic-router-flatten-menus', flattenMenuStart, {
menuCount: flatMenuList.length
})
const buttonStart = perfNow()
await authStore.getAuthButtonList()
logRouterPerf('dynamic-router-load-buttons', buttonStart)
if (!authStore.authMenuListGet.length) {
ElNotification({
title: '无权限访问',
message: '当前账号无任何菜单权限,请联系系统管理员',
type: 'warning',
duration: 3000
})
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setExp(0)
await router.replace(LOGIN_URL)
return Promise.reject('No permission')
}
const clearStart = perfNow()
clearDynamicRoutes()
logRouterPerf('dynamic-router-clear-routes', clearStart)
const addRoutesStart = perfNow()
let addedRouteCount = 0
for (const item of flatMenuList) {
if (item.children) delete item.children
if (item.name && STATIC_ROUTE_NAMES.has(String(item.name))) {
continue
}
// 动态菜单组件必须先映射成真实页面模块,否则 addRoute 后会直接落到 404。
if (item.component && typeof item.component === 'string') {
const { moduleLoader, resolvedPath } = resolveComponentModule(item.component)
if (moduleLoader) {
item.component = moduleLoader
} else {
unresolvedRoutes.push({
name: item.name,
path: item.path,
component: item.component,
candidates: resolvedPath
})
continue
}
}
if (
typeof item.path === 'string' &&
(typeof item.component === 'function' || typeof item.redirect === 'string')
) {
const routeItem = item as unknown as RouteRecordRaw
if (item.meta?.isFull) {
router.addRoute(routeItem)
} else {
router.addRoute('layout', routeItem)
}
addedRouteCount += 1
} else {
console.warn('Invalid route item:', item)
}
}
logRouterPerf('dynamic-router-add-routes', addRoutesStart, {
menuCount: flatMenuList.length,
addedRouteCount,
unresolvedRouteCount: unresolvedRoutes.length
})
if (unresolvedRoutes.length) {
console.error('[dynamic-router] unresolved route components', unresolvedRoutes)
}
} catch (error) {
userStore.setAccessToken('')
userStore.setRefreshToken('')
userStore.setExp(0)
await router.replace(LOGIN_URL)
return Promise.reject(error)
} finally {
logRouterPerf('dynamic-router-total', initStart, {
unresolvedRouteCount: unresolvedRoutes.length
})
isInitializing = false
}
}