f6a669070d
控制台从 useState 切 tab + 硬编码条件渲染,改为路由注册表驱动 + 真实 URL 路由, 加页面只需在 routes.tsx 加一条,不动外壳。 - 依赖 react-router-dom v7;App=HashRouter(静态托管/桌面内嵌都能深链) - routes.tsx:路由注册表(单一事实源,导航+内容都派生);real 页面 lazy 懒加载(代码分割) - shell/AppShell:NavLink 分组导航(配置/平台) + Routes + Suspense + 健康灯,全从注册表派生 - 页面归入 pages/(ModelsPage 移入),components/Soon 占位复用 - 验证:npm build✓(ModelsPage 独立 chunk=懒加载生效);真实浏览器——默认重定向 #/models、 nav 切换改 URL hash、深链 #/guardrails 直达、浏览器后退回 #/datasources、active 高亮 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
import { Suspense, useEffect, useState } from "react";
|
|
import { NavLink, Routes, Route, Navigate, useLocation } from "react-router-dom";
|
|
|
|
import { routes, navGroups, defaultPath } from "../routes";
|
|
import { gatewayOnline } from "../api";
|
|
|
|
// 控制台外壳:导航与内容均由路由注册表派生(动态路由)。
|
|
export function AppShell() {
|
|
const [online, setOnline] = useState(false);
|
|
const loc = useLocation();
|
|
const current = routes.find((r) => r.path === loc.pathname);
|
|
|
|
useEffect(() => {
|
|
const ping = () => gatewayOnline().then(setOnline);
|
|
ping();
|
|
const id = setInterval(ping, 4000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex h-screen w-screen text-gray-900">
|
|
<aside className="flex w-56 shrink-0 flex-col border-r bg-gray-50">
|
|
<div className="border-b p-4">
|
|
<div className="text-sm font-bold text-gray-800">sundynix-agentix</div>
|
|
<div className="text-[11px] text-gray-400">运维控制台</div>
|
|
</div>
|
|
<nav className="flex flex-col gap-1 p-2">
|
|
{navGroups().map((g) => (
|
|
<div key={g.group}>
|
|
<div className="mt-2 px-3 text-[9px] font-semibold tracking-wider text-gray-300">{g.group}</div>
|
|
{g.items.map((r) => (
|
|
<NavLink
|
|
key={r.path}
|
|
to={r.path}
|
|
className={({ isActive }) =>
|
|
`flex items-center justify-between rounded px-3 py-2 text-sm ${
|
|
isActive ? "bg-violet-50 font-medium text-violet-700" : "text-gray-600 hover:bg-gray-100"
|
|
}`
|
|
}
|
|
>
|
|
{r.label}
|
|
{!r.ready && <span className="text-[9px] text-gray-300">规划</span>}
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
))}
|
|
</nav>
|
|
<div className="mt-auto flex items-center gap-2 border-t p-4 text-[11px] text-gray-500">
|
|
<span className={`h-2 w-2 rounded-full ${online ? "bg-emerald-500" : "bg-rose-500"}`} />
|
|
Gateway {online ? "在线" : "离线"}
|
|
</div>
|
|
</aside>
|
|
|
|
<main className="flex-1 overflow-auto p-6">
|
|
<h1 className="mb-4 text-lg font-semibold text-gray-800">{current?.label ?? ""}</h1>
|
|
<Suspense fallback={<div className="text-sm text-gray-400">加载中…</div>}>
|
|
<Routes>
|
|
{routes.map((r) => (
|
|
<Route key={r.path} path={r.path} element={r.element} />
|
|
))}
|
|
<Route path="*" element={<Navigate to={defaultPath} replace />} />
|
|
</Routes>
|
|
</Suspense>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|