"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Ban, ChevronDown, FolderSearch, RotateCcw, Shield, SlidersHorizontal, Users, } from "lucide-react"; import { apiAdminSetUserBan, apiListBoards, apiMe, type Board, type User, } from "@/lib/api"; import { isOwner, isSuperOrOwner, ROLES } from "@/lib/roles"; import { toast } from "@/lib/toast"; import ConfirmDialog from "@/components/ConfirmDialog"; import RoleAccountModal, { type RoleAccountTarget } from "@/components/RoleAccountModal"; export interface StaffUserTarget { id: number; username: string; nickname: string; role: string; board_ids?: number[]; /** 有权访客资料接口下发;未知时按未封禁处理 */ banned?: boolean; } /** * 超管/站长对用户的前台快捷菜单:封禁、改角色、内容档案。 * 权限仅控显隐;写操作以后端为准。 */ export default function StaffUserMenu({ target, compact = false, onBannedChange, }: { target: StaffUserTarget; /** 作者栏等窄位用更小按钮 */ compact?: boolean; /** 封禁态变化回调(主页徽章等) */ onBannedChange?: (banned: boolean) => void; }) { const router = useRouter(); const menuRef = useRef(null); const [me, setMe] = useState(null); const [open, setOpen] = useState(false); const [banned, setBanned] = useState(!!target.banned); const [confirmBan, setConfirmBan] = useState(false); const [roleOpen, setRoleOpen] = useState(false); const [boards, setBoards] = useState([]); const [acting, setActing] = useState(false); useEffect(() => { apiMe() .then((res) => setMe(res.user || null)) .catch(() => setMe(null)); }, []); useEffect(() => { setBanned(!!target.banned); }, [target.banned, target.id]); useEffect(() => { if (!open) return; const onDown = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false); }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("mousedown", onDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); }; }, [open]); const canManage = !!me && isSuperOrOwner(me.role) && me.id !== target.id && target.role !== ROLES.OWNER; if (!canManage) return null; const openRoleModal = () => { setOpen(false); setRoleOpen(true); if (boards.length === 0) { apiListBoards() .then((res) => setBoards(res.boards ?? [])) .catch(() => toast("板块列表加载失败")); } }; const handleBanConfirm = async () => { const next = !banned; setActing(true); try { await apiAdminSetUserBan(target.id, next); setBanned(next); onBannedChange?.(next); setConfirmBan(false); toast(next ? "已封禁" : "已解封", "ok"); router.refresh(); } catch (e) { toast(e instanceof Error ? e.message : "操作失败"); } finally { setActing(false); } }; const roleTarget: RoleAccountTarget = { id: target.id, username: target.username, nickname: target.nickname, role: target.role, board_ids: target.board_ids, banned, }; const itemCls = "w-full flex items-center gap-2.5 px-4 py-2.5 text-[13px] transition-colors hover:bg-[var(--panel-2)] focus-visible:bg-[var(--panel-2)]"; return (
{open && (
{isOwner(me.role) && ( setOpen(false)} > 内容档案 )} setOpen(false)} > 后台用户列表
)} setConfirmBan(false)} /> setRoleOpen(false)} onSaved={(next) => { setBanned(next.banned); onBannedChange?.(next.banned); setRoleOpen(false); router.refresh(); }} setActing={setActing} />
); }