From 18c89111d8f8b15208b8eb613c722a4549445e87 Mon Sep 17 00:00:00 2001 From: WorkBuddy Date: Mon, 21 Sep 2026 19:34:31 +0800 Subject: [PATCH] =?UTF-8?q?debt(D6):=20Admin=20=E5=AF=BC=E8=88=AA=E5=8D=95?= =?UTF-8?q?=E6=BA=90=20=E2=80=94=E2=80=94=20NAV=5FITEMS=20=E6=B4=BE?= =?UTF-8?q?=E7=94=9F=E4=BE=A7=E6=A0=8F/=E5=91=BD=E4=BB=A4=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF/=E9=9D=A2=E5=8C=85=E5=B1=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 admin/nav.ts: 唯一配置源 NAV_ITEMS(to/label/group/icon/hideFromSidebar) - COMMAND_PALETTE_PAGES / ROUTE_LABELS / NAV_SECTIONS 全部由其派生 - 删除 AdminLayout 中三份平行清单(已出现漂移: data-pipeline 不在侧栏、 「系统」vs「系统设置」命名不一致、monitoring/eval 顺序两处相反) - 已知微差: 命令面板「评估与监控」组内顺序统一为侧栏口径(eval 在前) - 行为不变: 路由/标签/分组标题均保持现状;data-pipeline 保持仅面板可达 --- frontend/src/admin/AdminLayout.tsx | 58 +++----------------------- frontend/src/admin/nav.ts | 67 ++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 53 deletions(-) create mode 100644 frontend/src/admin/nav.ts diff --git a/frontend/src/admin/AdminLayout.tsx b/frontend/src/admin/AdminLayout.tsx index ba33bd1..e02fce3 100644 --- a/frontend/src/admin/AdminLayout.tsx +++ b/frontend/src/admin/AdminLayout.tsx @@ -9,6 +9,7 @@ import { useState, useEffect, useCallback } from 'react' import { NavLink, Outlet, useLocation } from 'react-router-dom' import { fetchAuthState, logout, UNAUTHORIZED_EVENT } from './api' import { fetchHealth } from './dal' +import { COMMAND_PALETTE_PAGES, ROUTE_LABELS, NAV_SECTIONS } from './nav' import Login from './Login' import { useCommandPalette, CommandPalette } from './useCommandPalette' @@ -88,58 +89,9 @@ function Icon({ name }: { name: string }) { } } -const NAV_PAGES: Array<{ to: string; label: string; group: string }> = [ - { to: '/admin', label: '仪表盘', group: '概览' }, - { to: '/admin/collection', label: '数据采集', group: '数据流水线' }, - { to: '/admin/data-completeness', label: '数据完整性', group: '数据流水线' }, - { to: '/admin/data-pipeline', label: '数据管线', group: '数据流水线' }, - { to: '/admin/predictions', label: '预测历史', group: '数据流水线' }, - { to: '/admin/backtest', label: '回测', group: '数据流水线' }, - { to: '/admin/monitoring', label: '监控', group: '评估与监控' }, - { to: '/admin/eval', label: '评估', group: '评估与监控' }, - { to: '/admin/settings', label: '设置', group: '系统' }, - { to: '/admin/logs', label: '日志', group: '系统' }, -] - -// 路由 → 面包屑标签 -const ROUTE_LABELS: Record = { - '/admin': '仪表盘', - '/admin/collection': '数据采集', - '/admin/data-completeness': '数据完整性', - '/admin/data-pipeline': '数据管线', - '/admin/predictions': '预测历史', - '/admin/backtest': '回测', - '/admin/monitoring': '监控', - '/admin/settings': '设置', - '/admin/logs': '日志', - '/admin/eval': '评估', -} - -const NAV_SECTIONS: { title: string; items: Array<{ to: string; label: string; icon: string }> }[] = [ - { - title: '数据流水线', - items: [ - { to: '/admin/collection', label: '数据采集', icon: 'collection' }, - { to: '/admin/data-completeness', label: '数据完整性', icon: 'chart' }, - { to: '/admin/predictions', label: '预测历史', icon: 'logs' }, - { to: '/admin/backtest', label: '回测', icon: 'repeat' }, - ], - }, - { - title: '评估与监控', - items: [ - { to: '/admin/eval', label: '评估', icon: 'eval' }, - { to: '/admin/monitoring', label: '监控', icon: 'monitor' }, - ], - }, - { - title: '系统设置', - items: [ - { to: '/admin/settings', label: '设置', icon: 'settings' }, - { to: '/admin/logs', label: '日志', icon: 'logs' }, - ], - }, -] +// D6: 导航三视图(侧栏/命令面板/面包屑)统一由 admin/nav.ts 的 NAV_ITEMS +// 单源派生 —— 此处的 NAV_PAGES/ROUTE_LABELS/NAV_SECTIONS 平行清单已删除, +// 新增页面/改标签只改 nav.ts 一处。 /** 报眉日期行,与前台同款式 */ function dateLine(): string { @@ -156,7 +108,7 @@ export default function AdminLayout() { const [healthOk, setHealthOk] = useState(null) const [authed, setAuthed] = useState(null) const location = useLocation() - const palette = useCommandPalette(NAV_PAGES) + const palette = useCommandPalette(COMMAND_PALETTE_PAGES) // 登录门禁:挂载时探测会话,收到 401 事件(会话过期)自动切回登录页 useEffect(() => { diff --git a/frontend/src/admin/nav.ts b/frontend/src/admin/nav.ts new file mode 100644 index 0000000..7b67e7d --- /dev/null +++ b/frontend/src/admin/nav.ts @@ -0,0 +1,67 @@ +/** + * Admin 导航单源(D6)。 + * + * 背景: 侧栏(NAV_SECTIONS)、命令面板(NAV_PAGES)、面包屑(ROUTE_LABELS) + * 此前各维护一份平行清单,已出现漂移 —— data-pipeline 不在侧栏、 + * 「系统」vs「系统设置」命名不一致、monitoring/eval 两处顺序相反。 + * + * 现在只允许维护 NAV_ITEMS 一份,其余视图一律由此派生; + * 禁止再新建平行导航清单(修改入口/新增页面只改这里)。 + */ + +export interface NavItem { + to: string + label: string + /** 命令面板中的分组名(展示原样) */ + group: string + /** 侧栏图标名(见 AdminLayout 的 Icon) */ + icon: string + /** 仅命令面板/面包屑可达,不进侧栏(深链页) */ + hideFromSidebar?: boolean +} + +/** 唯一的导航配置源。顺序 = 侧栏渲染顺序(命令面板分组内顺序与之相同)。 */ +export const NAV_ITEMS: NavItem[] = [ + { to: '/admin', label: '仪表盘', group: '概览', icon: 'chart' }, + { to: '/admin/collection', label: '数据采集', group: '数据流水线', icon: 'collection' }, + { to: '/admin/data-completeness', label: '数据完整性', group: '数据流水线', icon: 'chart' }, + { to: '/admin/data-pipeline', label: '数据管线', group: '数据流水线', icon: 'chart', hideFromSidebar: true }, + { to: '/admin/predictions', label: '预测历史', group: '数据流水线', icon: 'logs' }, + { to: '/admin/backtest', label: '回测', group: '数据流水线', icon: 'repeat' }, + { to: '/admin/eval', label: '评估', group: '评估与监控', icon: 'eval' }, + { to: '/admin/monitoring', label: '监控', group: '评估与监控', icon: 'monitor' }, + { to: '/admin/settings', label: '设置', group: '系统', icon: 'settings' }, + { to: '/admin/logs', label: '日志', group: '系统', icon: 'logs' }, +] + +/** 命令面板条目(原 NAV_PAGES 的唯一来源) */ +export const COMMAND_PALETTE_PAGES = NAV_ITEMS.map(({ to, label, group }) => ({ to, label, group })) + +/** 路由 → 面包屑标签(原 ROUTE_LABELS 的唯一来源) */ +export const ROUTE_LABELS: Record = Object.fromEntries( + NAV_ITEMS.map(i => [i.to, i.label]), +) + +/** 侧栏分组标题的历史显示名(仅侧栏使用;与面板分组名不同时在此映射) */ +const SIDEBAR_GROUP_TITLES: Record = { + 系统: '系统设置', +} + +/** + * 侧栏分组(原 NAV_SECTIONS 的唯一来源)。 + * 仪表盘(group=概览)在 AdminLayout 中独立渲染于顶部,不进分组循环。 + */ +export const NAV_SECTIONS = (() => { + const sidebarItems = NAV_ITEMS.filter(i => !i.hideFromSidebar && i.group !== '概览') + const titles: string[] = [] + for (const i of sidebarItems) { + const title = SIDEBAR_GROUP_TITLES[i.group] ?? i.group + if (!titles.includes(title)) titles.push(title) + } + return titles.map(title => ({ + title, + items: sidebarItems + .filter(i => (SIDEBAR_GROUP_TITLES[i.group] ?? i.group) === title) + .map(({ to, label, icon }) => ({ to, label, icon })), + })) +})()