礼物资源
This commit is contained in:
parent
216031f776
commit
08872009e1
@ -817,6 +817,38 @@
|
|||||||
"x-permissions": ["bd:create"]
|
"x-permissions": ["bd:create"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/managers/{user_id}": {
|
||||||
|
"put": {
|
||||||
|
"operationId": "updateManager",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "user_id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpdateManagerPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/ManagerResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-permission": "bd:update",
|
||||||
|
"x-permissions": ["bd:update"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/agencies": {
|
"/admin/agencies": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "listAgencies",
|
"operationId": "listAgencies",
|
||||||
@ -7189,6 +7221,36 @@
|
|||||||
"bdLeaderCount": {
|
"bdLeaderCount": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"canAddAdmin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canAddBdLeader": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canAddSuperadmin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canBlockUser": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantAvatarFrame": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantBadge": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantVehicle": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantVip": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canTransferUserCountry": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canUpdateUserLevel": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"createdAtMs": {
|
"createdAtMs": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
@ -7226,6 +7288,36 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["targetUserId"],
|
"required": ["targetUserId"],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"canAddAdmin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canAddBdLeader": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canAddSuperadmin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canBlockUser": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantAvatarFrame": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantBadge": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantVehicle": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantVip": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canTransferUserCountry": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canUpdateUserLevel": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"contact": {
|
"contact": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"maxLength": 128
|
"maxLength": 128
|
||||||
@ -7235,6 +7327,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"UpdateManagerPayload": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"canAddAdmin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canAddBdLeader": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canAddSuperadmin": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canBlockUser": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantAvatarFrame": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantBadge": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantVehicle": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canGrantVip": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canTransferUserCountry": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"canUpdateUserLevel": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["active", "disabled"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"BDProfile": {
|
"BDProfile": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["userId"],
|
"required": ["userId"],
|
||||||
|
|||||||
@ -62,10 +62,11 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
region_id: 7,
|
region_id: 7,
|
||||||
status: "active",
|
status: "active",
|
||||||
});
|
});
|
||||||
await createManager({ canBlockUser: false, canGrantBadge: false, contact: "+63", targetUserId: "1003" });
|
await createManager({ canBlockUser: false, canGrantBadge: false, canGrantVip: true, contact: "+63", targetUserId: "1003" });
|
||||||
await updateManager("1003", {
|
await updateManager("1003", {
|
||||||
canBlockUser: false,
|
canBlockUser: false,
|
||||||
canGrantVehicle: true,
|
canGrantVehicle: true,
|
||||||
|
canGrantVip: false,
|
||||||
canTransferUserCountry: false,
|
canTransferUserCountry: false,
|
||||||
contact: "+63",
|
contact: "+63",
|
||||||
status: "disabled",
|
status: "disabled",
|
||||||
@ -147,6 +148,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
expect(JSON.parse(String(managerCreateInit?.body))).toEqual({
|
expect(JSON.parse(String(managerCreateInit?.body))).toEqual({
|
||||||
canBlockUser: false,
|
canBlockUser: false,
|
||||||
canGrantBadge: false,
|
canGrantBadge: false,
|
||||||
|
canGrantVip: true,
|
||||||
contact: "+63",
|
contact: "+63",
|
||||||
targetUserId: "1003",
|
targetUserId: "1003",
|
||||||
});
|
});
|
||||||
@ -155,6 +157,7 @@ test("host org list APIs use generated admin paths and filters", async () => {
|
|||||||
expect(JSON.parse(String(managerUpdateInit?.body))).toEqual({
|
expect(JSON.parse(String(managerUpdateInit?.body))).toEqual({
|
||||||
canBlockUser: false,
|
canBlockUser: false,
|
||||||
canGrantVehicle: true,
|
canGrantVehicle: true,
|
||||||
|
canGrantVip: false,
|
||||||
canTransferUserCountry: false,
|
canTransferUserCountry: false,
|
||||||
contact: "+63",
|
contact: "+63",
|
||||||
status: "disabled",
|
status: "disabled",
|
||||||
|
|||||||
@ -191,9 +191,10 @@ export function createManager(payload: CreateManagerPayload): Promise<ManagerDto
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function updateManager(userId: EntityId, payload: UpdateManagerPayload): Promise<ManagerDto> {
|
export function updateManager(userId: EntityId, payload: UpdateManagerPayload): Promise<ManagerDto> {
|
||||||
return apiRequest<ManagerDto, UpdateManagerPayload>(`/v1/admin/managers/${encodeURIComponent(String(userId))}`, {
|
const endpoint = API_ENDPOINTS.updateManager;
|
||||||
|
return apiRequest<ManagerDto, UpdateManagerPayload>(apiEndpointPath(API_OPERATIONS.updateManager, { user_id: userId }), {
|
||||||
body: payload,
|
body: payload,
|
||||||
method: "PUT",
|
method: endpoint.method,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export const managerPermissionFields = [
|
|||||||
"canGrantAvatarFrame",
|
"canGrantAvatarFrame",
|
||||||
"canGrantVehicle",
|
"canGrantVehicle",
|
||||||
"canGrantBadge",
|
"canGrantBadge",
|
||||||
|
"canGrantVip",
|
||||||
"canUpdateUserLevel",
|
"canUpdateUserLevel",
|
||||||
"canAddBdLeader",
|
"canAddBdLeader",
|
||||||
"canAddAdmin",
|
"canAddAdmin",
|
||||||
|
|||||||
@ -19,6 +19,7 @@ const managerPermissionOptions = [
|
|||||||
{ key: "canGrantAvatarFrame", label: "头像框" },
|
{ key: "canGrantAvatarFrame", label: "头像框" },
|
||||||
{ key: "canGrantVehicle", label: "Car" },
|
{ key: "canGrantVehicle", label: "Car" },
|
||||||
{ key: "canGrantBadge", label: "徽章" },
|
{ key: "canGrantBadge", label: "徽章" },
|
||||||
|
{ key: "canGrantVip", label: "VIP" },
|
||||||
{ key: "canUpdateUserLevel", label: "升级等级" },
|
{ key: "canUpdateUserLevel", label: "升级等级" },
|
||||||
{ key: "canAddBdLeader", label: "BD Leader" },
|
{ key: "canAddBdLeader", label: "BD Leader" },
|
||||||
{ key: "canAddAdmin", label: "Admin" },
|
{ key: "canAddAdmin", label: "Admin" },
|
||||||
|
|||||||
@ -21,6 +21,8 @@ test("manager status column uses a switch and toggles through the page hook", ()
|
|||||||
|
|
||||||
render(<HostManagersPage />);
|
render(<HostManagersPage />);
|
||||||
|
|
||||||
|
expect(screen.getByText("VIP")).toBeInTheDocument();
|
||||||
|
|
||||||
const statusSwitch = screen.getByRole("switch", { name: "停用经理" });
|
const statusSwitch = screen.getByRole("switch", { name: "停用经理" });
|
||||||
expect(statusSwitch).toBeChecked();
|
expect(statusSwitch).toBeChecked();
|
||||||
|
|
||||||
@ -55,6 +57,7 @@ function pageFixture(patch = {}) {
|
|||||||
canGrantAvatarFrame: true,
|
canGrantAvatarFrame: true,
|
||||||
canGrantBadge: true,
|
canGrantBadge: true,
|
||||||
canGrantVehicle: true,
|
canGrantVehicle: true,
|
||||||
|
canGrantVip: true,
|
||||||
canTransferUserCountry: true,
|
canTransferUserCountry: true,
|
||||||
canUpdateUserLevel: true,
|
canUpdateUserLevel: true,
|
||||||
contact: "",
|
contact: "",
|
||||||
@ -88,6 +91,7 @@ function managerFixture() {
|
|||||||
canGrantAvatarFrame: true,
|
canGrantAvatarFrame: true,
|
||||||
canGrantBadge: true,
|
canGrantBadge: true,
|
||||||
canGrantVehicle: true,
|
canGrantVehicle: true,
|
||||||
|
canGrantVip: true,
|
||||||
canTransferUserCountry: true,
|
canTransferUserCountry: true,
|
||||||
canUpdateUserLevel: true,
|
canUpdateUserLevel: true,
|
||||||
contact: "+63 910 323 3670",
|
contact: "+63 910 323 3670",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
import { agencyCloseSchema, agencyJoinEnabledSchema, coinSellerStatusSchema } from "./schema";
|
import { agencyCloseSchema, agencyJoinEnabledSchema, coinSellerStatusSchema, createManagerSchema } from "./schema";
|
||||||
|
|
||||||
test("coin seller contact accepts plus sign with large internal user id", () => {
|
test("coin seller contact accepts plus sign with large internal user id", () => {
|
||||||
const result = coinSellerStatusSchema.safeParse({
|
const result = coinSellerStatusSchema.safeParse({
|
||||||
@ -40,3 +40,17 @@ test("agency action schemas preserve large agency ids as strings", () => {
|
|||||||
expect(joinResult.data.agencyId).toBe(agencyId);
|
expect(joinResult.data.agencyId).toBe(agencyId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("manager schema defaults vip grant permission on and preserves explicit false", () => {
|
||||||
|
const defaultResult = createManagerSchema.safeParse({ targetUserId: "1003" });
|
||||||
|
const disabledResult = createManagerSchema.safeParse({ canGrantVip: false, targetUserId: "1003" });
|
||||||
|
|
||||||
|
expect(defaultResult.success).toBe(true);
|
||||||
|
expect(disabledResult.success).toBe(true);
|
||||||
|
if (defaultResult.success) {
|
||||||
|
expect(defaultResult.data.canGrantVip).toBe(true);
|
||||||
|
}
|
||||||
|
if (disabledResult.success) {
|
||||||
|
expect(disabledResult.data.canGrantVip).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -116,6 +116,7 @@ export const createManagerSchema = z.object({
|
|||||||
canGrantAvatarFrame: z.boolean().optional().default(true),
|
canGrantAvatarFrame: z.boolean().optional().default(true),
|
||||||
canGrantBadge: z.boolean().optional().default(true),
|
canGrantBadge: z.boolean().optional().default(true),
|
||||||
canGrantVehicle: z.boolean().optional().default(true),
|
canGrantVehicle: z.boolean().optional().default(true),
|
||||||
|
canGrantVip: z.boolean().optional().default(true),
|
||||||
canTransferUserCountry: z.boolean().optional().default(true),
|
canTransferUserCountry: z.boolean().optional().default(true),
|
||||||
canUpdateUserLevel: z.boolean().optional().default(true),
|
canUpdateUserLevel: z.boolean().optional().default(true),
|
||||||
contact: z.string().trim().max(128, "联系方式不能超过 128 个字符").optional().default(""),
|
contact: z.string().trim().max(128, "联系方式不能超过 128 个字符").optional().default(""),
|
||||||
|
|||||||
@ -21,6 +21,7 @@ const drawerZIndex = 1600;
|
|||||||
export function GiftResourceSelectField({
|
export function GiftResourceSelectField({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
drawerTitle = "选择资源",
|
drawerTitle = "选择资源",
|
||||||
|
excludedResourceIds = [],
|
||||||
giftTypes = [],
|
giftTypes = [],
|
||||||
gifts = [],
|
gifts = [],
|
||||||
label = "资源",
|
label = "资源",
|
||||||
@ -36,6 +37,8 @@ export function GiftResourceSelectField({
|
|||||||
const [activeType, setActiveType] = useState("");
|
const [activeType, setActiveType] = useState("");
|
||||||
const selectedIds = useMemo(() => selectedResourceIds.map((id) => String(id)), [selectedResourceIds]);
|
const selectedIds = useMemo(() => selectedResourceIds.map((id) => String(id)), [selectedResourceIds]);
|
||||||
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
||||||
|
const excludedIds = useMemo(() => excludedResourceIds.map((id) => String(id)), [excludedResourceIds]);
|
||||||
|
const excludedIdSet = useMemo(() => new Set(excludedIds), [excludedIds]);
|
||||||
const giftsByResourceId = useMemo(() => {
|
const giftsByResourceId = useMemo(() => {
|
||||||
const items = new Map();
|
const items = new Map();
|
||||||
gifts.forEach((gift) => {
|
gifts.forEach((gift) => {
|
||||||
@ -49,10 +52,19 @@ export function GiftResourceSelectField({
|
|||||||
() => buildResourceItems(resources, giftsByResourceId),
|
() => buildResourceItems(resources, giftsByResourceId),
|
||||||
[giftsByResourceId, resources],
|
[giftsByResourceId, resources],
|
||||||
);
|
);
|
||||||
const itemCounts = useMemo(() => countResourceItems(resourceItems), [resourceItems]);
|
const visibleResourceItems = useMemo(
|
||||||
|
() =>
|
||||||
|
resourceItems.filter((item) => {
|
||||||
|
const resourceId = String(item.resource.resourceId);
|
||||||
|
return !excludedIdSet.has(resourceId) || selectedIdSet.has(resourceId);
|
||||||
|
}),
|
||||||
|
[excludedIdSet, resourceItems, selectedIdSet],
|
||||||
|
);
|
||||||
|
const visibleResources = useMemo(() => visibleResourceItems.map((item) => item.resource), [visibleResourceItems]);
|
||||||
|
const itemCounts = useMemo(() => countResourceItems(visibleResourceItems), [visibleResourceItems]);
|
||||||
const categoryOptions = useMemo(
|
const categoryOptions = useMemo(
|
||||||
() => buildCategoryOptions(giftTypes, resources, resourceItems),
|
() => buildCategoryOptions(giftTypes, visibleResources, visibleResourceItems),
|
||||||
[giftTypes, resourceItems, resources],
|
[giftTypes, visibleResourceItems, visibleResources],
|
||||||
);
|
);
|
||||||
const activeCategory = categoryOptions.some((item) => item.value === activeType)
|
const activeCategory = categoryOptions.some((item) => item.value === activeType)
|
||||||
? activeType
|
? activeType
|
||||||
@ -76,7 +88,7 @@ export function GiftResourceSelectField({
|
|||||||
}, [resourcesById, selectedIds]);
|
}, [resourcesById, selectedIds]);
|
||||||
const filteredResources = useMemo(() => {
|
const filteredResources = useMemo(() => {
|
||||||
const keyword = query.trim().toLowerCase();
|
const keyword = query.trim().toLowerCase();
|
||||||
return resourceItems.filter((item) => {
|
return visibleResourceItems.filter((item) => {
|
||||||
if (activeCategory && item.categoryValue !== activeCategory) {
|
if (activeCategory && item.categoryValue !== activeCategory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -99,7 +111,7 @@ export function GiftResourceSelectField({
|
|||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
return haystack.includes(keyword);
|
return haystack.includes(keyword);
|
||||||
});
|
});
|
||||||
}, [activeCategory, query, resourceItems]);
|
}, [activeCategory, query, visibleResourceItems]);
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
if (!disabled && !loading) {
|
if (!disabled && !loading) {
|
||||||
|
|||||||
@ -117,3 +117,71 @@ test("gift resource select drawer filters by gift type and supports multiple sel
|
|||||||
expect(changes.at(-1)).toEqual(["11", "12"]);
|
expect(changes.at(-1)).toEqual(["11", "12"]);
|
||||||
expect(screen.getByDisplayValue("Rose Gift,CP Ring")).toBeInTheDocument();
|
expect(screen.getByDisplayValue("Rose Gift,CP Ring")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("gift resource select drawer hides excluded gift resources from candidates", () => {
|
||||||
|
function Harness() {
|
||||||
|
const [value, setValue] = useState([]);
|
||||||
|
return (
|
||||||
|
<GiftResourceSelectField
|
||||||
|
excludedResourceIds={[11]}
|
||||||
|
giftTypes={giftTypes}
|
||||||
|
gifts={gifts}
|
||||||
|
resources={resources}
|
||||||
|
selectedResourceIds={value}
|
||||||
|
onChange={setValue}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<Harness />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByPlaceholderText("请选择资源"));
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "普通礼物" }));
|
||||||
|
|
||||||
|
expect(screen.queryByText("Rose Gift")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText("当前无可选资源")).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "CP礼物" }));
|
||||||
|
expect(screen.getByText("CP Ring")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("gift resource select drawer hides excluded resources but keeps selected resources removable", () => {
|
||||||
|
const changes = [];
|
||||||
|
|
||||||
|
function Harness() {
|
||||||
|
const [value, setValue] = useState(["11"]);
|
||||||
|
return (
|
||||||
|
<GiftResourceSelectField
|
||||||
|
excludedResourceIds={[11, 21]}
|
||||||
|
giftTypes={giftTypes}
|
||||||
|
gifts={gifts}
|
||||||
|
resources={resources}
|
||||||
|
selectedResourceIds={value}
|
||||||
|
onChange={(nextValue) => {
|
||||||
|
changes.push(nextValue);
|
||||||
|
setValue(nextValue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<Harness />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByPlaceholderText("请选择资源"));
|
||||||
|
|
||||||
|
expect(screen.getByText("Rose Gift")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("VIP Vehicle")).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "CP礼物" }));
|
||||||
|
fireEvent.click(screen.getByText("CP Ring"));
|
||||||
|
|
||||||
|
expect(changes.at(-1)).toEqual(["11", "12"]);
|
||||||
|
expect(screen.getByDisplayValue("Rose Gift,CP Ring")).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "普通礼物" }));
|
||||||
|
fireEvent.click(screen.getByText("Rose Gift"));
|
||||||
|
|
||||||
|
expect(changes.at(-1)).toEqual(["12"]);
|
||||||
|
expect(screen.queryByText("Rose Gift")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("CP Ring")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|||||||
@ -163,6 +163,7 @@ const emptyGiftForm = (gift = {}) => {
|
|||||||
priceVersion: gift.priceVersion || "default",
|
priceVersion: gift.priceVersion || "default",
|
||||||
regionIds: (gift.regionIds?.length ? gift.regionIds : [0]).map(String),
|
regionIds: (gift.regionIds?.length ? gift.regionIds : [0]).map(String),
|
||||||
resourceId: gift.resourceId ? String(gift.resourceId) : "",
|
resourceId: gift.resourceId ? String(gift.resourceId) : "",
|
||||||
|
items: [],
|
||||||
sortOrder: gift.sortOrder === 0 || gift.sortOrder ? String(gift.sortOrder) : "0",
|
sortOrder: gift.sortOrder === 0 || gift.sortOrder ? String(gift.sortOrder) : "0",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -180,6 +181,52 @@ export function applyGiftResourceSelection(form, resource, fillIdentity = true)
|
|||||||
return applyGiftResourcePrice(nextForm, resource);
|
return applyGiftResourcePrice(nextForm, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildGiftResourceDrafts(form, resourceIds = [], resources = []) {
|
||||||
|
const resourcesById = new Map(resources.map((resource) => [String(resource.resourceId), resource]));
|
||||||
|
const existingDraftsByResourceId = new Map(
|
||||||
|
(form.items || [])
|
||||||
|
.filter((item) => item?.resourceId)
|
||||||
|
.map((item) => [String(item.resourceId), item]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return resourceIds
|
||||||
|
.map((resourceId) => String(resourceId || "").trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((resourceId) => {
|
||||||
|
const existingDraft = existingDraftsByResourceId.get(resourceId);
|
||||||
|
if (existingDraft) {
|
||||||
|
return existingDraft;
|
||||||
|
}
|
||||||
|
const resource = resourcesById.get(resourceId) || { resourceId };
|
||||||
|
return {
|
||||||
|
...applyGiftResourceSelection(giftDraftDefaultsFromForm(form), resource, true),
|
||||||
|
resource,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function giftDraftDefaultsFromForm(form) {
|
||||||
|
const giftTypeCode = form.giftTypeCode || "normal";
|
||||||
|
const cpRelationType = giftTypeCode === cpGiftTypeCode ? form.cpRelationType || defaultCPRelationType : "";
|
||||||
|
return {
|
||||||
|
chargeAssetType: form.chargeAssetType || "COIN",
|
||||||
|
coinPrice: "",
|
||||||
|
cpRelationType,
|
||||||
|
effectTypes: form.effectTypes || [],
|
||||||
|
effectiveFrom: form.effectiveFrom || "",
|
||||||
|
effectiveTo: form.effectiveTo || "",
|
||||||
|
enabled: form.enabled !== false,
|
||||||
|
giftId: "",
|
||||||
|
giftTypeCode,
|
||||||
|
name: "",
|
||||||
|
presentationJson: normalizeGiftPresentationJson(form.presentationJson || "{}", giftTypeCode, cpRelationType),
|
||||||
|
priceVersion: form.priceVersion || "default",
|
||||||
|
regionIds: form.regionIds?.length ? form.regionIds : ["0"],
|
||||||
|
resourceId: "",
|
||||||
|
sortOrder: form.sortOrder || "0",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function applyGiftPriceDefaults(form, coinPrice) {
|
export function applyGiftPriceDefaults(form, coinPrice) {
|
||||||
const priceValue = String(coinPrice ?? "");
|
const priceValue = String(coinPrice ?? "");
|
||||||
return {
|
return {
|
||||||
@ -234,12 +281,12 @@ export function normalizeGiftPresentationJson(presentationJson, giftTypeCode, cp
|
|||||||
return isCPGift ? cpPresentationJson(nextPresentation, cpRelationType) : JSON.stringify(nextPresentation);
|
return isCPGift ? cpPresentationJson(nextPresentation, cpRelationType) : JSON.stringify(nextPresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAllOptionPages(fetcher, query = {}, pageSize = optionPageSize) {
|
export async function fetchAllOptionPages(fetcher, query = {}, pageSize = optionPageSize, maxPages = optionMaxPages) {
|
||||||
const items = [];
|
const items = [];
|
||||||
let total = Number.POSITIVE_INFINITY;
|
let total = Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
// 后台会把选项接口的 page_size 截断到 100;抽屉候选项必须按真实分页拉全,避免只展示第一页资源。
|
// 后台会把选项接口的 page_size 截断到 100;抽屉候选项必须按真实分页拉全,避免只展示第一页资源。
|
||||||
for (let page = 1; page <= optionMaxPages; page += 1) {
|
for (let page = 1; page <= maxPages; page += 1) {
|
||||||
const data = await fetcher({ ...query, page, page_size: pageSize });
|
const data = await fetcher({ ...query, page, page_size: pageSize });
|
||||||
const pageItems = Array.isArray(data?.items) ? data.items : [];
|
const pageItems = Array.isArray(data?.items) ? data.items : [];
|
||||||
const responsePageSize = Number(data?.pageSize || data?.page_size || pageSize) || pageSize;
|
const responsePageSize = Number(data?.pageSize || data?.page_size || pageSize) || pageSize;
|
||||||
@ -1049,6 +1096,8 @@ export function useGiftListPage() {
|
|||||||
const [giftTypeDialogOpen, setGiftTypeDialogOpen] = useState(false);
|
const [giftTypeDialogOpen, setGiftTypeDialogOpen] = useState(false);
|
||||||
const [giftTypesLoading, setGiftTypesLoading] = useState(false);
|
const [giftTypesLoading, setGiftTypesLoading] = useState(false);
|
||||||
const [resourceOptions, setResourceOptions] = useState([]);
|
const [resourceOptions, setResourceOptions] = useState([]);
|
||||||
|
const [giftResourceGiftOptions, setGiftResourceGiftOptions] = useState([]);
|
||||||
|
const [existingGiftResourceIds, setExistingGiftResourceIds] = useState([]);
|
||||||
const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
|
const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
|
||||||
const { loadingRegions, regionOptions } = useRegionOptions();
|
const { loadingRegions, regionOptions } = useRegionOptions();
|
||||||
const filters = useMemo(
|
const filters = useMemo(
|
||||||
@ -1113,10 +1162,25 @@ export function useGiftListPage() {
|
|||||||
}
|
}
|
||||||
let ignore = false;
|
let ignore = false;
|
||||||
setResourceOptionsLoading(true);
|
setResourceOptionsLoading(true);
|
||||||
fetchAllOptionPages(listResources, { resource_type: "gift" })
|
const optionRequests =
|
||||||
.then((data) => {
|
activeAction === "create"
|
||||||
|
? [
|
||||||
|
fetchAllOptionPages(listResources, { resource_type: "gift" }),
|
||||||
|
fetchAllOptionPages(listGifts, {}, optionPageSize, 1000),
|
||||||
|
]
|
||||||
|
: [fetchAllOptionPages(listResources, { resource_type: "gift" }), Promise.resolve({ items: [] })];
|
||||||
|
Promise.all(optionRequests)
|
||||||
|
.then(([resourceData, giftOptionsData]) => {
|
||||||
if (!ignore) {
|
if (!ignore) {
|
||||||
setResourceOptions(toGiftResourceOptions(data.items || [], selectedGift));
|
const giftOptions = giftOptionsData.items || [];
|
||||||
|
setGiftResourceGiftOptions(giftOptions);
|
||||||
|
setExistingGiftResourceIds(
|
||||||
|
giftOptions
|
||||||
|
.map((gift) => gift?.resourceId)
|
||||||
|
.filter((resourceId) => resourceId || resourceId === 0)
|
||||||
|
.map(String),
|
||||||
|
);
|
||||||
|
setResourceOptions(toGiftResourceOptions(resourceData.items || [], selectedGift));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -1165,6 +1229,66 @@ export function useGiftListPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectGiftResources = (resourceIds) => {
|
||||||
|
setForm((currentForm) => {
|
||||||
|
const items = buildGiftResourceDrafts(currentForm, resourceIds, resourceOptions);
|
||||||
|
return {
|
||||||
|
...currentForm,
|
||||||
|
items,
|
||||||
|
resourceId: items[0]?.resourceId || "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGiftDraft = (resourceId, patch) => {
|
||||||
|
setForm((currentForm) => ({
|
||||||
|
...currentForm,
|
||||||
|
items: (currentForm.items || []).map((item) => {
|
||||||
|
if (String(item.resourceId) !== String(resourceId)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
const nextItem = { ...item, ...patch };
|
||||||
|
if (Object.prototype.hasOwnProperty.call(patch, "giftTypeCode")) {
|
||||||
|
const cpRelationType =
|
||||||
|
nextItem.giftTypeCode === cpGiftTypeCode
|
||||||
|
? nextItem.cpRelationType || defaultCPRelationType
|
||||||
|
: "";
|
||||||
|
return {
|
||||||
|
...nextItem,
|
||||||
|
cpRelationType,
|
||||||
|
presentationJson: normalizeGiftPresentationJson(
|
||||||
|
nextItem.presentationJson,
|
||||||
|
nextItem.giftTypeCode,
|
||||||
|
cpRelationType,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(patch, "cpRelationType")) {
|
||||||
|
return {
|
||||||
|
...nextItem,
|
||||||
|
presentationJson: normalizeGiftPresentationJson(
|
||||||
|
nextItem.presentationJson,
|
||||||
|
nextItem.giftTypeCode,
|
||||||
|
nextItem.cpRelationType,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return nextItem;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeGiftDraft = (resourceId) => {
|
||||||
|
setForm((currentForm) => {
|
||||||
|
const items = (currentForm.items || []).filter((item) => String(item.resourceId) !== String(resourceId));
|
||||||
|
return {
|
||||||
|
...currentForm,
|
||||||
|
items,
|
||||||
|
resourceId: items[0]?.resourceId || "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const changeGiftPrice = (coinPrice) => {
|
const changeGiftPrice = (coinPrice) => {
|
||||||
setForm((currentForm) => applyGiftPriceDefaults(currentForm, coinPrice));
|
setForm((currentForm) => applyGiftPriceDefaults(currentForm, coinPrice));
|
||||||
};
|
};
|
||||||
@ -1249,21 +1373,55 @@ export function useGiftListPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoadingAction(editing ? "gift-edit" : "gift-create");
|
setLoadingAction(editing ? "gift-edit" : "gift-create");
|
||||||
|
const createdDrafts = [];
|
||||||
|
let shouldReload = false;
|
||||||
try {
|
try {
|
||||||
const payload = buildGiftPayload(parseForm(giftFormSchema, form));
|
const createDrafts = !editing && Array.isArray(form.items) && form.items.length ? form.items : [form];
|
||||||
if (editing) {
|
if (editing) {
|
||||||
|
const payload = buildGiftPayload(parseForm(giftFormSchema, form));
|
||||||
await updateGift(selectedGift.giftId, payload);
|
await updateGift(selectedGift.giftId, payload);
|
||||||
} else {
|
} else {
|
||||||
await createGift(payload);
|
const payloads = createDrafts.map((draft) => ({
|
||||||
|
draft,
|
||||||
|
payload: buildGiftPayload(parseForm(giftFormSchema, draft)),
|
||||||
|
}));
|
||||||
|
for (const { draft, payload } of payloads) {
|
||||||
|
await createGift(payload);
|
||||||
|
createdDrafts.push(draft);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showToast(editing ? "礼物已更新" : "礼物已创建", "success");
|
showToast(editing ? "礼物已更新" : `已创建 ${createDrafts.length} 个礼物`, "success");
|
||||||
closeAction();
|
closeAction();
|
||||||
await result.reload();
|
shouldReload = true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast(err.message || "操作失败", "error");
|
if (!editing && createdDrafts.length) {
|
||||||
|
const createdResourceIds = new Set(createdDrafts.map((draft) => String(draft.resourceId)));
|
||||||
|
setExistingGiftResourceIds((current) => Array.from(new Set([...current, ...createdResourceIds])));
|
||||||
|
setForm((currentForm) => {
|
||||||
|
const items = (currentForm.items || []).filter(
|
||||||
|
(item) => !createdResourceIds.has(String(item.resourceId)),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...currentForm,
|
||||||
|
items,
|
||||||
|
resourceId: items[0]?.resourceId || "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
shouldReload = true;
|
||||||
|
showToast(`已创建 ${createdDrafts.length} 个礼物,剩余礼物创建失败:${err.message || "操作失败"}`, "error");
|
||||||
|
} else {
|
||||||
|
showToast(err.message || "操作失败", "error");
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingAction("");
|
setLoadingAction("");
|
||||||
}
|
}
|
||||||
|
if (shouldReload) {
|
||||||
|
try {
|
||||||
|
await result.reload();
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message || "刷新礼物列表失败", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleGift = async (gift, nextEnabled = gift.status !== "active") => {
|
const toggleGift = async (gift, nextEnabled = gift.status !== "active") => {
|
||||||
@ -1389,6 +1547,8 @@ export function useGiftListPage() {
|
|||||||
giftTypeForm,
|
giftTypeForm,
|
||||||
giftTypeOptions: giftTypes,
|
giftTypeOptions: giftTypes,
|
||||||
giftTypesLoading,
|
giftTypesLoading,
|
||||||
|
giftResourceGiftOptions,
|
||||||
|
existingGiftResourceIds,
|
||||||
loadingAction,
|
loadingAction,
|
||||||
loadingRegions,
|
loadingRegions,
|
||||||
openCreateGift,
|
openCreateGift,
|
||||||
@ -1401,12 +1561,15 @@ export function useGiftListPage() {
|
|||||||
selectedGift,
|
selectedGift,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
selectGiftResource,
|
selectGiftResource,
|
||||||
|
selectGiftResources,
|
||||||
|
removeGiftDraft,
|
||||||
setForm,
|
setForm,
|
||||||
saveGiftSortOrder,
|
saveGiftSortOrder,
|
||||||
submitGiftTypes,
|
submitGiftTypes,
|
||||||
status,
|
status,
|
||||||
submitGift,
|
submitGift,
|
||||||
toggleGift,
|
toggleGift,
|
||||||
|
updateGiftDraft,
|
||||||
updateGiftTypeFormItem,
|
updateGiftTypeFormItem,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { expect, test, vi } from "vitest";
|
|||||||
import {
|
import {
|
||||||
applyGiftPriceDefaults,
|
applyGiftPriceDefaults,
|
||||||
applyGiftResourceSelection,
|
applyGiftResourceSelection,
|
||||||
|
buildGiftResourceDrafts,
|
||||||
cpRelationTypeFromGift,
|
cpRelationTypeFromGift,
|
||||||
cpRelationTypeFromPresentationJson,
|
cpRelationTypeFromPresentationJson,
|
||||||
cpRelationTypeShortLabel,
|
cpRelationTypeShortLabel,
|
||||||
@ -47,6 +48,76 @@ test("fills gift price without gift points", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("builds gift drafts from multiple selected resources and preserves edits", () => {
|
||||||
|
const form = {
|
||||||
|
chargeAssetType: "COIN",
|
||||||
|
cpRelationType: "",
|
||||||
|
effectTypes: ["animation"],
|
||||||
|
effectiveFrom: "",
|
||||||
|
effectiveTo: "",
|
||||||
|
enabled: true,
|
||||||
|
giftTypeCode: "normal",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
chargeAssetType: "COIN",
|
||||||
|
coinPrice: "88",
|
||||||
|
effectTypes: [],
|
||||||
|
effectiveFrom: "",
|
||||||
|
effectiveTo: "",
|
||||||
|
enabled: false,
|
||||||
|
giftId: "custom_rose",
|
||||||
|
giftTypeCode: "normal",
|
||||||
|
name: "Custom Rose",
|
||||||
|
presentationJson: "{}",
|
||||||
|
priceVersion: "default",
|
||||||
|
regionIds: ["0"],
|
||||||
|
resourceId: "11",
|
||||||
|
sortOrder: "7",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
presentationJson: "{}",
|
||||||
|
priceVersion: "default",
|
||||||
|
regionIds: ["0", "1001"],
|
||||||
|
sortOrder: "3",
|
||||||
|
};
|
||||||
|
|
||||||
|
const drafts = buildGiftResourceDrafts(form, ["11", "12"], [
|
||||||
|
{
|
||||||
|
coinPrice: 10,
|
||||||
|
name: "Rose",
|
||||||
|
priceType: "coin",
|
||||||
|
resourceCode: "gift_rose",
|
||||||
|
resourceId: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
coinPrice: 20,
|
||||||
|
name: "Ring",
|
||||||
|
priceType: "coin",
|
||||||
|
resourceCode: "gift_ring",
|
||||||
|
resourceId: 12,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(drafts).toHaveLength(2);
|
||||||
|
expect(drafts[0]).toMatchObject({
|
||||||
|
coinPrice: "88",
|
||||||
|
giftId: "custom_rose",
|
||||||
|
name: "Custom Rose",
|
||||||
|
resourceId: "11",
|
||||||
|
sortOrder: "7",
|
||||||
|
});
|
||||||
|
expect(drafts[1]).toMatchObject({
|
||||||
|
coinPrice: "20",
|
||||||
|
effectTypes: ["animation"],
|
||||||
|
giftId: "12",
|
||||||
|
giftTypeCode: "normal",
|
||||||
|
name: "Ring",
|
||||||
|
regionIds: ["0", "1001"],
|
||||||
|
resourceId: "12",
|
||||||
|
sortOrder: "3",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("fetches all option pages instead of only the first resource page", async () => {
|
test("fetches all option pages instead of only the first resource page", async () => {
|
||||||
const fetcher = vi
|
const fetcher = vi
|
||||||
.fn()
|
.fn()
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import {
|
|||||||
resourceStatusFilters,
|
resourceStatusFilters,
|
||||||
} from "@/features/resources/constants.js";
|
} from "@/features/resources/constants.js";
|
||||||
import { GiftTypeConfigDialog } from "@/features/resources/components/GiftTypeConfigDialog.jsx";
|
import { GiftTypeConfigDialog } from "@/features/resources/components/GiftTypeConfigDialog.jsx";
|
||||||
|
import { GiftResourceSelectField } from "@/features/resources/components/GiftResourceSelectDrawer.jsx";
|
||||||
import { ResourceSelectField } from "@/features/resources/components/ResourceSelectDrawer.jsx";
|
import { ResourceSelectField } from "@/features/resources/components/ResourceSelectDrawer.jsx";
|
||||||
import {
|
import {
|
||||||
cpRelationTypeFromGift,
|
cpRelationTypeFromGift,
|
||||||
@ -137,7 +138,7 @@ const baseColumns = (giftTypeOptions) => [
|
|||||||
|
|
||||||
export function GiftListPage() {
|
export function GiftListPage() {
|
||||||
const page = useGiftListPage();
|
const page = useGiftListPage();
|
||||||
const items = page.data.items || [];
|
const items = useMemo(() => page.data.items || [], [page.data.items]);
|
||||||
const total = page.data.total || 0;
|
const total = page.data.total || 0;
|
||||||
const [selectedGiftIds, setSelectedGiftIds] = useState([]);
|
const [selectedGiftIds, setSelectedGiftIds] = useState([]);
|
||||||
const isEditing = page.activeAction === "edit";
|
const isEditing = page.activeAction === "edit";
|
||||||
@ -419,7 +420,14 @@ function GiftSortOrderCell({ gift, page }) {
|
|||||||
function GiftFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, page, regionOptions }) {
|
function GiftFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, page, regionOptions }) {
|
||||||
const submitDisabled =
|
const submitDisabled =
|
||||||
disabled || loading || page.resourceOptionsLoading || page.loadingRegions || page.giftTypesLoading;
|
disabled || loading || page.resourceOptionsLoading || page.loadingRegions || page.giftTypesLoading;
|
||||||
const showGiftIdentityFields = mode === "edit" || Boolean(form.resourceId);
|
const isCreateMode = mode === "create";
|
||||||
|
const giftDrafts = Array.isArray(form.items) ? form.items : [];
|
||||||
|
const selectedResourceIds = isCreateMode
|
||||||
|
? giftDrafts.map((item) => item.resourceId).filter(Boolean)
|
||||||
|
: form.resourceId
|
||||||
|
? [form.resourceId]
|
||||||
|
: [];
|
||||||
|
const showGiftIdentityFields = mode === "edit" && Boolean(form.resourceId);
|
||||||
const isCPGiftType = form.giftTypeCode === "cp";
|
const isCPGiftType = form.giftTypeCode === "cp";
|
||||||
return (
|
return (
|
||||||
<AdminFormDialog
|
<AdminFormDialog
|
||||||
@ -433,15 +441,30 @@ function GiftFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open
|
|||||||
>
|
>
|
||||||
<AdminFormSection title="资源信息">
|
<AdminFormSection title="资源信息">
|
||||||
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
||||||
<ResourceSelectField
|
{isCreateMode ? (
|
||||||
disabled={disabled || page.resourceOptionsLoading}
|
<GiftResourceSelectField
|
||||||
drawerTitle="选择礼物资源"
|
disabled={disabled || page.resourceOptionsLoading}
|
||||||
label="礼物资源"
|
drawerTitle="选择礼物资源"
|
||||||
loading={page.resourceOptionsLoading}
|
excludedResourceIds={page.existingGiftResourceIds}
|
||||||
resources={page.resourceOptions}
|
giftTypes={page.giftTypeOptions}
|
||||||
value={form.resourceId}
|
gifts={page.giftResourceGiftOptions}
|
||||||
onChange={(resourceId) => page.selectGiftResource(resourceId)}
|
label="礼物资源"
|
||||||
/>
|
loading={page.resourceOptionsLoading}
|
||||||
|
resources={page.resourceOptions}
|
||||||
|
selectedResourceIds={selectedResourceIds}
|
||||||
|
onChange={page.selectGiftResources}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ResourceSelectField
|
||||||
|
disabled={disabled || page.resourceOptionsLoading}
|
||||||
|
drawerTitle="选择礼物资源"
|
||||||
|
label="礼物资源"
|
||||||
|
loading={page.resourceOptionsLoading}
|
||||||
|
resources={page.resourceOptions}
|
||||||
|
value={form.resourceId}
|
||||||
|
onChange={(resourceId) => page.selectGiftResource(resourceId)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{showGiftIdentityFields ? (
|
{showGiftIdentityFields ? (
|
||||||
<>
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
@ -460,21 +483,23 @@ function GiftFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<TextField
|
{!isCreateMode ? (
|
||||||
disabled={disabled || page.giftTypesLoading}
|
<TextField
|
||||||
label="礼物类型"
|
disabled={disabled || page.giftTypesLoading}
|
||||||
required
|
label="礼物类型"
|
||||||
select
|
required
|
||||||
value={form.giftTypeCode}
|
select
|
||||||
onChange={(event) => page.changeGiftFormTypeCode(event.target.value)}
|
value={form.giftTypeCode}
|
||||||
>
|
onChange={(event) => page.changeGiftFormTypeCode(event.target.value)}
|
||||||
{page.giftTypeOptions.map((item) => (
|
>
|
||||||
<MenuItem key={item.tabKey} value={item.tabKey}>
|
{page.giftTypeOptions.map((item) => (
|
||||||
{item.displayName || item.tabKey}
|
<MenuItem key={item.tabKey} value={item.tabKey}>
|
||||||
</MenuItem>
|
{item.displayName || item.tabKey}
|
||||||
))}
|
</MenuItem>
|
||||||
</TextField>
|
))}
|
||||||
{isCPGiftType ? (
|
</TextField>
|
||||||
|
) : null}
|
||||||
|
{!isCreateMode && isCPGiftType ? (
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label="关系类型"
|
label="关系类型"
|
||||||
@ -492,56 +517,216 @@ function GiftFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open
|
|||||||
) : null}
|
) : null}
|
||||||
</AdminFormFieldGrid>
|
</AdminFormFieldGrid>
|
||||||
</AdminFormSection>
|
</AdminFormSection>
|
||||||
<AdminFormSection title="价格与排序">
|
{isCreateMode ? (
|
||||||
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
<GiftDraftSection
|
||||||
<TextField disabled label="价格" required type="number" value={form.coinPrice} />
|
disabled={disabled}
|
||||||
|
drafts={giftDrafts}
|
||||||
|
page={page}
|
||||||
|
regionOptions={regionOptions}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<AdminFormSection title="价格与排序">
|
||||||
|
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
||||||
|
<TextField disabled label="价格" required type="number" value={form.coinPrice} />
|
||||||
|
<TextField
|
||||||
|
disabled={disabled}
|
||||||
|
label="排序"
|
||||||
|
type="number"
|
||||||
|
value={form.sortOrder}
|
||||||
|
onChange={(event) => page.setForm({ ...form, sortOrder: event.target.value })}
|
||||||
|
/>
|
||||||
|
<AdminFormSwitchField
|
||||||
|
checked={form.enabled}
|
||||||
|
checkedLabel="启用"
|
||||||
|
className={styles.giftSwitch}
|
||||||
|
disabled={disabled}
|
||||||
|
label="礼物状态"
|
||||||
|
uncheckedLabel="禁用"
|
||||||
|
onChange={(checked) => page.setForm({ ...form, enabled: checked })}
|
||||||
|
/>
|
||||||
|
</AdminFormFieldGrid>
|
||||||
|
</AdminFormSection>
|
||||||
|
<AdminFormSection title="投放与时间">
|
||||||
|
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
||||||
|
<GiftRegionSelect
|
||||||
|
disabled={disabled || page.loadingRegions}
|
||||||
|
labels={buildRegionLabelMap(regionOptions)}
|
||||||
|
options={regionOptions}
|
||||||
|
value={form.regionIds}
|
||||||
|
onChange={(regionIds) => page.setForm({ ...form, regionIds })}
|
||||||
|
/>
|
||||||
|
<DateTimeField
|
||||||
|
disabled={disabled}
|
||||||
|
label="有效开始时间"
|
||||||
|
value={form.effectiveFrom}
|
||||||
|
onChange={(effectiveFrom) => page.setForm({ ...form, effectiveFrom })}
|
||||||
|
/>
|
||||||
|
<DateTimeField
|
||||||
|
disabled={disabled}
|
||||||
|
label="有效结束时间"
|
||||||
|
value={form.effectiveTo}
|
||||||
|
onChange={(effectiveTo) => page.setForm({ ...form, effectiveTo })}
|
||||||
|
/>
|
||||||
|
<GiftEffectSelect
|
||||||
|
disabled={disabled}
|
||||||
|
value={form.effectTypes}
|
||||||
|
onChange={(effectTypes) => page.setForm({ ...form, effectTypes })}
|
||||||
|
/>
|
||||||
|
</AdminFormFieldGrid>
|
||||||
|
</AdminFormSection>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AdminFormDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GiftDraftSection({ disabled, drafts, page, regionOptions }) {
|
||||||
|
const regionLabels = buildRegionLabelMap(regionOptions);
|
||||||
|
if (!drafts.length) {
|
||||||
|
return (
|
||||||
|
<AdminFormSection title="礼物配置">
|
||||||
|
<div className={styles.giftDraftEmpty}>当前无数据</div>
|
||||||
|
</AdminFormSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminFormSection title={`礼物配置(${drafts.length})`}>
|
||||||
|
<div className={styles.giftDraftList}>
|
||||||
|
{drafts.map((draft, index) => (
|
||||||
|
<GiftDraftEditor
|
||||||
|
disabled={disabled}
|
||||||
|
draft={draft}
|
||||||
|
index={index}
|
||||||
|
key={draft.resourceId || index}
|
||||||
|
page={page}
|
||||||
|
regionLabels={regionLabels}
|
||||||
|
regionOptions={regionOptions}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AdminFormSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GiftDraftEditor({ disabled, draft, index, page, regionLabels, regionOptions }) {
|
||||||
|
const isCPGiftType = draft.giftTypeCode === "cp";
|
||||||
|
const updateDraft = (patch) => page.updateGiftDraft(draft.resourceId, patch);
|
||||||
|
return (
|
||||||
|
<div className={styles.giftDraftItem}>
|
||||||
|
<div className={styles.giftDraftHeader}>
|
||||||
|
<GiftDraftThumb draft={draft} />
|
||||||
|
<div className={styles.giftDraftTitle}>
|
||||||
|
<span>{draft.name || draft.giftId || `礼物 ${index + 1}`}</span>
|
||||||
|
<span>资源 {draft.resourceId || "-"}</span>
|
||||||
|
</div>
|
||||||
|
<AdminActionIconButton
|
||||||
|
disabled={disabled}
|
||||||
|
label="移除礼物"
|
||||||
|
onClick={() => page.removeGiftDraft(draft.resourceId)}
|
||||||
|
>
|
||||||
|
<DeleteOutlineOutlined fontSize="small" />
|
||||||
|
</AdminActionIconButton>
|
||||||
|
</div>
|
||||||
|
<AdminFormFieldGrid columns="repeat(4, minmax(0, 1fr))">
|
||||||
|
<TextField
|
||||||
|
disabled={disabled}
|
||||||
|
label="礼物名称"
|
||||||
|
required
|
||||||
|
value={draft.name}
|
||||||
|
onChange={(event) => updateDraft({ name: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
disabled={disabled}
|
||||||
|
label="礼物 ID"
|
||||||
|
required
|
||||||
|
value={draft.giftId}
|
||||||
|
onChange={(event) => updateDraft({ giftId: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField disabled label="价格" required type="number" value={draft.coinPrice} />
|
||||||
|
<TextField
|
||||||
|
disabled={disabled}
|
||||||
|
label="排序"
|
||||||
|
type="number"
|
||||||
|
value={draft.sortOrder}
|
||||||
|
onChange={(event) => updateDraft({ sortOrder: event.target.value })}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
disabled={disabled || page.giftTypesLoading}
|
||||||
|
label="礼物类型"
|
||||||
|
required
|
||||||
|
select
|
||||||
|
value={draft.giftTypeCode}
|
||||||
|
onChange={(event) => updateDraft({ giftTypeCode: event.target.value })}
|
||||||
|
>
|
||||||
|
{page.giftTypeOptions.map((item) => (
|
||||||
|
<MenuItem key={item.tabKey} value={item.tabKey}>
|
||||||
|
{item.displayName || item.tabKey}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
{isCPGiftType ? (
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label="排序"
|
label="关系类型"
|
||||||
type="number"
|
required
|
||||||
value={form.sortOrder}
|
select
|
||||||
onChange={(event) => page.setForm({ ...form, sortOrder: event.target.value })}
|
value={draft.cpRelationType || "cp"}
|
||||||
/>
|
onChange={(event) => updateDraft({ cpRelationType: event.target.value })}
|
||||||
<AdminFormSwitchField
|
>
|
||||||
checked={form.enabled}
|
{cpRelationTypeOptions.map(([value, label]) => (
|
||||||
checkedLabel="启用"
|
<MenuItem key={value} value={value}>
|
||||||
className={styles.giftSwitch}
|
{label}
|
||||||
disabled={disabled}
|
</MenuItem>
|
||||||
label="礼物状态"
|
))}
|
||||||
uncheckedLabel="禁用"
|
</TextField>
|
||||||
onChange={(checked) => page.setForm({ ...form, enabled: checked })}
|
) : null}
|
||||||
/>
|
<GiftRegionSelect
|
||||||
</AdminFormFieldGrid>
|
disabled={disabled || page.loadingRegions}
|
||||||
</AdminFormSection>
|
labels={regionLabels}
|
||||||
<AdminFormSection title="投放与时间">
|
options={regionOptions}
|
||||||
<AdminFormFieldGrid columns="repeat(2, minmax(0, 1fr))">
|
value={draft.regionIds}
|
||||||
<GiftRegionSelect
|
onChange={(regionIds) => updateDraft({ regionIds })}
|
||||||
disabled={disabled || page.loadingRegions}
|
/>
|
||||||
labels={buildRegionLabelMap(regionOptions)}
|
<AdminFormSwitchField
|
||||||
options={regionOptions}
|
checked={draft.enabled}
|
||||||
value={form.regionIds}
|
checkedLabel="启用"
|
||||||
onChange={(regionIds) => page.setForm({ ...form, regionIds })}
|
className={styles.giftSwitch}
|
||||||
/>
|
disabled={disabled}
|
||||||
<DateTimeField
|
label="礼物状态"
|
||||||
disabled={disabled}
|
uncheckedLabel="禁用"
|
||||||
label="有效开始时间"
|
onChange={(checked) => updateDraft({ enabled: checked })}
|
||||||
value={form.effectiveFrom}
|
/>
|
||||||
onChange={(effectiveFrom) => page.setForm({ ...form, effectiveFrom })}
|
<DateTimeField
|
||||||
/>
|
disabled={disabled}
|
||||||
<DateTimeField
|
label="有效开始时间"
|
||||||
disabled={disabled}
|
value={draft.effectiveFrom}
|
||||||
label="有效结束时间"
|
onChange={(effectiveFrom) => updateDraft({ effectiveFrom })}
|
||||||
value={form.effectiveTo}
|
/>
|
||||||
onChange={(effectiveTo) => page.setForm({ ...form, effectiveTo })}
|
<DateTimeField
|
||||||
/>
|
disabled={disabled}
|
||||||
<GiftEffectSelect
|
label="有效结束时间"
|
||||||
disabled={disabled}
|
value={draft.effectiveTo}
|
||||||
value={form.effectTypes}
|
onChange={(effectiveTo) => updateDraft({ effectiveTo })}
|
||||||
onChange={(effectTypes) => page.setForm({ ...form, effectTypes })}
|
/>
|
||||||
/>
|
<GiftEffectSelect
|
||||||
</AdminFormFieldGrid>
|
disabled={disabled}
|
||||||
</AdminFormSection>
|
value={draft.effectTypes}
|
||||||
</AdminFormDialog>
|
onChange={(effectTypes) => updateDraft({ effectTypes })}
|
||||||
|
/>
|
||||||
|
</AdminFormFieldGrid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GiftDraftThumb({ draft }) {
|
||||||
|
const resource = draft.resource || {};
|
||||||
|
const imageUrl = imageURL(resource.previewUrl || resource.assetUrl || resource.animationUrl);
|
||||||
|
return (
|
||||||
|
<span className={styles.giftDraftThumb}>
|
||||||
|
{imageUrl ? <img alt="" src={imageUrl} /> : <CardGiftcardOutlined fontSize="small" />}
|
||||||
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -308,6 +308,82 @@
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.giftDraftList {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftItem {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--space-3);
|
||||||
|
padding: var(--space-3);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
background: var(--bg-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftHeader {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
grid-template-columns: 44px minmax(0, 1fr) var(--control-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftThumb {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg-card-strong);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftThumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftTitle {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftTitle span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftTitle span:first-child {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 750;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftTitle span:last-child {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.giftDraftEmpty {
|
||||||
|
display: flex;
|
||||||
|
min-height: 120px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid var(--border-muted);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg-card-strong);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
.batchInput {
|
.batchInput {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -299,6 +299,7 @@ export const API_OPERATIONS = {
|
|||||||
updateHostAgencyPolicy: "updateHostAgencyPolicy",
|
updateHostAgencyPolicy: "updateHostAgencyPolicy",
|
||||||
updateHumanRoomRobotConfig: "updateHumanRoomRobotConfig",
|
updateHumanRoomRobotConfig: "updateHumanRoomRobotConfig",
|
||||||
updateInviteActivityRewardConfig: "updateInviteActivityRewardConfig",
|
updateInviteActivityRewardConfig: "updateInviteActivityRewardConfig",
|
||||||
|
updateManager: "updateManager",
|
||||||
updateMenu: "updateMenu",
|
updateMenu: "updateMenu",
|
||||||
updateMenuVisible: "updateMenuVisible",
|
updateMenuVisible: "updateMenuVisible",
|
||||||
updatePermission: "updatePermission",
|
updatePermission: "updatePermission",
|
||||||
@ -2329,6 +2330,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
|||||||
permission: "invite-activity-reward:update",
|
permission: "invite-activity-reward:update",
|
||||||
permissions: ["invite-activity-reward:update"]
|
permissions: ["invite-activity-reward:update"]
|
||||||
},
|
},
|
||||||
|
updateManager: {
|
||||||
|
method: "PUT",
|
||||||
|
operationId: API_OPERATIONS.updateManager,
|
||||||
|
path: "/v1/admin/managers/{user_id}",
|
||||||
|
permission: "bd:update",
|
||||||
|
permissions: ["bd:update"]
|
||||||
|
},
|
||||||
updateMenu: {
|
updateMenu: {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
operationId: API_OPERATIONS.updateMenu,
|
operationId: API_OPERATIONS.updateMenu,
|
||||||
|
|||||||
69
src/shared/api/generated/schema.d.ts
vendored
69
src/shared/api/generated/schema.d.ts
vendored
@ -676,6 +676,22 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/admin/managers/{user_id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put: operations["updateManager"];
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/admin/agencies": {
|
"/admin/agencies": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@ -3997,6 +4013,16 @@ export interface components {
|
|||||||
Manager: {
|
Manager: {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
bdLeaderCount?: number;
|
bdLeaderCount?: number;
|
||||||
|
canAddAdmin?: boolean;
|
||||||
|
canAddBdLeader?: boolean;
|
||||||
|
canAddSuperadmin?: boolean;
|
||||||
|
canBlockUser?: boolean;
|
||||||
|
canGrantAvatarFrame?: boolean;
|
||||||
|
canGrantBadge?: boolean;
|
||||||
|
canGrantVehicle?: boolean;
|
||||||
|
canGrantVip?: boolean;
|
||||||
|
canTransferUserCountry?: boolean;
|
||||||
|
canUpdateUserLevel?: boolean;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
displayUserId?: string;
|
displayUserId?: string;
|
||||||
@ -4010,9 +4036,34 @@ export interface components {
|
|||||||
username?: string;
|
username?: string;
|
||||||
};
|
};
|
||||||
CreateManagerPayload: {
|
CreateManagerPayload: {
|
||||||
|
canAddAdmin?: boolean;
|
||||||
|
canAddBdLeader?: boolean;
|
||||||
|
canAddSuperadmin?: boolean;
|
||||||
|
canBlockUser?: boolean;
|
||||||
|
canGrantAvatarFrame?: boolean;
|
||||||
|
canGrantBadge?: boolean;
|
||||||
|
canGrantVehicle?: boolean;
|
||||||
|
canGrantVip?: boolean;
|
||||||
|
canTransferUserCountry?: boolean;
|
||||||
|
canUpdateUserLevel?: boolean;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
targetUserId: string;
|
targetUserId: string;
|
||||||
};
|
};
|
||||||
|
UpdateManagerPayload: {
|
||||||
|
canAddAdmin?: boolean;
|
||||||
|
canAddBdLeader?: boolean;
|
||||||
|
canAddSuperadmin?: boolean;
|
||||||
|
canBlockUser?: boolean;
|
||||||
|
canGrantAvatarFrame?: boolean;
|
||||||
|
canGrantBadge?: boolean;
|
||||||
|
canGrantVehicle?: boolean;
|
||||||
|
canGrantVip?: boolean;
|
||||||
|
canTransferUserCountry?: boolean;
|
||||||
|
canUpdateUserLevel?: boolean;
|
||||||
|
contact?: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
status?: "active" | "disabled";
|
||||||
|
};
|
||||||
BDProfile: {
|
BDProfile: {
|
||||||
agencyCount?: number;
|
agencyCount?: number;
|
||||||
createdAtMs?: number;
|
createdAtMs?: number;
|
||||||
@ -5416,6 +5467,24 @@ export interface operations {
|
|||||||
201: components["responses"]["ManagerResponse"];
|
201: components["responses"]["ManagerResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
updateManager: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
user_id: number;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["UpdateManagerPayload"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
200: components["responses"]["ManagerResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
listAgencies: {
|
listAgencies: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
|||||||
@ -681,6 +681,7 @@ export interface ManagerDto {
|
|||||||
canGrantAvatarFrame?: boolean;
|
canGrantAvatarFrame?: boolean;
|
||||||
canGrantBadge?: boolean;
|
canGrantBadge?: boolean;
|
||||||
canGrantVehicle?: boolean;
|
canGrantVehicle?: boolean;
|
||||||
|
canGrantVip?: boolean;
|
||||||
canTransferUserCountry?: boolean;
|
canTransferUserCountry?: boolean;
|
||||||
canUpdateUserLevel?: boolean;
|
canUpdateUserLevel?: boolean;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
@ -703,6 +704,7 @@ export interface CreateManagerPayload {
|
|||||||
canGrantAvatarFrame?: boolean;
|
canGrantAvatarFrame?: boolean;
|
||||||
canGrantBadge?: boolean;
|
canGrantBadge?: boolean;
|
||||||
canGrantVehicle?: boolean;
|
canGrantVehicle?: boolean;
|
||||||
|
canGrantVip?: boolean;
|
||||||
canTransferUserCountry?: boolean;
|
canTransferUserCountry?: boolean;
|
||||||
canUpdateUserLevel?: boolean;
|
canUpdateUserLevel?: boolean;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
@ -717,6 +719,7 @@ export interface UpdateManagerPayload {
|
|||||||
canGrantAvatarFrame?: boolean;
|
canGrantAvatarFrame?: boolean;
|
||||||
canGrantBadge?: boolean;
|
canGrantBadge?: boolean;
|
||||||
canGrantVehicle?: boolean;
|
canGrantVehicle?: boolean;
|
||||||
|
canGrantVip?: boolean;
|
||||||
canTransferUserCountry?: boolean;
|
canTransferUserCountry?: boolean;
|
||||||
canUpdateUserLevel?: boolean;
|
canUpdateUserLevel?: boolean;
|
||||||
contact?: string;
|
contact?: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user