/** * Ai-DOP 侧栏一级目录显示名(与 ai-dop-platform/tools/gen_aidop_menu.py 中模块编码一致)。 * 在登录菜单树写入路由前覆盖 meta.title,避免库中仍为旧标题时侧栏不更新。 */ export const AIDOP_DIRECTORY_TITLES: Record = { aidopDirS0: 'S0 运营建模', aidopDirS1: 'S1 产销协同', aidopDirS2: 'S2 制造协同', aidopDirS3: 'S3 供应协同', aidopDirS4: 'S4 采购执行', aidopDirS5: 'S5 物料仓储', aidopDirS6: 'S6 生产执行', aidopDirS7: 'S7 成品仓储', aidopDirS8: 'S8 异常监控', aidopDirS9: 'S9 运营指标', // 兼容旧种子路由名(数据库未迁移到新 Path/Name 时) aidopDirM01: 'S0 运营建模', aidopDirM02: 'S1 产销协同', aidopDirM03: 'S2 制造协同', aidopDirM04: 'S3 供应协同', aidopDirM05: 'S4 采购执行', aidopDirM06: 'S5 物料仓储', aidopDirM07: 'S6 生产执行', aidopDirM08: 'S7 成品仓储', aidopDirM09: 'S8 异常监控', aidopDirM10: 'S9 运营指标', }; /** * 兼容库中旧菜单 component: * 例如 M16/系统首页 仍指向占位页时,前端在菜单树注入阶段改为新首页组件。 */ const AIDOP_COMPONENT_OVERRIDES: Record = { '/aidop/m16/001': '/dashboard/home', }; type AMenu = Record; /** 侧栏「智慧运营看板」下 10 个看板(九宫格+S1~S9);统一查询条件仅应出现在这些 component 对应页面 */ /** 工单排产子页(新标签打开,侧栏隐藏) */ const PRODUCTION_AUX_ROUTES: Array<{ path: string; title: string; component: string; name: string }> = [ { path: '/aidop/production/work-order-trace', title: '工单执行追踪', component: '/aidop/production/workOrderSchedulingTraceForm', name: 'aidopProductionWorkOrderTrace' }, { path: '/aidop/production/work-order-materials', title: '工单物料明细', component: '/aidop/production/workOrderMaterialDetailList', name: 'aidopProductionWorkOrderMaterials' }, { path: '/aidop/production/work-order-routings', title: '工单工序明细', component: '/aidop/production/workOrderRoutingDetailList', name: 'aidopProductionWorkOrderRoutings' }, ]; const SMART_OPS_CHILDREN: Array<{ path: string; title: string; component: string; name: string }> = [ { path: '/aidop/smart-ops/grid', title: '九宫格智慧运营看板', component: '/dashboard/home', name: 'aidopSmartOpsGrid' }, { path: '/aidop/smart-ops/modeling', title: '运营指标建模', component: '/aidop/kanban/s0', name: 'aidopSmartOpsModeling' }, { path: '/aidop/smart-ops/dashboard-config', title: '详情看板配置', component: '/aidop/kanban/dashboardConfig', name: 'aidopSmartOpsDashboardConfig' }, { path: '/aidop/smart-ops/s1', title: 'S1产销协同看板', component: '/aidop/kanban/s1', name: 'aidopSmartOpsS1' }, { path: '/aidop/smart-ops/s2', title: 'S2制造协同看板', component: '/aidop/kanban/s2', name: 'aidopSmartOpsS2' }, { path: '/aidop/smart-ops/s3', title: 'S3供应协同看板', component: '/aidop/kanban/s3', name: 'aidopSmartOpsS3' }, { path: '/aidop/smart-ops/s4', title: 'S4采购执行看板', component: '/aidop/kanban/s4', name: 'aidopSmartOpsS4' }, { path: '/aidop/smart-ops/s5', title: 'S5物料仓储看板', component: '/aidop/kanban/s5', name: 'aidopSmartOpsS5' }, { path: '/aidop/smart-ops/s6', title: 'S6生产执行看板', component: '/aidop/kanban/s6', name: 'aidopSmartOpsS6' }, { path: '/aidop/smart-ops/s7', title: 'S7成品仓储看板', component: '/aidop/kanban/s7', name: 'aidopSmartOpsS7' }, { path: '/aidop/smart-ops/s8', title: 'S8异常监控看板', component: '/aidop/kanban/s8', name: 'aidopSmartOpsS8' }, { path: '/aidop/smart-ops/s9', title: 'S9运营指标看板', component: '/aidop/kanban/s9', name: 'aidopSmartOpsS9' }, ]; /** * S8 异常监控大屏下的可见子菜单兜底(visible,非 hidden)。 * 用于 SysMenuSeedData 已新增但库尚未刷新的过渡期,避免侧栏不显示「订单执行档案」。 * 数据库刷新后由 upsertMenu 合并 component/meta,行为幂等。 */ const S8_MONITORING_VISIBLE_ROUTES: Array<{ path: string; title: string; component: string; name: string; icon: string }> = [ { path: '/aidop/s8/monitoring/order-execution', title: '订单执行档案', component: '/aidop/s8/monitoring/SoOrderExecutionDashboardPage', name: 'aidopS8OrderExecution', icon: 'ele-Tickets', }, ]; /** S8 配置中心中的隐藏详情页;库中若缺少授权或未同步新种子,前端补齐后可避免点卡片进入 404 */ const S8_CONFIG_HIDDEN_ROUTES: Array<{ path: string; title: string; component: string; name: string }> = [ { path: '/aidop/s8/config/exception-types', title: '异常类型配置', component: '/aidop/s8/config/S8ExceptionTypeConfigPage', name: 'aidopS8ExceptionTypeConfig', }, { path: '/aidop/s8/config/dashboard-cells', title: '大屏卡片配置', component: '/aidop/s8/config/S8DashboardCellConfigPage', name: 'aidopS8DashboardCellConfig', }, { path: '/aidop/s8/config/notifications', title: '通知分层配置', component: '/aidop/s8/config/S8NotificationLayerPage', name: 'aidopS8NotificationLayerConfig', }, ]; function collectPathsUnder(node: AMenu, acc: Set): void { const p = node.path as string | undefined; if (p) acc.add(p); for (const c of node.children ?? []) collectPathsUnder(c as AMenu, acc); } function upsertMenu(parent: AMenu, menu: AMenu): void { parent.children = parent.children ?? []; const idx = parent.children.findIndex((x: AMenu) => x.path === menu.path); if (idx >= 0) { parent.children[idx] = { ...parent.children[idx], ...menu, meta: { ...(parent.children[idx].meta ?? {}), ...(menu.meta ?? {}) } }; } else { parent.children.unshift(menu); } } /** * 用户已有 /aidop 根菜单时,patchAidopCustomMenusIfMissing 会补全智慧运营等子路由; * 个性化首页校验须认可这些 path(避免库中子项 path 与绝对 path 不一致时误判回退)。 */ export function augmentAllowedPathsForAidopHomepage(menuTree: any[] | undefined, allowed: Set): void { if (!menuTree?.length) return; const aidopRoot = menuTree.find((x: AMenu) => x.path === '/aidop' || x.name === 'aidopRoot'); if (!aidopRoot) return; for (const c of SMART_OPS_CHILDREN) allowed.add(c.path); for (const c of PRODUCTION_AUX_ROUTES) allowed.add(c.path); allowed.add('/aidop/smart-ops'); allowed.add('/aidop/smart-diagnosis'); } /** 仅当接口返回的树里尚不存在对应 path 时补节点,避免与 sys_menu 重复;库已同步时等价于 no-op。 */ function patchAidopCustomMenusIfMissing(routes: AMenu[] | undefined): void { if (!routes?.length) return; const aidopRoot = routes.find((x) => x.path === '/aidop' || x.name === 'aidopRoot'); if (!aidopRoot) return; const existing = new Set(); collectPathsUnder(aidopRoot, existing); if (!existing.has('/aidop/smart-ops')) { upsertMenu(aidopRoot, { path: '/aidop/smart-ops', name: 'aidopSmartOpsRoot', component: 'Layout', meta: { title: '智慧运营看板', icon: 'ele-DataBoard' }, }); existing.add('/aidop/smart-ops'); } const smartOps = aidopRoot.children.find((x: AMenu) => x.path === '/aidop/smart-ops'); if (smartOps) { for (const child of SMART_OPS_CHILDREN) { if (existing.has(child.path)) continue; upsertMenu(smartOps, { path: child.path, name: child.name, component: child.component, meta: { title: child.title, icon: 'ele-DataAnalysis' }, }); existing.add(child.path); } const orderMap = new Map(SMART_OPS_CHILDREN.map((c, i) => [c.path, i])); smartOps.children = (smartOps.children ?? []).sort((a: AMenu, b: AMenu) => { const ai = orderMap.has(a.path) ? (orderMap.get(a.path) as number) : 9999; const bi = orderMap.has(b.path) ? (orderMap.get(b.path) as number) : 9999; return ai - bi; }); } if (!existing.has('/aidop/smart-diagnosis')) { upsertMenu(aidopRoot, { path: '/aidop/smart-diagnosis', name: 'aidopSmartDiagnosis', component: '/aidop/diagnosis/index', meta: { title: '智慧诊断', icon: 'ele-TrendCharts' }, }); } for (const child of PRODUCTION_AUX_ROUTES) { if (existing.has(child.path)) continue; upsertMenu(aidopRoot, { path: child.path, name: child.name, component: child.component, meta: { title: child.title, isHide: true, icon: 'ele-Document' }, }); existing.add(child.path); } } /** 为 S8 异常监控目录补可见子路由(如「订单执行档案」),兼容库未刷新到最新菜单的过渡期。 */ function patchS8MonitoringVisibleRoutesIfMissing(routes: AMenu[] | undefined): void { if (!routes?.length) return; const aidopRoot = routes.find((x) => x.path === '/aidop' || x.name === 'aidopRoot'); if (!aidopRoot?.children?.length) return; const s8Root = aidopRoot.children.find( (x: AMenu) => x.path === '/aidop/s8' || x.name === 'aidopDirS8' || x.name === 'aidopDirM09', ); if (!s8Root?.children?.length) return; const monitoringDir = s8Root.children.find( (x: AMenu) => x.path === '/aidop/s8/monitoring' || x.name === 'aidopS8MonitoringDir', ); if (!monitoringDir) return; for (const route of S8_MONITORING_VISIBLE_ROUTES) { upsertMenu(monitoringDir, { path: route.path, name: route.name, component: route.component, meta: { title: route.title, icon: route.icon }, }); } } /** 为 S8 配置中心补隐藏子路由,兼容数据库未刷新到最新菜单/角色授权时的直达 404 */ function patchS8ConfigHiddenRoutesIfMissing(routes: AMenu[] | undefined): void { if (!routes?.length) return; const aidopRoot = routes.find((x) => x.path === '/aidop' || x.name === 'aidopRoot'); if (!aidopRoot?.children?.length) return; const s8Root = aidopRoot.children.find((x: AMenu) => x.path === '/aidop/s8' || x.name === 'aidopDirS8' || x.name === 'aidopDirM09'); if (!s8Root) return; const existing = new Set(); collectPathsUnder(s8Root, existing); for (const route of S8_CONFIG_HIDDEN_ROUTES) { upsertMenu(s8Root, { path: route.path, name: route.name, component: route.component, meta: { title: route.title, icon: 'ele-Document', isHide: true }, }); existing.add(route.path); } } /** * 递归覆盖 Ai-DOP 一级目录的 meta.title,供侧栏 / tagsView / 菜单搜索使用。 * 智慧诊断、智慧运营看板以 sys_menu 为准;若租户/角色未关联到库中菜单,则仅补缺失 path,避免双份。 */ /** 强制智慧运营子菜单 component,避免库中仍为占位/错误路径时前端仍加载旧页 */ function patchSmartOpsRouteComponents(nodes: AMenu[] | undefined): void { if (!nodes?.length) return; const byPath = Object.fromEntries(SMART_OPS_CHILDREN.map((c) => [c.path, c.component])) as Record; for (const item of nodes) { const raw = item.path as string | undefined; const p = raw ? (raw.startsWith('/') ? raw : `/${raw}`) : ''; const comp = p ? byPath[p] : undefined; if (comp) item.component = comp; if (item.children?.length) patchSmartOpsRouteComponents(item.children as AMenu[]); } } function patchS8ConfigRouteComponents(nodes: AMenu[] | undefined): void { if (!nodes?.length) return; const byPath = Object.fromEntries(S8_CONFIG_HIDDEN_ROUTES.map((c) => [c.path, c.component])) as Record; for (const item of nodes) { const raw = item.path as string | undefined; const p = raw ? (raw.startsWith('/') ? raw : `/${raw}`) : ''; const comp = p ? byPath[p] : undefined; if (comp) item.component = comp; if (item.children?.length) patchS8ConfigRouteComponents(item.children as AMenu[]); } } export function patchAidopMenuTitles(routes: any[] | undefined): void { if (!routes?.length) return; patchAidopCustomMenusIfMissing(routes as AMenu[]); patchS8MonitoringVisibleRoutesIfMissing(routes as AMenu[]); patchS8ConfigHiddenRoutesIfMissing(routes as AMenu[]); patchSmartOpsRouteComponents(routes as AMenu[]); patchS8ConfigRouteComponents(routes as AMenu[]); for (const item of routes) { const name = item.name as string | undefined; if (name && AIDOP_DIRECTORY_TITLES[name]) { item.meta = item.meta ?? {}; item.meta.title = AIDOP_DIRECTORY_TITLES[name]; } const path = item.path as string | undefined; if (path && AIDOP_COMPONENT_OVERRIDES[path]) { item.component = AIDOP_COMPONENT_OVERRIDES[path]; } if (item.children?.length) patchAidopMenuTitles(item.children); } }