import { useCallback, useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { downloadCsv } from "@/shared/api/download"; import { toPageQuery } from "@/shared/api/query"; import { parseForm } from "@/shared/forms/validation"; import { useAdminQuery } from "@/shared/hooks/useAdminQuery.js"; import { mergePaginatedItems } from "@/shared/hooks/usePaginatedQuery.js"; import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx"; import { useToast } from "@/shared/ui/ToastProvider.jsx"; import { listRoles } from "@/features/roles/api"; import { batchUpdateUserStatus, createUser, exportUsers, listUsers, resetUserPassword, updateUser, updateUserStatus, } from "@/features/users/api"; import { useUserAbilities } from "@/features/users/permissions.js"; import { userCreateFormSchema, userUpdateFormSchema } from "@/features/users/schema"; const pageSize = 50; const emptyUsers = { items: [], total: 0, page: 1, pageSize }; const emptyData = { roles: [], users: emptyUsers }; const emptyForm = { mfaEnabled: false, name: "", password: "", roleIds: [], status: "active", team: "", username: "", }; export function useUsersPage() { const [query, setQuery] = useState(""); const [roleFilter, setRoleFilter] = useState(""); const [statusFilter, setStatusFilter] = useState(""); const [page, setPage] = useState(1); const [selectedIds, setSelectedIds] = useState([]); const [formOpen, setFormOpen] = useState(false); const [editingUser, setEditingUser] = useState(null); const [profileUser, setProfileUser] = useState(null); const [form, setForm] = useState(emptyForm); const [mergedUsers, setMergedUsers] = useState(emptyUsers); const abilities = useUserAbilities(); const confirm = useConfirm(); const { showToast } = useToast(); const navigate = useNavigate(); const requestQuery = useMemo( () => toPageQuery({ keyword: query, page, pageSize, role: roleFilter, status: statusFilter }), [page, query, roleFilter, statusFilter], ); const queryFn = useCallback(async () => { const [userData, roleData] = await Promise.all([ listUsers(requestQuery), abilities.canViewRoles ? listRoles() : [], ]); setSelectedIds([]); return { roles: roleData || [], users: userData || emptyUsers }; }, [abilities.canViewRoles, requestQuery]); const { data, error, loading: queryLoading, reload } = useAdminQuery(queryFn, { errorMessage: "加载用户失败", initialData: emptyData, keepPreviousData: true, queryKey: ["users", requestQuery, abilities.canViewRoles], }); useEffect(() => { setMergedUsers(emptyUsers); }, [query, roleFilter, statusFilter]); useEffect(() => { const nextUsers = data?.users || emptyUsers; const nextItems = Array.isArray(nextUsers.items) ? nextUsers.items : []; const nextPage = Number(nextUsers.page || page || 1); const nextPageSize = Number(nextUsers.pageSize || pageSize); const nextTotal = Number(nextUsers.total || 0); setMergedUsers((current) => { if (nextPage <= 1) { return { ...nextUsers, items: nextItems, page: nextPage, pageSize: nextPageSize, total: nextTotal }; } return { ...nextUsers, items: mergePaginatedItems(current.items || [], nextItems), page: nextPage, pageSize: nextPageSize, total: nextTotal, }; }); }, [data?.users, page]); const users = mergedUsers; const loading = queryLoading && (users.items || []).length === 0; const roles = useMemo(() => data?.roles || [], [data?.roles]); const roleFilters = useMemo(() => [["", "全部角色"], ...roles.map((role) => [role.name, role.name])], [roles]); const stats = useMemo(() => { const items = users.items || []; const active = items.filter((user) => user.status === "active").length; const locked = items.filter((user) => user.status === "locked").length; const admin = items.filter((user) => String(user.role).includes("管理员")).length; return { active, admin, locked }; }, [users.items]); const changeQuery = (value) => { setQuery(value); setPage(1); }; const changeRoleFilter = (value) => { setRoleFilter(value); setPage(1); }; const changeStatusFilter = (value) => { setStatusFilter(value); setPage(1); }; const resetFilters = () => { setQuery(""); setRoleFilter(""); setStatusFilter(""); setPage(1); }; const openCreate = () => { setEditingUser(null); setForm(emptyForm); setFormOpen(true); }; const openEdit = (user) => { setEditingUser(user); setForm({ mfaEnabled: user.mfaEnabled, name: user.name, password: "", roleIds: user.roleIds || [], status: user.status, team: user.team || "", username: user.account, }); setFormOpen(true); }; const closeForm = () => { setFormOpen(false); }; const openProfile = (user) => { setProfileUser(user); }; const closeProfile = () => { setProfileUser(null); }; const submitUser = async (event) => { event.preventDefault(); try { if (editingUser) { const payload = parseForm(userUpdateFormSchema, form); await updateUser(editingUser.id, payload); showToast("用户已更新", "success"); } else { const payload = parseForm(userCreateFormSchema, form); const result = await createUser(payload); showToast(`用户已创建,初始密码:${result.initialPassword}`, "success"); } setFormOpen(false); reload(); } catch (err) { showToast(err.message || "保存用户失败", "error"); } }; const toggleLocked = async (user) => { const nextStatus = user.status === "locked" ? "active" : "locked"; const ok = await confirm({ confirmText: user.status === "locked" ? "解锁" : "锁定", message: `${user.name} 的登录状态会立即变更。`, title: user.status === "locked" ? "解锁用户" : "锁定用户", tone: user.status === "locked" ? "primary" : "danger", }); if (!ok) { return; } await updateUserStatus(user.id, nextStatus); showToast("用户状态已更新", "success"); reload(); }; const toggleDisabled = async (user) => { const nextStatus = user.status === "disabled" ? "active" : "disabled"; const ok = await confirm({ confirmText: user.status === "disabled" ? "启用" : "停用", message: `${user.name} 的账号状态会立即变更。`, title: user.status === "disabled" ? "启用用户" : "停用用户", tone: user.status === "disabled" ? "primary" : "danger", }); if (!ok) { return; } await updateUserStatus(user.id, nextStatus); showToast("用户状态已更新", "success"); reload(); }; const resetPassword = async (user) => { const ok = await confirm({ confirmText: "重置", message: `${user.name} 的密码会重置为系统生成的初始密码。`, title: "重置密码", tone: "danger", }); if (!ok) { return; } const result = await resetUserPassword(user.id); showToast(`已重置,初始密码:${result.initialPassword}`, "success"); }; const batchStatus = async (status) => { if (!selectedIds.length) { showToast("请先选择用户", "warning"); return; } const ok = await confirm({ confirmText: "确认", message: `将更新 ${selectedIds.length} 个用户状态。`, title: "批量更新状态", tone: status === "active" ? "primary" : "danger", }); if (!ok) { return; } await batchUpdateUserStatus(selectedIds, status); showToast("批量状态已更新", "success"); reload(); }; const downloadUsers = () => { return downloadCsv( () => exportUsers({ keyword: query, role: roleFilter, status: statusFilter }), "hyapp-users.csv", ); }; const setRoleChecked = (roleId, checked) => { setForm((current) => ({ ...current, roleIds: checked ? [...current.roleIds, roleId] : current.roleIds.filter((id) => id !== roleId), })); }; return { abilities, batchStatus, changeQuery, changeRoleFilter, changeStatusFilter, closeForm, closeProfile, downloadUsers, editingUser, error, form, formOpen, loading, navigate, openCreate, openEdit, openProfile, page, profileUser, query, reload, resetPassword, resetFilters, roleFilter, roleFilters, roles, selectedIds, setForm, setPage, setRoleChecked, setSelectedIds, stats, statusFilter, submitUser, toggleDisabled, toggleLocked, users, }; }