feat: 实现前端管理看板与移动端扫码视图组件

This commit is contained in:
2026-08-04 17:10:01 +08:00
parent d18d9dbaac
commit 340a09007e
15 changed files with 1225 additions and 0 deletions

View File

@ -0,0 +1,87 @@
import { NavLink, Outlet, useLocation } from "react-router-dom";
import { QrCode, Package, ArrowLeft, LayoutDashboard, Smartphone } from "lucide-react";
const MENU = [
{
title: "全局概览",
path: "/admin/dashboard",
icon: LayoutDashboard,
description: "生产数据总览 · 产品 + 任务统计",
},
{
title: "产品管理",
path: "/admin/products",
icon: Package,
description: "创建产品 · 生成二维码 · 打印标签",
},
];
export default function AdminLayout() {
const location = useLocation();
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 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>
<div className="p-6">
<Outlet />
</div>
</main>
</div>
);
}

View File

@ -0,0 +1,49 @@
import { NavLink, Outlet } from "react-router-dom";
import { ScanLine, ClipboardList, Bell, User } from "lucide-react";
/** 底部导航 Tab 配置 */
const TABS = [
{ path: "/scan", label: "扫码干活", icon: ScanLine },
{ path: "/tasks", label: "我的任务", icon: ClipboardList },
{ path: "/notifications", label: "消息", icon: Bell },
{ path: "/profile", label: "我的", icon: User },
] as const;
/** 导航栏高度(供页面计算偏移量) */
export const TAB_BAR_HEIGHT = 64; // px(h-16)
export default function AppLayout() {
return (
<div className="flex h-dvh flex-col bg-gray-50">
{/* ======== 主内容区 ======== */}
<main className="flex-1 overflow-y-auto">
<Outlet />
</main>
{/* ======== 底部导航栏 ======== */}
<nav
className="fixed bottom-0 z-50 w-full border-t border-gray-200 bg-white pb-safe"
style={{ height: TAB_BAR_HEIGHT }}
>
<div className="mx-auto flex h-full max-w-lg items-center justify-around">
{TABS.map(({ path, label, icon: Icon }) => (
<NavLink
key={path}
to={path}
className={({ isActive }) =>
`flex flex-col items-center gap-0.5 px-3 py-1 transition-colors ${
isActive
? "text-blue-600"
: "text-gray-400 hover:text-gray-600"
}`
}
>
<Icon size={22} strokeWidth={2} />
<span className="text-[10px] font-medium leading-none">{label}</span>
</NavLink>
))}
</div>
</nav>
</div>
);
}