Files
track/frontend/src/components/layout/AdminLayout.tsx
duxingchen fdd7465afb feat(前端): 新增 WIP 分布矩阵透视表页面
- MatrixBoard.tsx: Radio切换 按人员/按工序,动态交叉表转换(规格型号为行)
- 单元格显示设备数量+主负责人,首列规格固定左、末列合计固定右,bordered Excel风格
- 路由 /admin/matrix 注册,侧边栏新增「WIP 分布矩阵」入口
2026-08-28 16:25:26 +08:00

151 lines
5.1 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.

import { NavLink, Outlet, useLocation, useNavigate, Navigate } from "react-router-dom";
import { QrCode, Package, ArrowLeft, LayoutDashboard, Smartphone, GitBranch, LogOut, User, Users, BarChart3, Table2 } from "lucide-react";
import { useAuth } from "../../contexts/AuthContext";
const MENU = [
{
title: "全局概览",
path: "/admin/dashboard",
icon: LayoutDashboard,
description: "生产数据总览 · 产品 + 任务统计",
},
{
title: "产品管理",
path: "/admin/products",
icon: Package,
description: "创建产品 · 生成二维码 · 打印标签",
},
{
title: "任务全景",
path: "/admin/tasks",
icon: GitBranch,
description: "身份证查任务树 · 裂变/返工可视化",
},
{
title: "人员看板",
path: "/admin/people",
icon: Users,
description: "按负责人查看在制品设备分布",
},
{
title: "效能分析",
path: "/admin/analytics",
icon: BarChart3,
description: "人员效能 / 设备流转 ECharts 可视化",
},
{
title: "WIP 分布矩阵",
path: "/admin/matrix",
icon: Table2,
description: "规格型号 × 人员/工序 在制品透视表",
},
];
export default function AdminLayout() {
const location = useLocation();
const navigate = useNavigate();
const { user, logout, isAuthenticated, loading } = useAuth();
// 认证加载中
if (loading) {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<div className="h-8 w-8 animate-spin rounded-full border-2 border-blue-500 border-t-transparent" />
</div>
);
}
// 未登录 → 跳转登录页
if (!isAuthenticated) {
return <Navigate to="/admin/login" replace />;
}
function handleLogout() {
logout();
navigate("/admin/login", { replace: true });
}
return (
<div className="flex min-h-screen bg-gray-50">
{/* 侧边栏 */}
<aside className="fixed left-0 top-0 z-40 flex h-screen w-56 flex-col border-r border-gray-200 bg-white">
<div className="flex items-center gap-2 border-b border-gray-100 px-5 py-4">
<QrCode className="h-5 w-5 text-blue-600" />
<span className="text-sm font-bold text-gray-800"> · </span>
</div>
<nav className="flex-1 space-y-0.5 px-3 py-4">
{MENU.map((item) => {
const isActive = location.pathname.startsWith(item.path);
return (
<NavLink
key={item.path}
to={item.path}
className={`flex items-start gap-3 rounded-lg px-3 py-2.5 transition-colors ${
isActive ? "bg-blue-50 text-blue-700" : "text-gray-600 hover:bg-gray-100"
}`}
>
<item.icon className={`mt-0.5 h-4 w-4 shrink-0 ${isActive ? "text-blue-600" : "text-gray-400"}`} />
<div>
<p className="text-sm font-medium">{item.title}</p>
<p className="text-xs text-gray-400">{item.description}</p>
</div>
</NavLink>
);
})}
</nav>
<div className="space-y-1 border-t border-gray-100 px-5 py-3">
<a
href="/scan"
className="flex items-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium text-green-600 transition-colors hover:bg-green-50"
>
<Smartphone className="h-3.5 w-3.5" />
</a>
<a
href="/"
className="flex items-center gap-1.5 rounded-md px-2 py-1.5 text-xs text-gray-400 transition-colors hover:text-gray-600"
>
<ArrowLeft className="h-3.5 w-3.5" />
</a>
</div>
</aside>
<main className="ml-56 flex-1">
<div className="sticky top-0 z-30 flex items-center justify-between border-b border-gray-200 bg-white px-6 py-3">
<div className="flex items-center gap-2 text-xs text-gray-400">
<LayoutDashboard className="h-3 w-3" />
<span></span>
<span>/</span>
<span className="font-medium text-gray-600">
{MENU.find((m) => location.pathname.startsWith(m.path))?.title ?? "页面"}
</span>
</div>
{/* 用户信息 + 登出 */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5 text-xs text-gray-500">
<User className="h-3.5 w-3.5" />
<span className="font-medium text-gray-700">
{user?.display_name ?? user?.username ?? "—"}
</span>
</div>
<button
onClick={handleLogout}
className="flex items-center gap-1 rounded-md px-2 py-1 text-xs text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600"
>
<LogOut className="h-3 w-3" />
退
</button>
</div>
</div>
<div className="p-6">
<Outlet />
</div>
</main>
</div>
);
}