批量删除
This commit is contained in:
parent
f12b2767c0
commit
4874bba9ac
@ -15,7 +15,17 @@
|
||||
"operationId": "listAchievementDefinitions",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
"description": "批量创建的礼物",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-permission": "achievement:view",
|
||||
@ -25,7 +35,17 @@
|
||||
"operationId": "createAchievementDefinition",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
"description": "批量删除的资源",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-permission": "achievement:create",
|
||||
@ -2353,6 +2373,21 @@
|
||||
"x-permissions": ["gift:create"]
|
||||
}
|
||||
},
|
||||
"/admin/gifts/batch": {
|
||||
"post": {
|
||||
"operationId": "batchCreateGifts",
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/BatchCreateGiftsRequest"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "gift:create",
|
||||
"x-permissions": ["gift:create"]
|
||||
}
|
||||
},
|
||||
"/admin/gifts/{gift_id}": {
|
||||
"put": {
|
||||
"operationId": "updateGift",
|
||||
@ -4205,6 +4240,21 @@
|
||||
"x-permissions": ["resource:create"]
|
||||
}
|
||||
},
|
||||
"/admin/resources/batch-delete": {
|
||||
"post": {
|
||||
"operationId": "batchDeleteResources",
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/BatchDeleteResourcesRequest"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/EmptyResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "resource:delete",
|
||||
"x-permissions": ["resource:delete"]
|
||||
}
|
||||
},
|
||||
"/admin/resources/{resource_id}": {
|
||||
"get": {
|
||||
"operationId": "getResource",
|
||||
@ -5955,6 +6005,42 @@
|
||||
}
|
||||
},
|
||||
"requestBodies": {
|
||||
"BatchCreateGiftsRequest": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["items"],
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"BatchDeleteResourcesRequest": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["resourceIds"],
|
||||
"properties": {
|
||||
"resourceIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"BatchStatusRequest": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
import { setAccessToken } from "@/shared/api/request";
|
||||
import {
|
||||
batchCreateGifts,
|
||||
batchDeleteResources,
|
||||
createEmojiPack,
|
||||
createGift,
|
||||
createResource,
|
||||
@ -85,6 +87,7 @@ test("resource APIs use generated admin paths", async () => {
|
||||
status: "active",
|
||||
});
|
||||
await deleteResource(11);
|
||||
await batchDeleteResources([11, 12]);
|
||||
await listGifts({ page: 1, page_size: 10, region_id: 1001 });
|
||||
await createGift({
|
||||
chargeAssetType: "COIN",
|
||||
@ -104,6 +107,26 @@ test("resource APIs use generated admin paths", async () => {
|
||||
sortOrder: 0,
|
||||
status: "active",
|
||||
});
|
||||
await batchCreateGifts([
|
||||
{
|
||||
chargeAssetType: "COIN",
|
||||
coinPrice: 20,
|
||||
cpRelationType: "",
|
||||
effectiveAtMs: 1777766400000,
|
||||
effectiveFromMs: 0,
|
||||
effectiveToMs: 0,
|
||||
effectTypes: ["animation"],
|
||||
giftId: "ring",
|
||||
giftTypeCode: "normal",
|
||||
name: "Ring",
|
||||
presentationJson: "{}",
|
||||
priceVersion: "default",
|
||||
regionIds: [0],
|
||||
resourceId: 12,
|
||||
sortOrder: 1,
|
||||
status: "active",
|
||||
},
|
||||
]);
|
||||
await enableResource(11);
|
||||
await disableResource(11);
|
||||
await createResourceGroup({
|
||||
@ -180,23 +203,25 @@ test("resource APIs use generated admin paths", async () => {
|
||||
const [createUrl, createInit] = vi.mocked(fetch).mock.calls[1];
|
||||
const [updateUrl, updateInit] = vi.mocked(fetch).mock.calls[2];
|
||||
const [deleteResourceUrl, deleteResourceInit] = vi.mocked(fetch).mock.calls[3];
|
||||
const [giftListUrl, giftListInit] = vi.mocked(fetch).mock.calls[4];
|
||||
const [createGiftUrl, createGiftInit] = vi.mocked(fetch).mock.calls[5];
|
||||
const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[6];
|
||||
const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[7];
|
||||
const [createGroupUrl, createGroupInit] = vi.mocked(fetch).mock.calls[8];
|
||||
const [updateGroupUrl, updateGroupInit] = vi.mocked(fetch).mock.calls[9];
|
||||
const [enableGroupUrl, enableGroupInit] = vi.mocked(fetch).mock.calls[10];
|
||||
const [disableGroupUrl, disableGroupInit] = vi.mocked(fetch).mock.calls[11];
|
||||
const [updateGiftUrl, updateGiftInit] = vi.mocked(fetch).mock.calls[12];
|
||||
const [enableGiftUrl, enableGiftInit] = vi.mocked(fetch).mock.calls[13];
|
||||
const [disableGiftUrl, disableGiftInit] = vi.mocked(fetch).mock.calls[14];
|
||||
const [deleteGiftUrl, deleteGiftInit] = vi.mocked(fetch).mock.calls[15];
|
||||
const [grantListUrl, grantListInit] = vi.mocked(fetch).mock.calls[16];
|
||||
const [grantTargetUrl, grantTargetInit] = vi.mocked(fetch).mock.calls[17];
|
||||
const [grantResourceUrl, grantResourceInit] = vi.mocked(fetch).mock.calls[18];
|
||||
const [grantGroupUrl, grantGroupInit] = vi.mocked(fetch).mock.calls[19];
|
||||
const [revokeGrantUrl, revokeGrantInit] = vi.mocked(fetch).mock.calls[20];
|
||||
const [batchDeleteResourceUrl, batchDeleteResourceInit] = vi.mocked(fetch).mock.calls[4];
|
||||
const [giftListUrl, giftListInit] = vi.mocked(fetch).mock.calls[5];
|
||||
const [createGiftUrl, createGiftInit] = vi.mocked(fetch).mock.calls[6];
|
||||
const [batchCreateGiftUrl, batchCreateGiftInit] = vi.mocked(fetch).mock.calls[7];
|
||||
const [enableUrl, enableInit] = vi.mocked(fetch).mock.calls[8];
|
||||
const [disableUrl, disableInit] = vi.mocked(fetch).mock.calls[9];
|
||||
const [createGroupUrl, createGroupInit] = vi.mocked(fetch).mock.calls[10];
|
||||
const [updateGroupUrl, updateGroupInit] = vi.mocked(fetch).mock.calls[11];
|
||||
const [enableGroupUrl, enableGroupInit] = vi.mocked(fetch).mock.calls[12];
|
||||
const [disableGroupUrl, disableGroupInit] = vi.mocked(fetch).mock.calls[13];
|
||||
const [updateGiftUrl, updateGiftInit] = vi.mocked(fetch).mock.calls[14];
|
||||
const [enableGiftUrl, enableGiftInit] = vi.mocked(fetch).mock.calls[15];
|
||||
const [disableGiftUrl, disableGiftInit] = vi.mocked(fetch).mock.calls[16];
|
||||
const [deleteGiftUrl, deleteGiftInit] = vi.mocked(fetch).mock.calls[17];
|
||||
const [grantListUrl, grantListInit] = vi.mocked(fetch).mock.calls[18];
|
||||
const [grantTargetUrl, grantTargetInit] = vi.mocked(fetch).mock.calls[19];
|
||||
const [grantResourceUrl, grantResourceInit] = vi.mocked(fetch).mock.calls[20];
|
||||
const [grantGroupUrl, grantGroupInit] = vi.mocked(fetch).mock.calls[21];
|
||||
const [revokeGrantUrl, revokeGrantInit] = vi.mocked(fetch).mock.calls[22];
|
||||
|
||||
expect(String(listUrl)).toContain("/api/v1/admin/resources?");
|
||||
expect(String(listUrl)).toContain("resource_type=gift");
|
||||
@ -222,12 +247,18 @@ test("resource APIs use generated admin paths", async () => {
|
||||
});
|
||||
expect(String(deleteResourceUrl)).toContain("/api/v1/admin/resources/11");
|
||||
expect(deleteResourceInit?.method).toBe("DELETE");
|
||||
expect(String(batchDeleteResourceUrl)).toContain("/api/v1/admin/resources/batch-delete");
|
||||
expect(batchDeleteResourceInit?.method).toBe("POST");
|
||||
expect(JSON.parse(String(batchDeleteResourceInit?.body))).toEqual({ resourceIds: [11, 12] });
|
||||
expect(String(giftListUrl)).toContain("/api/v1/admin/gifts?");
|
||||
expect(String(giftListUrl)).toContain("region_id=1001");
|
||||
expect(giftListInit?.method).toBe("GET");
|
||||
expect(String(createGiftUrl)).toContain("/api/v1/admin/gifts");
|
||||
expect(createGiftInit?.method).toBe("POST");
|
||||
expect(JSON.parse(String(createGiftInit?.body))).toMatchObject({ giftId: "rose", resourceId: 11 });
|
||||
expect(String(batchCreateGiftUrl)).toContain("/api/v1/admin/gifts/batch");
|
||||
expect(batchCreateGiftInit?.method).toBe("POST");
|
||||
expect(JSON.parse(String(batchCreateGiftInit?.body))).toMatchObject({ items: [{ giftId: "ring", resourceId: 12 }] });
|
||||
expect(String(enableUrl)).toContain("/api/v1/admin/resources/11/enable");
|
||||
expect(enableInit?.method).toBe("POST");
|
||||
expect(String(disableUrl)).toContain("/api/v1/admin/resources/11/disable");
|
||||
|
||||
@ -224,6 +224,10 @@ export interface GiftPayload {
|
||||
regionIds: number[];
|
||||
}
|
||||
|
||||
export interface BatchCreateGiftsPayload {
|
||||
items: GiftPayload[];
|
||||
}
|
||||
|
||||
export interface GiftTypePayload {
|
||||
displayName: string;
|
||||
tabName: string;
|
||||
@ -337,6 +341,10 @@ export interface ResourceShopItemsPayload {
|
||||
items: ResourceShopItemPayload[];
|
||||
}
|
||||
|
||||
export interface BatchDeleteResourcesPayload {
|
||||
resourceIds: EntityId[];
|
||||
}
|
||||
|
||||
export function listResources(query: PageQuery = {}): Promise<ApiPage<ResourceDto>> {
|
||||
const endpoint = API_ENDPOINTS.listResources;
|
||||
return apiRequest<ApiPage<ResourceDto>>(apiEndpointPath(API_OPERATIONS.listResources), {
|
||||
@ -371,6 +379,17 @@ export function deleteResource(resourceId: EntityId): Promise<ResourceDto> {
|
||||
});
|
||||
}
|
||||
|
||||
export function batchDeleteResources(resourceIds: EntityId[]): Promise<ResourceDto[]> {
|
||||
const endpoint = API_ENDPOINTS.batchDeleteResources;
|
||||
return apiRequest<ResourceDto[], BatchDeleteResourcesPayload>(
|
||||
apiEndpointPath(API_OPERATIONS.batchDeleteResources),
|
||||
{
|
||||
body: { resourceIds },
|
||||
method: endpoint.method,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function updateResourceMp4Layouts(items: ResourceMp4LayoutUpdatePayload[]): Promise<ResourceDto[]> {
|
||||
return apiRequest<ResourceDto[], ResourceMp4LayoutsPayload>("/v1/admin/resources/mp4-layouts/batch", {
|
||||
body: { items },
|
||||
@ -548,6 +567,14 @@ export function createGift(payload: GiftPayload): Promise<GiftDto> {
|
||||
});
|
||||
}
|
||||
|
||||
export function batchCreateGifts(payloads: GiftPayload[]): Promise<GiftDto[]> {
|
||||
const endpoint = API_ENDPOINTS.batchCreateGifts;
|
||||
return apiRequest<GiftDto[], BatchCreateGiftsPayload>(apiEndpointPath(API_OPERATIONS.batchCreateGifts), {
|
||||
body: { items: payloads },
|
||||
method: endpoint.method,
|
||||
});
|
||||
}
|
||||
|
||||
export function updateGift(giftId: string, payload: GiftPayload): Promise<GiftDto> {
|
||||
const endpoint = API_ENDPOINTS.updateGift;
|
||||
return apiRequest<GiftDto, GiftPayload>(apiEndpointPath(API_OPERATIONS.updateGift, { gift_id: giftId }), {
|
||||
|
||||
117
src/features/resources/hooks/useGiftListPageBatchCreate.test.jsx
Normal file
117
src/features/resources/hooks/useGiftListPageBatchCreate.test.jsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
||||
import { useGiftListPage } from "./useResourcePages.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
batchCreateGifts: vi.fn(),
|
||||
can: vi.fn(),
|
||||
createGift: vi.fn(),
|
||||
listGifts: vi.fn(),
|
||||
listGiftTypes: vi.fn(),
|
||||
listResources: vi.fn(),
|
||||
reload: vi.fn(),
|
||||
showToast: vi.fn(),
|
||||
usePaginatedQuery: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/auth/AuthProvider.jsx", () => ({
|
||||
useAuth: () => ({ can: mocks.can }),
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/hooks/usePaginatedQuery.js", () => ({
|
||||
usePaginatedQuery: mocks.usePaginatedQuery,
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/hooks/useRegionOptions.js", () => ({
|
||||
useRegionOptions: () => ({ loadingRegions: false, regionOptions: [{ label: "全球", value: "0" }] }),
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({
|
||||
useConfirm: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/shared/ui/ToastProvider.jsx", () => ({
|
||||
useToast: () => ({ showToast: mocks.showToast }),
|
||||
}));
|
||||
|
||||
vi.mock("@/features/resources/api", async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
batchCreateGifts: mocks.batchCreateGifts,
|
||||
createGift: mocks.createGift,
|
||||
listGifts: mocks.listGifts,
|
||||
listGiftTypes: mocks.listGiftTypes,
|
||||
listResources: mocks.listResources,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.can.mockReturnValue(true);
|
||||
mocks.batchCreateGifts.mockResolvedValue([]);
|
||||
mocks.createGift.mockResolvedValue({});
|
||||
mocks.listGifts.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
|
||||
mocks.listGiftTypes.mockResolvedValue([]);
|
||||
mocks.listResources.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
|
||||
mocks.reload.mockResolvedValue(undefined);
|
||||
mocks.usePaginatedQuery.mockReturnValue({
|
||||
data: { items: [], page: 1, pageSize: 50, total: 0 },
|
||||
error: null,
|
||||
loading: false,
|
||||
reload: mocks.reload,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("creating multiple gift drafts calls the batch create endpoint once", async () => {
|
||||
const { result } = renderHook(() => useGiftListPage());
|
||||
|
||||
await act(async () => {
|
||||
result.current.openCreateGift();
|
||||
});
|
||||
await waitFor(() => expect(result.current.activeAction).toBe("create"));
|
||||
|
||||
await act(async () => {
|
||||
result.current.setForm({
|
||||
...giftDraft({ giftId: "rose", name: "Rose", resourceId: "11" }),
|
||||
items: [
|
||||
giftDraft({ giftId: "rose", name: "Rose", resourceId: "11" }),
|
||||
giftDraft({ giftId: "ring", name: "Ring", resourceId: "12" }),
|
||||
],
|
||||
resourceId: "11",
|
||||
});
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitGift({ preventDefault: vi.fn() });
|
||||
});
|
||||
|
||||
expect(mocks.batchCreateGifts).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.batchCreateGifts).toHaveBeenCalledWith([
|
||||
expect.objectContaining({ giftId: "rose", name: "Rose", resourceId: 11 }),
|
||||
expect.objectContaining({ giftId: "ring", name: "Ring", resourceId: 12 }),
|
||||
]);
|
||||
expect(mocks.createGift).not.toHaveBeenCalled();
|
||||
expect(mocks.reload).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
function giftDraft(patch = {}) {
|
||||
return {
|
||||
chargeAssetType: "COIN",
|
||||
coinPrice: "10",
|
||||
cpRelationType: "",
|
||||
effectTypes: ["animation"],
|
||||
effectiveFrom: "",
|
||||
effectiveTo: "",
|
||||
enabled: true,
|
||||
giftId: "gift",
|
||||
giftTypeCode: "normal",
|
||||
name: "Gift",
|
||||
presentationJson: "{}",
|
||||
priceVersion: "default",
|
||||
regionIds: ["0"],
|
||||
resourceId: "1",
|
||||
sortOrder: "0",
|
||||
...patch,
|
||||
};
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { afterEach, beforeEach, expect, test, vi } from "vitest";
|
||||
import { useResourceListPage } from "./useResourcePages.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
batchDeleteResources: vi.fn(),
|
||||
can: vi.fn(),
|
||||
confirm: vi.fn(),
|
||||
deleteResource: vi.fn(),
|
||||
@ -29,12 +30,14 @@ vi.mock("@/shared/ui/ToastProvider.jsx", () => ({
|
||||
|
||||
vi.mock("@/features/resources/api", async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
batchDeleteResources: mocks.batchDeleteResources,
|
||||
deleteResource: mocks.deleteResource,
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.can.mockReturnValue(true);
|
||||
mocks.confirm.mockResolvedValue(true);
|
||||
mocks.batchDeleteResources.mockResolvedValue([]);
|
||||
mocks.deleteResource.mockResolvedValue({});
|
||||
mocks.reload.mockResolvedValue(undefined);
|
||||
mocks.usePaginatedQuery.mockReturnValue({
|
||||
@ -76,14 +79,14 @@ test("batch resource delete hides successful rows without reloading the list", a
|
||||
});
|
||||
|
||||
expect(deletedResourceIds).toEqual([11, 12]);
|
||||
expect(mocks.deleteResource).toHaveBeenNthCalledWith(1, 11);
|
||||
expect(mocks.deleteResource).toHaveBeenNthCalledWith(2, 12);
|
||||
expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12]);
|
||||
expect(mocks.deleteResource).not.toHaveBeenCalled();
|
||||
expect(mocks.reload).not.toHaveBeenCalled();
|
||||
expect(result.current.data.items).toEqual([]);
|
||||
expect(mocks.showToast).toHaveBeenCalledWith("已删除 2 个资源", "success");
|
||||
});
|
||||
|
||||
test("batch resource delete continues after an item fails and hides successful rows", async () => {
|
||||
test("failed batch resource delete keeps all rows visible", async () => {
|
||||
mocks.usePaginatedQuery.mockReturnValue({
|
||||
data: {
|
||||
items: [
|
||||
@ -99,9 +102,7 @@ test("batch resource delete continues after an item fails and hides successful r
|
||||
loading: false,
|
||||
reload: mocks.reload,
|
||||
});
|
||||
mocks.deleteResource.mockImplementation((resourceId) =>
|
||||
resourceId === 12 ? Promise.reject(new Error("delete failed")) : Promise.resolve({}),
|
||||
);
|
||||
mocks.batchDeleteResources.mockRejectedValueOnce(new Error("delete failed"));
|
||||
const { result } = renderHook(() => useResourceListPage());
|
||||
|
||||
let deletedResourceIds = [];
|
||||
@ -113,12 +114,11 @@ test("batch resource delete continues after an item fails and hides successful r
|
||||
]);
|
||||
});
|
||||
|
||||
expect(deletedResourceIds).toEqual([11, 13]);
|
||||
expect(mocks.deleteResource).toHaveBeenNthCalledWith(1, 11);
|
||||
expect(mocks.deleteResource).toHaveBeenNthCalledWith(2, 12);
|
||||
expect(mocks.deleteResource).toHaveBeenNthCalledWith(3, 13);
|
||||
expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([12]);
|
||||
expect(mocks.showToast).toHaveBeenCalledWith("已删除 2 个资源,1 个删除失败:delete failed", "error");
|
||||
expect(deletedResourceIds).toEqual([]);
|
||||
expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12, 13]);
|
||||
expect(mocks.deleteResource).not.toHaveBeenCalled();
|
||||
expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([11, 12, 13]);
|
||||
expect(mocks.showToast).toHaveBeenCalledWith("delete failed", "error");
|
||||
});
|
||||
|
||||
test("failed resource delete keeps the row visible", async () => {
|
||||
|
||||
@ -6,6 +6,8 @@ import { useConfirm } from "@/shared/ui/ConfirmProvider.jsx";
|
||||
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
||||
import { emptyUserIdentityFilter, userIdentityFilterParams } from "@/shared/ui/userIdentityFilter.js";
|
||||
import {
|
||||
batchCreateGifts,
|
||||
batchDeleteResources as requestBatchDeleteResources,
|
||||
createEmojiPack,
|
||||
createGift,
|
||||
createResource,
|
||||
@ -593,32 +595,23 @@ export function useResourceListPage() {
|
||||
}
|
||||
|
||||
const deletedResourceIds = [];
|
||||
const failedDeletes = [];
|
||||
setLoadingAction(
|
||||
targetResources.length > 1 ? "resource-delete-batch" : `resource-delete-${targetResources[0].resourceId}`,
|
||||
);
|
||||
try {
|
||||
for (const resource of targetResources) {
|
||||
try {
|
||||
await requestDeleteResource(resource.resourceId);
|
||||
deletedResourceIds.push(resource.resourceId);
|
||||
} catch (err) {
|
||||
failedDeletes.push({ err, resource });
|
||||
}
|
||||
if (targetResources.length > 1) {
|
||||
await requestBatchDeleteResources(targetResources.map((resource) => resource.resourceId));
|
||||
deletedResourceIds.push(...targetResources.map((resource) => resource.resourceId));
|
||||
} else {
|
||||
await requestDeleteResource(targetResources[0].resourceId);
|
||||
deletedResourceIds.push(targetResources[0].resourceId);
|
||||
}
|
||||
hideResourceRows(deletedResourceIds);
|
||||
if (failedDeletes.length) {
|
||||
const firstError = failedDeletes[0].err?.message || "删除资源失败";
|
||||
showToast(
|
||||
deletedResourceIds.length
|
||||
? `已删除 ${deletedResourceIds.length} 个资源,${failedDeletes.length} 个删除失败:${firstError}`
|
||||
: firstError,
|
||||
"error",
|
||||
);
|
||||
return deletedResourceIds;
|
||||
}
|
||||
showToast(targetResources.length > 1 ? `已删除 ${deletedResourceIds.length} 个资源` : "资源已删除", "success");
|
||||
return deletedResourceIds;
|
||||
} catch (err) {
|
||||
showToast(err.message || "删除资源失败", "error");
|
||||
return [];
|
||||
} finally {
|
||||
setLoadingAction("");
|
||||
}
|
||||
@ -1510,9 +1503,12 @@ export function useGiftListPage() {
|
||||
draft,
|
||||
payload: buildGiftPayload(parseForm(giftFormSchema, draft)),
|
||||
}));
|
||||
for (const { draft, payload } of payloads) {
|
||||
await createGift(payload);
|
||||
createdDrafts.push(draft);
|
||||
if (payloads.length > 1) {
|
||||
await batchCreateGifts(payloads.map(({ payload }) => payload));
|
||||
createdDrafts.push(...payloads.map(({ draft }) => draft));
|
||||
} else {
|
||||
await createGift(payloads[0].payload);
|
||||
createdDrafts.push(payloads[0].draft);
|
||||
}
|
||||
}
|
||||
showToast(editing ? "礼物已更新" : `已创建 ${createDrafts.length} 个礼物`, "success");
|
||||
|
||||
@ -26,6 +26,8 @@ export const API_OPERATIONS = {
|
||||
appSetPassword: "appSetPassword",
|
||||
appUnbanUser: "appUnbanUser",
|
||||
appUpdateUser: "appUpdateUser",
|
||||
batchCreateGifts: "batchCreateGifts",
|
||||
batchDeleteResources: "batchDeleteResources",
|
||||
batchUpdateUserStatus: "batchUpdateUserStatus",
|
||||
cancelRoomPin: "cancelRoomPin",
|
||||
changePassword: "changePassword",
|
||||
@ -436,6 +438,20 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
permission: "app-user:update",
|
||||
permissions: ["app-user:update"]
|
||||
},
|
||||
batchCreateGifts: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.batchCreateGifts,
|
||||
path: "/v1/admin/gifts/batch",
|
||||
permission: "gift:create",
|
||||
permissions: ["gift:create"]
|
||||
},
|
||||
batchDeleteResources: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.batchDeleteResources,
|
||||
path: "/v1/admin/resources/batch-delete",
|
||||
permission: "resource:delete",
|
||||
permissions: ["resource:delete"]
|
||||
},
|
||||
batchUpdateUserStatus: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.batchUpdateUserStatus,
|
||||
|
||||
90
src/shared/api/generated/schema.d.ts
vendored
90
src/shared/api/generated/schema.d.ts
vendored
@ -1620,6 +1620,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/gifts/batch": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["batchCreateGifts"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/gifts/{gift_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -2660,6 +2676,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/resources/batch-delete": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["batchDeleteResources"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/resources/{resource_id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -4601,6 +4633,20 @@ export interface components {
|
||||
Status: string;
|
||||
};
|
||||
requestBodies: {
|
||||
BatchCreateGiftsRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
items: Record<string, never>[];
|
||||
};
|
||||
};
|
||||
};
|
||||
BatchDeleteResourcesRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
resourceIds: number[];
|
||||
};
|
||||
};
|
||||
};
|
||||
BatchStatusRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
@ -4729,7 +4775,15 @@ export interface operations {
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
/** @description 批量创建的礼物 */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": Record<string, never>[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
createAchievementDefinition: {
|
||||
@ -4741,7 +4795,15 @@ export interface operations {
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
/** @description 批量删除的资源 */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": Record<string, never>[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
updateAchievementDefinition: {
|
||||
@ -6631,6 +6693,18 @@ export interface operations {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
batchCreateGifts: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: components["requestBodies"]["BatchCreateGiftsRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
updateGift: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -7873,6 +7947,18 @@ export interface operations {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
batchDeleteResources: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: components["requestBodies"]["BatchDeleteResourcesRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["EmptyResponse"];
|
||||
};
|
||||
};
|
||||
getResource: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user