"use client"; import { useEffect, useMemo, useState } from "react"; import { Building2, KeyRound, Loader2, Pencil, Plus, RefreshCw, ShieldCheck, Trash2, UserPlus } from "lucide-react"; import { AccountSecurityPanel } from "@/components/account-security-panel"; type Role = "super_admin" | "organization_admin" | "user"; type Status = "active" | "disabled"; type Organization = { id: string; name: string; status: Status }; type Member = { id: string; phone: string; displayName: string; role: Role; organizationId: string | null; status: Status; createdAt: string; lastLoginAt: string | null; lockedUntil: string | null; }; type AccountPayload = { currentOrganizationId: string | null; organizations: Organization[]; members: Member[]; canManageOrganizations: boolean; canAssignOrganizationAdmin: boolean; }; type AccountManagerProps = { canManageAccounts: boolean; }; const roleLabels: Record = { super_admin: "超级管理员", organization_admin: "组织管理员", user: "普通用户" }; export function AccountManager({ canManageAccounts }: AccountManagerProps) { const [payload, setPayload] = useState(null); const [loading, setLoading] = useState(canManageAccounts); const [saving, setSaving] = useState(false); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const [selectedOrganizationId, setSelectedOrganizationId] = useState(""); const [form, setForm] = useState({ phone: "", displayName: "", password: "", role: "user" as Role }); const [organizationName, setOrganizationName] = useState(""); const selectedOrganization = useMemo( () => payload?.organizations.find((organization) => organization.id === selectedOrganizationId), [payload?.organizations, selectedOrganizationId] ); const members = payload?.members || []; async function loadAccounts(organizationId = selectedOrganizationId) { if (!canManageAccounts) return; setLoading(true); setError(null); try { const query = organizationId ? `?organizationId=${encodeURIComponent(organizationId)}` : ""; const response = await fetch(`/api/admin/accounts${query}`, { cache: "no-store" }); const next = await response.json() as AccountPayload & { error?: string }; if (!response.ok) throw new Error(next.error || "账号列表加载失败。"); setPayload(next); setSelectedOrganizationId(next.currentOrganizationId || ""); } catch (requestError) { setError(requestError instanceof Error ? requestError.message : String(requestError)); } finally { setLoading(false); } } useEffect(() => { if (canManageAccounts) void loadAccounts(); else setLoading(false); }, [canManageAccounts]); async function createUser(event: React.FormEvent) { event.preventDefault(); setSaving(true); setMessage(null); setError(null); try { const response = await fetch("/api/admin/accounts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ phone: form.phone, displayName: form.displayName, password: form.password, role: form.role, organizationId: selectedOrganization?.id }) }); const result = await response.json().catch(() => ({})) as { error?: string }; if (!response.ok) throw new Error(result.error || "账号创建失败。"); setMessage("账号已创建。"); setForm({ phone: "", displayName: "", password: "", role: "user" }); await loadAccounts(selectedOrganization?.id || ""); } catch (requestError) { setError(requestError instanceof Error ? requestError.message : String(requestError)); } finally { setSaving(false); } } async function createOrganization(event: React.FormEvent) { event.preventDefault(); if (!organizationName.trim()) return; setSaving(true); setMessage(null); setError(null); try { const response = await fetch("/api/admin/organizations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: organizationName }) }); const result = await response.json().catch(() => ({})) as { error?: string }; if (!response.ok) throw new Error(result.error || "组织创建失败。"); setOrganizationName(""); setMessage("组织已创建。"); await loadAccounts(); } catch (requestError) { setError(requestError instanceof Error ? requestError.message : String(requestError)); } finally { setSaving(false); } } async function renameOrganization(organization: Organization) { const name = window.prompt("请输入新的组织名称", organization.name)?.trim(); if (!name || name === organization.name) return; await mutateOrganization("PATCH", { organizationId: organization.id, name }, "组织名称已更新。"); } async function toggleOrganization(organization: Organization) { const status: Status = organization.status === "active" ? "disabled" : "active"; await mutateOrganization("PATCH", { organizationId: organization.id, status }, status === "active" ? "组织已启用。" : "组织已停用。登录和新账号创建会受到限制。"); } async function deleteOrganization(organization: Organization) { if (!window.confirm(`确定删除“${organization.name}”吗?只有没有任何账号的组织才可以删除。`)) return; await mutateOrganization("DELETE", { organizationId: organization.id }, "组织已删除。"); } async function mutateOrganization(method: string, body: Record, successMessage: string) { setError(null); setMessage(null); try { const response = await fetch("/api/admin/organizations", { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const result = await response.json().catch(() => ({})) as { error?: string }; if (!response.ok) throw new Error(result.error || "组织操作失败。"); setMessage(successMessage); if (method === "DELETE") { setSelectedOrganizationId(""); await loadAccounts(""); } else { await loadAccounts(selectedOrganizationId); } } catch (requestError) { setError(requestError instanceof Error ? requestError.message : String(requestError)); } } async function changeStatus(member: Member) { const nextStatus: Status = member.status === "active" ? "disabled" : "active"; await mutateMember("PUT", { userId: member.id, status: nextStatus }, nextStatus === "active" ? "账号已启用。" : "账号已停用。"); } async function resetPassword(member: Member) { const password = window.prompt(`为 ${member.displayName} 设置新密码(至少 8 位)`); if (!password) return; await mutateMember("POST", { userId: member.id, newPassword: password }, "密码已重置。", "/api/admin/accounts/password"); } async function changeRole(member: Member, nextRole: Role) { await mutateMember("PATCH", { userId: member.id, role: nextRole }, "账号角色已更新。"); } async function deleteMember(member: Member) { if (!window.confirm(`确定彻底删除“${member.displayName}”吗?素材和任务会转移到组织归档账号,用量记录保留。`)) return; await mutateMember("DELETE", { userId: member.id }, "账号已删除。"); } async function mutateMember(method: string, body: Record, successMessage: string, endpoint = "/api/admin/accounts") { setError(null); setMessage(null); try { const response = await fetch(endpoint, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const result = await response.json().catch(() => ({})) as { error?: string }; if (!response.ok) throw new Error(result.error || "操作失败。"); setMessage(successMessage); await loadAccounts(selectedOrganizationId); } catch (requestError) { setError(requestError instanceof Error ? requestError.message : String(requestError)); } } return (

账户与访问

{message ?
{message}
: null} {error ?
{error}
: null}
{canManageAccounts ? ( <>

组织与成员管理

创建账号

{payload?.canAssignOrganizationAdmin ? : null}
{payload?.canManageOrganizations ? (

组织目录

{payload.organizations.map((organization) => (
{organization.name}{organization.status === "active" ? "启用" : "停用"}
))}
) : null}

{selectedOrganization?.name || "全平台账号"}

{loading ? : members.length ? (
账号角色状态操作
{members.map((member) => (
{member.displayName}{member.phone}
{payload?.canAssignOrganizationAdmin ? : {roleLabels[member.role]}}
{member.status === "active" ? "启用" : "停用"}
))}
) :
当前组织还没有账号。
}
) : null}
); } function AccountLoading() { return (
正在加载账号
); } function accountInitial(value: string) { return value.trim().slice(0, 2) || "账"; }