import { resolveErrorMessage as resolveUserErrorMessage } from "../lib/resolveErrorMessage"; import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { fetchDepartments } from "../api/departments"; import { fetchEmployees } from "../api/employees"; import { createUser, disableUser, enableUser, fetchUsers, resetUserPassword, setUserRoles, type UserStatus, type UserSummary, } from "../api/users"; import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table"; import { toast } from "sonner"; import { TableSkeleton } from "../components/common/TableSkeleton"; import { StatusBadge } from "../components/common/StatusBadge"; import { Pagination } from "../components/common/Pagination"; import { CreateUserDialog, type CreateUserFormData } from "./user-management/CreateUserDialog"; import { UserImportDialog } from "./user-management/UserImportDialog"; import { UserRoleDialog } from "./user-management/UserRoleDialog"; import { useAuth } from "../auth/AuthContext"; const PAGE_SIZE = 20; const ROLE_LABELS: Record = { employee: "员工", manager: "直属经理", ceo: "公司负责人", finance: "财务", system_admin: "OA 系统管理员", finance_mgr: "财务负责人(兼容)", administration: "行政(兼容)", hr: "人事(兼容)", admin: "应急超级管理员", }; const ORDINARY_ROLE_OPTIONS = [ "employee", "manager", "ceo", "finance", "system_admin", "finance_mgr", "administration", "hr", ] as const; function formatRoleCodes(roleCodes: string[]) { return roleCodes.length ? roleCodes.map((roleCode) => ROLE_LABELS[roleCode] ?? roleCode).join("、") : "-"; } function isProtectedUserAccount(user: UserSummary) { return user.roleCodes.some((roleCode) => roleCode === "admin" || roleCode === "system_admin"); } export function UserManagementPage() { const { me } = useAuth(); const queryClient = useQueryClient(); const isEmergencyAdmin = (me?.roleCodes ?? []).includes("admin"); const roleOptions = ORDINARY_ROLE_OPTIONS.map((code) => ({ code, label: ROLE_LABELS[code], disabled: code === "system_admin" && !isEmergencyAdmin, })); const [status, setStatus] = useState(""); const [q, setQ] = useState(""); const [page, setPage] = useState(1); const [error, setError] = useState(""); const [credentialResult, setCredentialResult] = useState<{ mode: "create" | "reset"; userId: string; employeeNo?: string; password: string; } | null>(null); const [createOpen, setCreateOpen] = useState(false); const [importOpen, setImportOpen] = useState(false); const [roleDialogUser, setRoleDialogUser] = useState(null); const listQuery = useQuery({ queryKey: ["users", "list", status, q, page], queryFn: () => fetchUsers({ page, pageSize: PAGE_SIZE, status: status || undefined, q: q || undefined }), }); const departmentsQuery = useQuery({ queryKey: ["departments", "active"], queryFn: () => fetchDepartments({ includeArchived: false }), }); const managersQuery = useQuery({ queryKey: ["employees", "active"], queryFn: () => fetchEmployees({ page: 1, pageSize: 200, status: "active" }), }); const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE)); const users = listQuery.data?.items ?? []; const userStats = { total: listQuery.data?.total ?? 0, active: users.filter((item) => item.status === "active").length, archived: users.filter((item) => item.status === "archived").length, roles: new Set(users.flatMap((item) => item.roleCodes)).size, }; const createMutation = useMutation({ mutationFn: (payload: CreateUserFormData) => createUser(payload), onSuccess: async (data) => { await queryClient.invalidateQueries({ queryKey: ["users"] }); setCreateOpen(false); setError(""); setCredentialResult({ mode: "create", userId: data.id, employeeNo: data.employeeNo, password: data.oneTimePassword, }); toast.success("用户已创建"); }, onError: (err) => { const msg = resolveUserErrorMessage(err, "创建用户失败"); setError(msg); toast.error(msg); }, }); const toggleMutation = useMutation({ mutationFn: async (item: UserSummary) => { if (item.status === "active") { return disableUser(item.id); } return enableUser(item.id); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["users"] }); setError(""); toast.success("状态已更新"); }, onError: (err) => { const msg = resolveUserErrorMessage(err, "更新用户状态失败"); setError(msg); toast.error(msg); }, }); const resetMutation = useMutation({ mutationFn: async (item: UserSummary) => resetUserPassword(item.id), onSuccess: async (data) => { setCredentialResult({ mode: "reset", userId: data.id, password: data.oneTimePassword }); await queryClient.invalidateQueries({ queryKey: ["users"] }); setError(""); toast.success("密码已重置"); }, onError: (err) => { const msg = resolveUserErrorMessage(err, "重置密码失败"); setError(msg); toast.error(msg); }, }); const rolesMutation = useMutation({ mutationFn: async ({ userId, roles }: { userId: string; roles: string[] }) => { return setUserRoles(userId, roles); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["users"] }); setRoleDialogUser(null); setError(""); toast.success("角色已更新"); }, onError: (err) => { const msg = resolveUserErrorMessage(err, "更新角色失败"); setError(msg); toast.error(msg); }, }); return ( { await Promise.all([ queryClient.invalidateQueries({ queryKey: ["users"] }), queryClient.invalidateQueries({ queryKey: ["employees"] }), ]); }} /> createMutation.mutate(data)} isPending={createMutation.isPending} departments={{ isLoading: departmentsQuery.isLoading, items: departmentsQuery.data?.items ?? [], }} managers={{ isLoading: managersQuery.isLoading, items: (managersQuery.data?.items ?? []).map((item) => ({ id: item.id, name: item.name, employeeNo: item.employeeNo, })), }} roleOptions={roleOptions} /> } /> {error ? (

{error}

) : null} {credentialResult ? (

{credentialResult.mode === "create" ? ( <> 已创建用户(user={credentialResult.userId} {credentialResult.employeeNo ? `,工号=${credentialResult.employeeNo}` : ""} ),一次性口令:{credentialResult.password} ) : ( <> 已重置密码(user={credentialResult.userId}),一次性口令:{credentialResult.password} )}

) : null}
用户总数
{userStats.total}
当前页生效
{userStats.active}
当前页停用
{userStats.archived}
当前页角色
{userStats.roles}
当前页
{page}
{ setQ(e.target.value); setPage(1); }} placeholder="搜索工号/姓名/手机号/邮箱" />
{listQuery.isLoading ? : null} {listQuery.isError ? (

{resolveUserErrorMessage(listQuery.error, "加载用户列表失败")}

) : null}
工号/姓名联系方式状态角色操作 {users.length ? ( users.map((item) => { const protectedAccount = isProtectedUserAccount(item); const canManageAccount = isEmergencyAdmin || !protectedAccount; const isCurrentAccount = item.id === me?.id; const canToggleAccount = canManageAccount && !isCurrentAccount; const canResetPassword = canManageAccount; const canEditRoles = canManageAccount && !isCurrentAccount; return (
{item.employeeNo} / {item.name}
{item.id}
{item.email}
{item.phone ?? "手机号未填写"}
{formatRoleCodes(item.roleCodes)}
{canToggleAccount ? ( ) : null} {canResetPassword ? ( ) : null} {canEditRoles ? ( ) : null} {protectedAccount && !isEmergencyAdmin ? ( 受保护账号 ) : null}
); }) ) : ( 暂无数据 )}
{ if (!open) { setRoleDialogUser(null); } }} user={roleDialogUser} roleOptions={roleOptions} onSubmit={(roles) => { if (roleDialogUser) { rolesMutation.mutate({ userId: roleDialogUser.id, roles }); } }} isPending={rolesMutation.isPending} />
); }