Files
hunjing/frontend/src/components/layout/AdminShell.tsx
T
chenxin 73ad3c02b0 启动补丁:乾坤装配参数名、注册接口双前缀、/chat 残留链接清理
- main.py:Executor(sandbox, http_client=…) 参数名与构造签名不符(http=…),致乾坤沙箱装配降级 503——修正
- 注册页手写 fetch 多拼一层 v1(/api/proxy/v1/auth/register → 后端 /api/v1/v1/* 404);BACKEND_ROOT 已含 /api/v1,proxy 调用不应再带——去掉
- /chat 页 M2b 已随线性会话退役,对话主入口为 /trees(镜树);六处残留链接(首页/登录/注册跳转/错误页/404 页/管理壳)全部改指 /trees
- 回归:前端 type-check + 203 passed;后端百工+API 套件 45 passed
2026-08-28 10:31:36 +08:00

238 lines
7.6 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.
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useCallback } from "react";
import type { ReactNode } from "react";
import {
AdminIcon,
AgentIcon,
AuditIcon,
GoalIcon,
MemoryIcon,
PerceptionIcon,
PlanIcon,
TenantIcon,
ToolsIcon,
TraceIcon,
UserIcon,
WorldIcon,
} from "@/components/ui/Icons";
import { cn } from "@/lib/utils/cn";
type AdminNavItem = { href: string; label: string; icon: ReactNode };
/**
* 司命台治理导航(横向 — 设计稿 topbar-nav)。
*/
const ADMIN_NAV: AdminNavItem[] = [
{ href: "/admin", label: "概览", icon: <AdminIcon /> },
{ href: "/admin/audit", label: "起居注", icon: <AuditIcon /> },
{ href: "/admin/traces", label: "链路追踪", icon: <TraceIcon /> },
{ href: "/admin/users", label: "用户管理", icon: <UserIcon /> },
{ href: "/admin/tenants", label: "多租户", icon: <TenantIcon /> },
{
href: "/admin/characters/templates",
label: "人物模板",
icon: <AdminIcon />,
},
];
/** 九枢治理导航(设计稿 pivot nav)。与 pivots/[pivot] 路由对齐 */
const PIVOT_NAV: AdminNavItem[] = [
{ href: "/admin/goals", label: "太一", icon: <GoalIcon /> },
{ href: "/admin/safety", label: "司命", icon: <AdminIcon /> },
{ href: "/admin/perception", label: "烛照", icon: <PerceptionIcon /> },
{ href: "/admin/world", label: "河图", icon: <WorldIcon /> },
{ href: "/admin/plans", label: "洛书", icon: <PlanIcon /> },
{ href: "/admin/memory", label: "息壤", icon: <MemoryIcon /> },
{ href: "/admin/tools", label: "百工", icon: <ToolsIcon /> },
{ href: "/admin/agents", label: "天工阁", icon: <AgentIcon /> },
];
/**
* 司命台外壳:独立冷色壳层(admin-shell → slate 色温分化)。
* 设计稿 admin-preview.html §4 AdminShell 完全对齐。
*
* 壳层特性:
* - 独立冷色 CSS 变量(.admin-shell 激活 `--slate` 色系)
* - 顶部横向双层导航(治理 nav + 九枢 pivot nav)
* - 主题切换 + 返回主域入口
* - iOS safe-area 适配
*/
export function AdminShell({ children }: { children: ReactNode }) {
const pathname = usePathname();
/** 设计稿 toggleTheme — 通过触发表单更改 / 自定义事件 */
const toggleTheme = useCallback(() => {
const root = document.documentElement;
const isDark = root.classList.contains("dark");
if (isDark) {
root.classList.remove("dark");
} else {
root.classList.add("dark");
}
// 持久化到 settings
try {
const raw = localStorage.getItem("hunjing-settings");
if (raw) {
const parsed = JSON.parse(raw);
if (parsed.state) {
parsed.state.theme = isDark ? "light" : "dark";
localStorage.setItem("hunjing-settings", JSON.stringify(parsed));
}
}
} catch {
// silent
}
}, []);
const navItemClass = (href: string, opts?: { pivot?: boolean }) =>
cn(
"inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm font-medium transition-colors shrink-0",
opts?.pivot ? "text-xs" : "text-sm",
pathname === href || pathname.startsWith(`${href}/`)
? "bg-patina-wash text-patina"
: "text-muted hover:bg-overlay-hover hover:text-fg",
);
return (
<div className="relative flex min-h-screen flex-col admin-shell">
<header
className="sticky top-0 z-30 flex flex-wrap items-center gap-2 border-b px-4 py-2.5 md:gap-3 md:px-6"
style={{
background: "var(--glass)",
backdropFilter: "blur(16px) saturate(140%)",
borderColor: "var(--rule)",
paddingLeft: "calc(1rem + env(safe-area-inset-left, 0px))",
paddingRight: "calc(1rem + env(safe-area-inset-right, 0px))",
}}
>
{/* 品牌标志 — 设计稿 topbar-brand */}
<Link
href="/admin"
className="flex items-center gap-1.5 font-serif text-base font-bold tracking-wide text-fg shrink-0"
style={{ letterSpacing: "0.06em" }}
>
{/* 设计稿 SVG knob:描边铜镜无极标记 */}
<svg
className="shrink-0"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="8.5" />
<circle cx="12" cy="12" r="4.5" opacity="0.45" />
<circle
cx="12"
cy="12"
r="1.6"
stroke="none"
fill="var(--cinnabar)"
/>
</svg>
司命台
</Link>
{/* 分隔符 */}
<span className="shrink-0 text-fg-faint opacity-50 select-none">/</span>
{/* 司命治理导航 — 设计稿 topbar-nav */}
<nav
aria-label="司命台导航"
className="order-3 flex w-full items-center gap-0.5 overflow-x-auto md:order-none md:w-auto md:flex-shrink"
>
{ADMIN_NAV.map((item) => (
<Link
key={item.href}
href={item.href}
className={navItemClass(item.href)}
>
{item.label}
</Link>
))}
</nav>
{/* 九枢分隔 + 导航 — 设计稿中金色竖线 | 分隔 */}
<span className="mx-1 hidden shrink-0 select-none text-fg-faint opacity-40 md:inline">
|
</span>
<nav
aria-label="九枢导航"
className="order-4 flex w-full items-center gap-0.5 overflow-x-auto md:order-none md:w-auto"
>
{PIVOT_NAV.map((item) => (
<Link
key={item.href}
href={item.href}
className={navItemClass(item.href, { pivot: true })}
>
{item.label}
</Link>
))}
</nav>
{/* 弹性间隔 */}
<span className="flex-1 min-w-0" />
{/* 返回主域 — 设计稿 topbar-back */}
<Link
href="/trees"
className="flex shrink-0 items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-overlay-hover hover:text-fg"
title="返回主域"
>
<svg
width="12"
height="12"
viewBox="0 0 16 16"
fill="none"
aria-hidden
>
<path
d="M10 4l-4 4 4 4"
stroke="currentColor"
strokeWidth="1.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
主域
</Link>
{/* 主题切换 — 设计稿 topbar-back 风格 */}
<button
type="button"
onClick={toggleTheme}
className="flex shrink-0 items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-overlay-hover hover:text-fg"
title="切换主题"
>
<svg
width="13"
height="13"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.3"
strokeLinecap="round"
>
<circle cx="8" cy="8" r="3" />
<path d="M8 1v2M8 13v2M2 8h2M12 8h2M3.5 3.5l1.5 1.5M11 11l1.5 1.5M3.5 12.5l1.5-1.5M11 5l1.5-1.5" />
</svg>
<span id="theme-label-admin">{/* 客户端渲染时显示当前主题 */}</span>
</button>
</header>
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
{children}
</main>
</div>
);
}