feat: 新增管理员用户管理功能与账号封禁体系
此提交完成了全站管理员后台核心功能开发: 1. 新增AdminUserService与管理接口,支持用户列表查询、角色变更与封禁解封 2. 重构认证链路,统一账号封禁错误码与处理逻辑 3. 新增全局强制下线机制,账号被封禁时自动弹出告知弹窗并清理登录态 4. 重构管理员后台路由与布局,统一权限校验逻辑 5. 优化通知轮询逻辑,页面切回前台时立即刷新未读计数 6. 合并原公告、外观管理页到统一后台面板
This commit is contained in:
@@ -18,12 +18,11 @@ import {
|
||||
Home,
|
||||
User as UserIcon,
|
||||
ChevronDown,
|
||||
Megaphone,
|
||||
Palette,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
import { apiLogout, apiMe, type User } from "@/lib/api";
|
||||
import { hasAuthCookieHint } from "@/lib/cookies";
|
||||
import { onUserUpdate } from "@/lib/userEvents";
|
||||
import { onUserUpdate, onForceLogout } from "@/lib/userEvents";
|
||||
import NotificationBell from "./NotificationBell";
|
||||
import Avatar from "./Avatar";
|
||||
|
||||
@@ -60,6 +59,21 @@ export default function Header({
|
||||
setUser((prev) => (prev ? { ...prev, ...patch } : prev));
|
||||
}), []);
|
||||
|
||||
// 强制下线(当前唯一来源:账号被管理员封禁)。
|
||||
// 统一复用登出清理:尽力调用 /api/logout 清除 HttpOnly cookie(该端点不要求
|
||||
// 有效登录态),本地立即游客化并 router.refresh 校正 SSR 区域,
|
||||
// 封禁告知弹窗由 AccountGuard 负责,避免状态与 UI 清理分散
|
||||
useEffect(() => onForceLogout(() => {
|
||||
setMenuOpen(false);
|
||||
apiLogout()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
setUser(null);
|
||||
setUnread(0);
|
||||
router.refresh();
|
||||
});
|
||||
}), [router]);
|
||||
|
||||
// 挂载后静默校正一次(仅一次):覆盖 SSR 期间后端不可用、旧 Path refresh
|
||||
// 半登录等边缘情况。正常 F5 下结果与 SSR 一致,不产生可见闪动;
|
||||
// 无可读 CSRF cookie 说明必然是游客,跳过请求
|
||||
@@ -281,14 +295,9 @@ export default function Header({
|
||||
)}
|
||||
{user && <MobileLink href="/settings">账号设置</MobileLink>}
|
||||
{user?.role === "admin" && (
|
||||
<>
|
||||
<MobileLink href="/admin/announcements">
|
||||
<span className="inline-flex items-center gap-2"><Megaphone size={15} /> 公告管理</span>
|
||||
</MobileLink>
|
||||
<MobileLink href="/admin/appearance">
|
||||
<span className="inline-flex items-center gap-2"><Palette size={15} /> 外观设置</span>
|
||||
</MobileLink>
|
||||
</>
|
||||
<MobileLink href="/admin">
|
||||
<span className="inline-flex items-center gap-2"><ShieldCheck size={15} /> 管理面板</span>
|
||||
</MobileLink>
|
||||
)}
|
||||
{!user && <MobileLink href="/login">登录</MobileLink>}
|
||||
{!user && <MobileLink href="/register">注册</MobileLink>}
|
||||
@@ -449,11 +458,8 @@ function UserMenu({ user, onLogout }: { user: User; onLogout: () => void }) {
|
||||
</div>
|
||||
{user.role === "admin" && (
|
||||
<div className="p-1.5" style={{ borderTop: "1px solid var(--line)" }}>
|
||||
<Link href="/admin/announcements" onClick={() => setOpen(false)} className={itemCls} role="menuitem">
|
||||
<Megaphone size={15} style={{ color: "var(--accent)" }} /> 公告管理
|
||||
</Link>
|
||||
<Link href="/admin/appearance" onClick={() => setOpen(false)} className={itemCls} role="menuitem">
|
||||
<Palette size={15} style={{ color: "var(--accent)" }} /> 外观设置
|
||||
<Link href="/admin" onClick={() => setOpen(false)} className={itemCls} role="menuitem">
|
||||
<ShieldCheck size={15} style={{ color: "var(--accent)" }} /> 管理面板
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user