"use client"; import { PlusOutlined } from "@ant-design/icons"; import { useGetIdentity } from "@refinedev/core"; import { useTable } from "@refinedev/antd"; import { App, Button, Card, Form, Input, Modal, Select, Space, Table, Tag, Typography } from "antd"; import { useState } from "react"; import { adminRequestJson, type AdminIdentity } from "@/lib/admin/providers"; import { ReasonActionModal } from "./reason-action-modal"; import { formatAdminDate } from "./resource-table"; const roleOptions = [ { value: "owner", label: "Owner" }, { value: "model_admin", label: "Model Admin" }, { value: "billing_admin", label: "Billing Admin" }, { value: "operations", label: "Operations" }, { value: "support", label: "Support" }, { value: "auditor", label: "Auditor" }, ] as const; type RoleCode = (typeof roleOptions)[number]["value"]; interface Administrator { id: string; email: string; name: string; roles: RoleCode[]; createdAt: string; } type AssignmentForm = { email: string; roleCode: RoleCode }; type PendingRoleAction = { action: "assign" | "revoke"; roleCode: RoleCode; userId?: string; email?: string; label: string; }; export default function AdministratorsResource() { const { message } = App.useApp(); const { data: identity } = useGetIdentity(); const { tableProps, searchFormProps, tableQuery } = useTable({ resource: "administrators", syncWithLocation: true, pagination: { pageSize: 20 }, onSearch: ({ q }) => [{ field: "q", operator: "contains", value: q }], }); const [assignmentForm] = Form.useForm(); const [assignmentOpen, setAssignmentOpen] = useState(false); const [assignmentUser, setAssignmentUser] = useState(null); const [pendingAction, setPendingAction] = useState(null); const [saving, setSaving] = useState(false); const canManage = Boolean(identity?.permissions.includes("admin.users.manage_roles")); function openAssignment(item?: Administrator) { setAssignmentUser(item ?? null); assignmentForm.setFieldsValue({ email: item?.email ?? "", roleCode: undefined }); setAssignmentOpen(true); } function prepareAssignment(values: AssignmentForm) { const role = roleOptions.find((item) => item.value === values.roleCode)!; setPendingAction({ action: "assign", roleCode: values.roleCode, ...(assignmentUser ? { userId: assignmentUser.id } : { email: values.email.trim() }), label: `${assignmentUser?.email ?? values.email.trim()} · ${role.label}`, }); } async function submitRoleAction(reason: string) { if (!pendingAction) return; setSaving(true); try { await adminRequestJson("/api/admin/administrators", { method: pendingAction.action === "assign" ? "POST" : "DELETE", body: JSON.stringify({ userId: pendingAction.userId, email: pendingAction.email, roleCode: pendingAction.roleCode, reason, }), }); message.success(pendingAction.action === "assign" ? "管理员角色已分配" : "管理员角色已撤销"); setPendingAction(null); setAssignmentOpen(false); setAssignmentUser(null); assignmentForm.resetFields(); await tableQuery.refetch(); } finally { setSaving(false); } } return ( } onClick={() => openAssignment()}>按邮箱分配角色 : RBAC} > 六类系统角色可在此分配和撤销。每次变更都需要邮箱验证码与操作原因;最后一位 Owner 受服务端保护,不能被撤销。
value || "—" }, { title: "角色", dataIndex: "roles", render: (roles: RoleCode[], item: Administrator) => {roles.map((role) => { event.preventDefault(); setPendingAction({ action: "revoke", roleCode: role, userId: item.id, label: `${item.email} · ${role}` }); }} >{role})}, }, { title: "加入时间", dataIndex: "createdAt", render: formatAdminDate }, ...(canManage ? [{ title: "操作", fixed: "right" as const, render: (_: unknown, item: Administrator) => }] : []), ]} /> assignmentForm.submit()} onCancel={() => setAssignmentOpen(false)} destroyOnHidden > form={assignmentForm} layout="vertical" onFinish={prepareAssignment}>