feat: Web端401队列拦截器 + 消息/任务/个人中心重写 + TabBar红点 + Admin页面完善
This commit is contained in:
@ -19,7 +19,7 @@ const MENU = [
|
||||
title: "任务全景",
|
||||
path: "/admin/tasks",
|
||||
icon: GitBranch,
|
||||
description: "序列号查任务树 · 裂变/返工可视化",
|
||||
description: "身份证查任务树 · 裂变/返工可视化",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { NavLink, Outlet } from "react-router-dom";
|
||||
import { ScanLine, ClipboardList, Bell, User } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { NavLink, Outlet, useNavigate } from "react-router-dom";
|
||||
import { ScanLine, ClipboardList, Bell, User, ArrowLeft } from "lucide-react";
|
||||
import { useAuth } from "../../contexts/AuthContext";
|
||||
import { getNotifications } from "../../services/notificationApi";
|
||||
|
||||
/** 底部导航 Tab 配置 */
|
||||
/** 底部导航 Tab 配置 — 与 uni-app pages.json tabBar.list 完全一致 */
|
||||
const TABS = [
|
||||
{ path: "/scan", label: "扫码干活", icon: ScanLine },
|
||||
{ path: "/tasks", label: "我的任务", icon: ClipboardList },
|
||||
@ -10,38 +13,92 @@ const TABS = [
|
||||
] as const;
|
||||
|
||||
/** 导航栏高度(供页面计算偏移量) */
|
||||
export const TAB_BAR_HEIGHT = 64; // px(h-16)
|
||||
export const TAB_BAR_HEIGHT = 64;
|
||||
const TOP_BAR_HEIGHT = 48;
|
||||
|
||||
export default function AppLayout() {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
|
||||
// 🔔 未读消息数轮询
|
||||
useEffect(() => {
|
||||
const userId = user?.username || user?.id || "";
|
||||
if (!userId) return;
|
||||
|
||||
function poll() {
|
||||
getNotifications(userId, 0, 1)
|
||||
.then((res) => setUnreadCount(res.unread_count || 0))
|
||||
.catch(() => {}); // 静默
|
||||
}
|
||||
|
||||
poll(); // 立即请求一次
|
||||
const id = setInterval(poll, 30_000); // 每 30 秒轮询
|
||||
return () => clearInterval(id);
|
||||
}, [user]);
|
||||
|
||||
return (
|
||||
<div className="flex h-dvh flex-col bg-gray-50">
|
||||
{/* ======== 顶部导航栏 — 返回管理端 ======== */}
|
||||
<header
|
||||
className="fixed top-0 left-0 right-0 z-50 flex items-center border-b border-gray-200 bg-white px-4 shadow-sm"
|
||||
style={{ height: TOP_BAR_HEIGHT }}
|
||||
>
|
||||
<button
|
||||
onClick={() => navigate("/admin/dashboard")}
|
||||
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-50 active:bg-blue-100"
|
||||
>
|
||||
<ArrowLeft size={16} strokeWidth={2.5} />
|
||||
返回管理端
|
||||
</button>
|
||||
<span className="ml-auto text-xs text-gray-400">生产流转 · 移动端</span>
|
||||
</header>
|
||||
|
||||
{/* ======== 主内容区 ======== */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
<main
|
||||
className="flex-1 overflow-y-auto"
|
||||
style={{
|
||||
paddingTop: TOP_BAR_HEIGHT,
|
||||
paddingBottom: TAB_BAR_HEIGHT,
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
{/* ======== 底部导航栏 ======== */}
|
||||
{/* ======== 底部导航栏 — 与 uni-app tabBar 完全一致 ======== */}
|
||||
<nav
|
||||
className="fixed bottom-0 z-50 w-full border-t border-gray-200 bg-white pb-safe"
|
||||
className="fixed bottom-0 left-0 right-0 z-50 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>
|
||||
))}
|
||||
{TABS.map(({ path, label, icon: Icon }) => {
|
||||
const isNotifyTab = path === "/notifications";
|
||||
return (
|
||||
<NavLink
|
||||
key={path}
|
||||
to={path}
|
||||
className={({ isActive }) =>
|
||||
`relative 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"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<span className="relative">
|
||||
<Icon size={22} strokeWidth={2} />
|
||||
{/* 🔴 消息 Tab 未读红点 */}
|
||||
{isNotifyTab && unreadCount > 0 && (
|
||||
<span className="absolute -top-1 -right-2 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-red-500 px-1 text-[9px] font-bold text-white shadow-[0_0_0_2px_white]">
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium leading-none">{label}</span>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user