From b6d8af0fa3ffaa8ce8b8b66733f4788929bf11b4 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 24 Jun 2026 15:50:08 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=8F=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/host-org/api.test.ts | 2 + .../host-org/hooks/useHostManagersPage.js | 22 ++++ .../host-org/pages/HostManagersPage.jsx | 16 +-- .../host-org/pages/HostManagersPage.test.jsx | 103 ++++++++++++++++++ src/shared/api/types.ts | 1 + 5 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 src/features/host-org/pages/HostManagersPage.test.jsx diff --git a/src/features/host-org/api.test.ts b/src/features/host-org/api.test.ts index b6a51ad..db07449 100644 --- a/src/features/host-org/api.test.ts +++ b/src/features/host-org/api.test.ts @@ -67,6 +67,7 @@ test("host org list APIs use generated admin paths and filters", async () => { canGrantVehicle: true, canTransferUserCountry: false, contact: "+63", + status: "disabled", }); await listHosts({ agency_id: 41, page: 1, page_size: 10, sort_by: "diamond", sort_direction: "desc" }); await listCoinSellers({ @@ -149,6 +150,7 @@ test("host org list APIs use generated admin paths and filters", async () => { canGrantVehicle: true, canTransferUserCountry: false, contact: "+63", + status: "disabled", }); expect(String(hostUrl)).toContain("/api/v1/admin/hosts?"); expect(String(hostUrl)).toContain("agency_id=41"); diff --git a/src/features/host-org/hooks/useHostManagersPage.js b/src/features/host-org/hooks/useHostManagersPage.js index 991a0db..3f4b6c5 100644 --- a/src/features/host-org/hooks/useHostManagersPage.js +++ b/src/features/host-org/hooks/useHostManagersPage.js @@ -28,6 +28,12 @@ const emptyManagerForm = () => ({ contact: "", targetUserId: "", ...emptyPermiss const managerPermissionPayload = (form) => Object.fromEntries(managerPermissionFields.map((field) => [field, form[field] !== false])); +const managerStatusPayload = (item, status) => ({ + contact: item?.contact || "", + status, + ...Object.fromEntries(managerPermissionFields.map((field) => [field, item?.[field] !== false])), +}); + const managerFormFromItem = (item) => ({ contact: item?.contact || "", targetUserId: String(item?.userId || ""), @@ -139,6 +145,21 @@ export function useHostManagersPage() { setActiveAction("manager-edit"); }; + const toggleManager = async (item, enabled) => { + const nextStatus = enabled ? "active" : "disabled"; + setLoadingAction(`manager-status-${item.userId}`); + try { + // 状态接口复用经理更新入口;提交当前行的权限快照,避免只改状态时把已有权限开关覆盖成后端默认值。 + await updateManager(item.userId, managerStatusPayload(item, nextStatus)); + showToast(enabled ? "经理已启用" : "经理已停用", "success"); + await reload(); + } catch (err) { + showToast(err.message || (enabled ? "启用经理失败" : "停用经理失败"), "error"); + } finally { + setLoadingAction(""); + } + }; + return { abilities, activeAction, @@ -165,5 +186,6 @@ export function useHostManagersPage() { setPage, status, submitManager, + toggleManager, }; } diff --git a/src/features/host-org/pages/HostManagersPage.jsx b/src/features/host-org/pages/HostManagersPage.jsx index 738fa2c..d428c4a 100644 --- a/src/features/host-org/pages/HostManagersPage.jsx +++ b/src/features/host-org/pages/HostManagersPage.jsx @@ -6,7 +6,7 @@ import { formatMillis } from "@/shared/utils/time.js"; import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx"; import { DataState } from "@/shared/ui/DataState.jsx"; import { IconButton } from "@/shared/ui/IconButton.jsx"; -import { managerStatusFilters, statusLabels } from "@/features/host-org/constants.js"; +import { managerStatusFilters } from "@/features/host-org/constants.js"; import { HostOrgActionModal } from "@/features/host-org/components/HostOrgActionModal.jsx"; import { HostOrgPerson, hostOrgRegionName } from "@/features/host-org/components/HostOrgIdentity.jsx"; import { HostOrgTable } from "@/features/host-org/components/HostOrgTable.jsx"; @@ -55,7 +55,7 @@ const managerColumns = [ { key: "status", label: "状态", - render: (item) => , + render: (item, _index, context) => , width: "minmax(90px, 0.7fr)", }, { @@ -187,12 +187,14 @@ function ManagerUser({ item }) { ); } -function ManagerStatus({ item }) { - const active = item.status === "active"; +function ManagerStatusSwitch({ item, page }) { return ( - - {statusLabels[item.status] || item.status || "-"} - + page.toggleManager(item, event.target.checked)} + /> ); } diff --git a/src/features/host-org/pages/HostManagersPage.test.jsx b/src/features/host-org/pages/HostManagersPage.test.jsx new file mode 100644 index 0000000..52d0a56 --- /dev/null +++ b/src/features/host-org/pages/HostManagersPage.test.jsx @@ -0,0 +1,103 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, expect, test, vi } from "vitest"; +import { HostManagersPage } from "./HostManagersPage.jsx"; +import { useHostManagersPage } from "@/features/host-org/hooks/useHostManagersPage.js"; + +const mocks = vi.hoisted(() => ({ + toggleManager: vi.fn(), +})); + +vi.mock("@/features/host-org/hooks/useHostManagersPage.js", () => ({ + useHostManagersPage: vi.fn(), +})); + +afterEach(() => { + vi.clearAllMocks(); +}); + +test("manager status column uses a switch and toggles through the page hook", () => { + const manager = managerFixture(); + vi.mocked(useHostManagersPage).mockReturnValue(pageFixture({ data: { items: [manager], page: 1, pageSize: 50, total: 1 } })); + + render(); + + const statusSwitch = screen.getByRole("switch", { name: "停用经理" }); + expect(statusSwitch).toBeChecked(); + + fireEvent.click(statusSwitch); + + expect(mocks.toggleManager).toHaveBeenCalledWith(manager, false); +}); + +function pageFixture(patch = {}) { + return { + abilities: { + canCreate: true, + canUpdate: true, + canView: true, + }, + activeAction: "", + changeQuery: vi.fn(), + changeRegionId: vi.fn(), + changeStatus: vi.fn(), + closeAction: vi.fn(), + data: { items: [], page: 1, pageSize: 50, total: 0 }, + editingManager: null, + error: null, + loading: false, + loadingAction: "", + loadingRegions: false, + managerForm: { + canAddAdmin: true, + canAddBdLeader: true, + canAddSuperadmin: true, + canBlockUser: true, + canGrantAvatarFrame: true, + canGrantBadge: true, + canGrantVehicle: true, + canTransferUserCountry: true, + canUpdateUserLevel: true, + contact: "", + targetUserId: "", + }, + openManagerEdit: vi.fn(), + openManagerForm: vi.fn(), + page: 1, + query: "", + regionId: "", + regionOptions: [], + reload: vi.fn(), + resetFilters: vi.fn(), + setManagerForm: vi.fn(), + setPage: vi.fn(), + status: "", + submitManager: vi.fn(), + toggleManager: mocks.toggleManager, + ...patch, + }; +} + +function managerFixture() { + return { + avatar: "", + bdLeaderCount: 3, + canAddAdmin: true, + canAddBdLeader: false, + canAddSuperadmin: true, + canBlockUser: true, + canGrantAvatarFrame: true, + canGrantBadge: true, + canGrantVehicle: true, + canTransferUserCountry: true, + canUpdateUserLevel: true, + contact: "+63 910 323 3670", + displayUserId: "167579", + lastInvitedAtMs: 1782198308000, + regionId: 1, + regionName: "欧亚美区", + status: "active", + updatedAtMs: 1782284840000, + userId: "10001", + username: "PH-MANAGER", + }; +} diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts index 793f9c2..fac3286 100644 --- a/src/shared/api/types.ts +++ b/src/shared/api/types.ts @@ -607,6 +607,7 @@ export interface UpdateManagerPayload { canTransferUserCountry?: boolean; canUpdateUserLevel?: boolean; contact?: string; + status?: "active" | "disabled"; } export interface HostProfileDto {