import { useEffect, useState } from "react"; import { Plus, Search } from "lucide-react"; import { ApiRequestError, type Employee, type Role } from "../../services/api"; import { useApi, useAuth } from "../../context/AuthContext"; import { AppFormDialog, dialogPrimaryButtonClass, dialogSecondaryButtonClass, } from "~/components/common/AppFormDialog"; import { ConfirmDialog } from "~/components/common/ConfirmDialog"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { AdminColgroup, USER_PROJECT_COL_WIDTHS } from "./AdminTable"; import { formFieldClass, formInputClass, modalFormClass, } from "../platform/modalUi"; import "../../styles/admin.css"; const EMPTY_FORM = { username: "", display_name: "", email: "", role_code: "developer" as "admin" | "developer", password: "", status: "active" as "active" | "disabled" | "locked", }; const PRIMARY_BUTTON_CLASS = "h-[37px] gap-[7px] rounded-md border border-[#126ac3] bg-gradient-to-br from-[#1881e7] to-[#1268c9] px-[15px] text-xs font-[650] text-white shadow-[0_6px_15px_rgb(20_111_202/18%)] enabled:hover:from-[#1175d8] enabled:hover:to-[#0e5bad]"; const FORM_INPUT_CLASS = "h-[39px] w-full rounded-md border border-[#d6e0e9] bg-white px-[11px] text-xs text-[#283a4e] outline-none focus:border-[#5b9ddb] focus:shadow-[0_0_0_3px_rgb(38_125_207/8%)] disabled:cursor-not-allowed disabled:opacity-50"; const ROW_BUTTON_CLASS = "h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#4c6c88] hover:bg-[#f7fafc] disabled:cursor-not-allowed disabled:opacity-[0.45]"; const ROW_DANGER_BUTTON_CLASS = "h-6 gap-1 rounded-[4px] border-[#d9e3ec] bg-white px-[9px] text-[11px] text-[#c14a4a] hover:bg-[#fdf2f2] disabled:cursor-not-allowed disabled:opacity-[0.45]"; export function UserManagementPage({ onNotify, onConnectionChange, }: { onNotify: (notice: { tone: "success" | "error" | "info"; message: string }) => void; onConnectionChange: (online: boolean) => void; }) { const api = useApi(); const { user } = useAuth(); const [employees, setEmployees] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [editing, setEditing] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [form, setForm] = useState(EMPTY_FORM); const [userSearchTerm, setUserSearchTerm] = useState(""); const [deleteTarget, setDeleteTarget] = useState(null); const canManage = user?.role_code === "admin"; const load = async (): Promise => { setLoading(true); try { const [employeeList, roleList] = await Promise.all([ api.listPlatformEmployees(), api.listPlatformRoles(), ]); setEmployees(employeeList); setRoles(roleList); onConnectionChange(true); } catch (error) { onConnectionChange(false); onNotify({ tone: "error", message: error instanceof Error ? error.message : "用户列表加载失败", }); } finally { setLoading(false); } }; useEffect(() => { void load(); }, []); const openCreate = (): void => { setEditing(null); setForm(EMPTY_FORM); setDialogOpen(true); }; const openEdit = (employee: Employee): void => { setEditing(employee); setForm({ username: employee.username, display_name: employee.display_name, email: employee.email ?? "", role_code: employee.role_code, password: "", status: employee.status, }); setDialogOpen(true); }; const submit = async (event: React.FormEvent): Promise => { event.preventDefault(); if (!form.display_name.trim() || (!editing && !form.username.trim())) return; // 新建用户时密码必填 if (!editing && !form.password.trim()) { onNotify({ tone: "error", message: "请输入密码" }); return; } // 编辑自身时,前端二次拦截禁用 status / role_code 的修改;管理员账号禁止停用/锁定 if (editing) { const editingSelf = editing.user_id === user?.user_id; const targetIsAdmin = editing.role_code === "admin"; if (editingSelf && form.status !== editing.status) { onNotify({ tone: "error", message: "不能停用当前登录账号" }); return; } if (targetIsAdmin && form.status !== editing.status) { onNotify({ tone: "error", message: "不能停用或锁定管理员账号" }); return; } if (editingSelf && form.role_code !== editing.role_code) { onNotify({ tone: "error", message: "不能降级自身管理员角色" }); return; } } // 密码长度校验 8~72 字符 if (form.password && (form.password.length < 8 || form.password.length > 72)) { onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" }); return; } setSaving(true); try { if (editing) { const updated = await api.updatePlatformEmployee(editing.user_id, { display_name: form.display_name.trim(), email: form.email.trim() || null, role_code: form.role_code, status: form.status, }); setEmployees((current) => current.map( (item) => item.user_id === updated.user_id ? updated : item, )); onNotify({ tone: "success", message: "用户信息已更新" }); } else { const created = await api.createPlatformEmployee({ username: form.username.trim(), display_name: form.display_name.trim(), email: form.email.trim() || undefined, password: form.password, role_code: "developer", }); setEmployees((current) => [...current, created]); onNotify({ tone: "success", message: "用户已添加" }); } setDialogOpen(false); } catch (error) { onNotify({ tone: "error", message: error instanceof ApiRequestError ? error.message : "保存用户失败", }); } finally { setSaving(false); } }; const executeRemove = async (employee: Employee): Promise => { try { await api.deletePlatformEmployee(employee.user_id); setEmployees((current) => current.filter((item) => item.user_id !== employee.user_id)); onNotify({ tone: "success", message: "用户已删除" }); } catch (error) { onNotify({ tone: "error", message: error instanceof Error ? error.message : "删除用户失败", }); } }; return (
setUserSearchTerm(event.target.value)} />
{!canManage &&
当前为开发人员,只能查看用户列表。
} 用户 账号 角色 状态 操作 {loading ? (

正在加载用户…

) : ( employees .filter((employee) => { const term = userSearchTerm.toLowerCase().trim(); if (!term) return true; return ( employee.display_name.toLowerCase().includes(term) || employee.username.toLowerCase().includes(term) || (employee.email && employee.email.toLowerCase().includes(term)) ); }) .map((employee) => { const isProtectedAdmin = employee.role_code === "admin"; return ( {employee.display_name.slice(0, 1)}{employee.display_name}{employee.email ?? "未设置邮箱"} {employee.username} {employee.role_code ? ({employee.role_name}) : (-)} {employee.status === "active" ? "正常" : employee.status === "disabled" ? "已停用" : "已锁定"} ); }) )}
{ if (!nextOpen) setDialogOpen(false); }} eyebrow="EMPLOYEE" title={editing ? "编辑用户" : "添加用户"} >
void submit(event)}> {!editing && ( )} {editing && ( )} {editing && ( )}
{ if (!nextOpen) setDeleteTarget(null); }} title="确定删除用户?" description={ deleteTarget ? `确定从平台删除用户"${deleteTarget.display_name}"吗?` : "" } confirmLabel="删除" destructive onConfirm={async () => { if (!deleteTarget) return; await executeRemove(deleteTarget); setDeleteTarget(null); }} />
); }