Files
sundynix-agentix/sundynix-admin/src/shell/AppShell.tsx
T
Blizzard f6a669070d refactor(admin): 控制台改为路由表驱动的动态路由 (react-router)
控制台从 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>
2026-06-10 16:09:07 +08:00

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>
);
}