812 lines
26 KiB
JavaScript
812 lines
26 KiB
JavaScript
import { useEffect, useMemo, useState } from "react";
|
|
import { parseForm } from "@/shared/forms/validation";
|
|
import { useRegionOptions } from "@/shared/hooks/useRegionOptions.js";
|
|
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
|
|
import { useToast } from "@/shared/ui/ToastProvider.jsx";
|
|
import {
|
|
createGift,
|
|
createResource,
|
|
createResourceGroup,
|
|
disableGift,
|
|
disableResource,
|
|
disableResourceGroup,
|
|
enableGift,
|
|
enableResource,
|
|
enableResourceGroup,
|
|
grantResource,
|
|
grantResourceGroup,
|
|
listGifts,
|
|
listResourceGrants,
|
|
listResourceGroups,
|
|
listResources,
|
|
updateGift,
|
|
updateResource,
|
|
updateResourceGroup,
|
|
} from "@/features/resources/api";
|
|
import { useResourceAbilities } from "@/features/resources/permissions.js";
|
|
import {
|
|
giftFormSchema,
|
|
resourceFormSchema,
|
|
resourceGrantFormSchema,
|
|
resourceGroupCreateFormSchema,
|
|
} from "@/features/resources/schema.js";
|
|
|
|
const pageSize = 10;
|
|
const dayMillis = 24 * 60 * 60 * 1000;
|
|
const emptyData = { items: [], page: 1, pageSize, total: 0 };
|
|
const emptyResourceForm = (resource = {}) => ({
|
|
assetUrl: resource.assetUrl || "",
|
|
enabled: resource.status ? resource.status === "active" : true,
|
|
name: resource.name || "",
|
|
previewUrl: resource.previewUrl || "",
|
|
resourceCode: resource.resourceCode || "",
|
|
resourceType: resource.resourceType || "avatar_frame",
|
|
walletAssetAmount: resource.walletAssetAmount ? String(resource.walletAssetAmount) : "",
|
|
});
|
|
const emptyResourceGroupForm = (group = {}) => ({
|
|
description: group.description || "",
|
|
enabled: group.status ? group.status === "active" : true,
|
|
groupCode: group.groupCode || "",
|
|
items: group.items?.length ? group.items.map(groupItemToForm) : [emptyGroupResourceItem()],
|
|
name: group.name || "",
|
|
sortOrder: group.sortOrder === 0 || group.sortOrder ? String(group.sortOrder) : "0",
|
|
});
|
|
const emptyGroupResourceItem = () => ({
|
|
durationDays: "1",
|
|
itemType: "resource",
|
|
resourceId: "",
|
|
sortOrder: "0",
|
|
walletAssetAmount: "",
|
|
walletAssetType: "",
|
|
});
|
|
const emptyGroupWalletAssetItem = (walletAssetType) => ({
|
|
durationDays: "",
|
|
itemType: "wallet_asset",
|
|
resourceId: "",
|
|
sortOrder: "0",
|
|
walletAssetAmount: "",
|
|
walletAssetType,
|
|
});
|
|
const emptyGiftForm = (gift = {}) => ({
|
|
coinPrice: gift.coinPrice === 0 || gift.coinPrice ? String(gift.coinPrice) : "",
|
|
enabled: gift.status ? gift.status === "active" : true,
|
|
giftId: gift.giftId || "",
|
|
giftPointAmount: gift.giftPointAmount === 0 || gift.giftPointAmount ? String(gift.giftPointAmount) : "0",
|
|
heatValue: gift.heatValue === 0 || gift.heatValue ? String(gift.heatValue) : "0",
|
|
name: gift.name || "",
|
|
presentationJson: gift.presentationJson || "{}",
|
|
priceVersion: gift.priceVersion || "default",
|
|
regionIds: (gift.regionIds?.length ? gift.regionIds : [0]).map(String),
|
|
resourceId: gift.resourceId ? String(gift.resourceId) : "",
|
|
sortOrder: gift.sortOrder === 0 || gift.sortOrder ? String(gift.sortOrder) : "0",
|
|
});
|
|
const emptyGrantForm = () => ({
|
|
durationDays: "0",
|
|
groupId: "",
|
|
quantity: "1",
|
|
reason: "",
|
|
resourceId: "",
|
|
subjectType: "resource",
|
|
targetUserId: "",
|
|
});
|
|
|
|
function groupItemToForm(item) {
|
|
const sortOrder = item.sortOrder === 0 || item.sortOrder ? String(item.sortOrder) : "0";
|
|
if (item.itemType === "wallet_asset") {
|
|
return {
|
|
durationDays: "",
|
|
itemType: "wallet_asset",
|
|
resourceId: "",
|
|
sortOrder,
|
|
walletAssetAmount:
|
|
item.walletAssetAmount === 0 || item.walletAssetAmount ? String(item.walletAssetAmount) : "",
|
|
walletAssetType: item.walletAssetType || "",
|
|
};
|
|
}
|
|
|
|
return {
|
|
durationDays: item.durationMs ? formatDurationDays(item.durationMs) : "1",
|
|
itemType: "resource",
|
|
resourceId: item.resourceId ? String(item.resourceId) : "",
|
|
sortOrder,
|
|
walletAssetAmount: "",
|
|
walletAssetType: "",
|
|
};
|
|
}
|
|
|
|
export function useResourceListPage() {
|
|
const abilities = useResourceAbilities();
|
|
const { showToast } = useToast();
|
|
const [query, setQuery] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
const [resourceType, setResourceType] = useState("");
|
|
const [page, setPage] = useState(1);
|
|
const [activeAction, setActiveAction] = useState("");
|
|
const [form, setForm] = useState(emptyResourceForm);
|
|
const [selectedResource, setSelectedResource] = useState(null);
|
|
const [loadingAction, setLoadingAction] = useState("");
|
|
const filters = useMemo(
|
|
() => ({
|
|
keyword: query,
|
|
resource_type: resourceType,
|
|
status,
|
|
}),
|
|
[query, resourceType, status],
|
|
);
|
|
const result = usePaginatedQuery({
|
|
errorMessage: "加载资源列表失败",
|
|
fetcher: listResources,
|
|
filters,
|
|
page,
|
|
pageSize,
|
|
queryKey: ["resources", filters, page],
|
|
});
|
|
|
|
const openCreateResource = () => {
|
|
setForm(emptyResourceForm());
|
|
setSelectedResource(null);
|
|
setActiveAction("create");
|
|
};
|
|
|
|
const openEditResource = (resource) => {
|
|
if (!resource?.resourceId) {
|
|
return;
|
|
}
|
|
setForm(emptyResourceForm(resource));
|
|
setSelectedResource(resource);
|
|
setActiveAction("edit");
|
|
};
|
|
|
|
const closeAction = () => {
|
|
setActiveAction("");
|
|
setSelectedResource(null);
|
|
};
|
|
|
|
const submitResource = async (event) => {
|
|
event.preventDefault();
|
|
const editing = activeAction === "edit";
|
|
await runAction(
|
|
editing ? "resource-edit" : "resource-create",
|
|
editing ? "资源已更新" : "资源已创建",
|
|
async () => {
|
|
const payload = buildResourcePayload(parseForm(resourceFormSchema, form));
|
|
if (editing) {
|
|
await updateResource(selectedResource.resourceId, payload);
|
|
} else {
|
|
await createResource(payload);
|
|
}
|
|
setForm(emptyResourceForm());
|
|
closeAction();
|
|
await result.reload();
|
|
},
|
|
);
|
|
};
|
|
|
|
const toggleResource = async (resource, nextEnabled = resource.status !== "active") => {
|
|
if (!abilities.canUpdate || !resource?.resourceId) {
|
|
return;
|
|
}
|
|
const nextStatus = nextEnabled ? "active" : "disabled";
|
|
if ((resource.status === "active") === nextEnabled) {
|
|
return;
|
|
}
|
|
await runAction(
|
|
`resource-status-${resource.resourceId}`,
|
|
nextEnabled ? "资源已启用" : "资源已禁用",
|
|
async () => {
|
|
if (nextStatus === "active") {
|
|
await enableResource(resource.resourceId);
|
|
} else {
|
|
await disableResource(resource.resourceId);
|
|
}
|
|
await result.reload();
|
|
},
|
|
);
|
|
};
|
|
|
|
const runAction = async (action, successMessage, submitter) => {
|
|
setLoadingAction(action);
|
|
try {
|
|
await submitter();
|
|
showToast(successMessage, "success");
|
|
} catch (err) {
|
|
showToast(err.message || "操作失败", "error");
|
|
} finally {
|
|
setLoadingAction("");
|
|
}
|
|
};
|
|
|
|
return {
|
|
...sharedPageState({ page, query, result, setPage, setQuery }),
|
|
abilities,
|
|
activeAction,
|
|
changeResourceType: resetSetter(setResourceType, setPage),
|
|
changeStatus: resetSetter(setStatus, setPage),
|
|
closeAction,
|
|
form,
|
|
loadingAction,
|
|
openCreateResource,
|
|
openEditResource,
|
|
resourceType,
|
|
selectedResource,
|
|
setForm,
|
|
status,
|
|
submitResource,
|
|
toggleResource,
|
|
};
|
|
}
|
|
|
|
export function useResourceGroupListPage() {
|
|
const abilities = useResourceAbilities();
|
|
const { showToast } = useToast();
|
|
const [query, setQuery] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
const [page, setPage] = useState(1);
|
|
const [activeAction, setActiveAction] = useState("");
|
|
const [form, setForm] = useState(emptyResourceGroupForm);
|
|
const [selectedGroup, setSelectedGroup] = useState(null);
|
|
const [loadingAction, setLoadingAction] = useState("");
|
|
const [resourceOptions, setResourceOptions] = useState([]);
|
|
const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
|
|
const filters = useMemo(
|
|
() => ({
|
|
keyword: query,
|
|
status,
|
|
}),
|
|
[query, status],
|
|
);
|
|
const result = usePaginatedQuery({
|
|
errorMessage: "加载资源组列表失败",
|
|
fetcher: listResourceGroups,
|
|
filters,
|
|
page,
|
|
pageSize,
|
|
queryKey: ["resource-groups", filters, page],
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (activeAction !== "create" && activeAction !== "edit") {
|
|
return undefined;
|
|
}
|
|
let ignore = false;
|
|
setResourceOptionsLoading(true);
|
|
listResources({ page: 1, page_size: 100, status: "active" })
|
|
.then((data) => {
|
|
if (!ignore) {
|
|
setResourceOptions(toGroupResourceOptions(data.items || [], selectedGroup));
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
if (!ignore) {
|
|
showToast(err.message || "加载资源选项失败", "error");
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (!ignore) {
|
|
setResourceOptionsLoading(false);
|
|
}
|
|
});
|
|
return () => {
|
|
ignore = true;
|
|
};
|
|
}, [activeAction, selectedGroup, showToast]);
|
|
|
|
const openCreateGroup = () => {
|
|
setForm(emptyResourceGroupForm());
|
|
setSelectedGroup(null);
|
|
setActiveAction("create");
|
|
};
|
|
|
|
const openEditGroup = (group) => {
|
|
if (!group?.groupId) {
|
|
return;
|
|
}
|
|
setForm(emptyResourceGroupForm(group));
|
|
setSelectedGroup(group);
|
|
setActiveAction("edit");
|
|
};
|
|
|
|
const closeAction = () => {
|
|
setActiveAction("");
|
|
setSelectedGroup(null);
|
|
};
|
|
|
|
const addResourceItem = () => {
|
|
setForm((current) => ({ ...current, items: [...current.items, emptyGroupResourceItem()] }));
|
|
};
|
|
|
|
const addWalletAssetItem = (walletAssetType) => {
|
|
setForm((current) => ({ ...current, items: [...current.items, emptyGroupWalletAssetItem(walletAssetType)] }));
|
|
};
|
|
|
|
const updateGroupItem = (index, patch) => {
|
|
setForm((current) => ({
|
|
...current,
|
|
items: current.items.map((item, itemIndex) => (itemIndex === index ? { ...item, ...patch } : item)),
|
|
}));
|
|
};
|
|
|
|
const removeGroupItem = (index) => {
|
|
setForm((current) => ({
|
|
...current,
|
|
items: current.items.filter((_, itemIndex) => itemIndex !== index),
|
|
}));
|
|
};
|
|
|
|
const submitGroup = async (event) => {
|
|
event.preventDefault();
|
|
const editing = activeAction === "edit";
|
|
const action = editing ? "group-edit" : "group-create";
|
|
const successMessage = editing ? "资源组已更新" : "资源组已创建";
|
|
setLoadingAction(action);
|
|
try {
|
|
const payload = buildResourceGroupPayload(parseForm(resourceGroupCreateFormSchema, form));
|
|
if (editing) {
|
|
await updateResourceGroup(selectedGroup.groupId, payload);
|
|
} else {
|
|
await createResourceGroup(payload);
|
|
}
|
|
showToast(successMessage, "success");
|
|
setForm(emptyResourceGroupForm());
|
|
closeAction();
|
|
await result.reload();
|
|
} catch (err) {
|
|
showToast(err.message || "操作失败", "error");
|
|
} finally {
|
|
setLoadingAction("");
|
|
}
|
|
};
|
|
|
|
const toggleGroup = async (group, nextEnabled = group.status !== "active") => {
|
|
if (!abilities.canUpdateGroup || !group?.groupId) {
|
|
return;
|
|
}
|
|
if ((group.status === "active") === nextEnabled) {
|
|
return;
|
|
}
|
|
setLoadingAction(`group-status-${group.groupId}`);
|
|
try {
|
|
if (nextEnabled) {
|
|
await enableResourceGroup(group.groupId);
|
|
} else {
|
|
await disableResourceGroup(group.groupId);
|
|
}
|
|
showToast(nextEnabled ? "资源组已启用" : "资源组已禁用", "success");
|
|
await result.reload();
|
|
} catch (err) {
|
|
showToast(err.message || "操作失败", "error");
|
|
} finally {
|
|
setLoadingAction("");
|
|
}
|
|
};
|
|
|
|
return {
|
|
...sharedPageState({ page, query, result, setPage, setQuery }),
|
|
abilities,
|
|
activeAction,
|
|
addResourceItem,
|
|
addWalletAssetItem,
|
|
changeStatus: resetSetter(setStatus, setPage),
|
|
closeAction,
|
|
form,
|
|
loadingAction,
|
|
openCreateGroup,
|
|
openEditGroup,
|
|
removeGroupItem,
|
|
resourceOptions,
|
|
resourceOptionsLoading,
|
|
selectedGroup,
|
|
setForm,
|
|
status,
|
|
submitGroup,
|
|
toggleGroup,
|
|
updateGroupItem,
|
|
};
|
|
}
|
|
|
|
export function useGiftListPage() {
|
|
const abilities = useResourceAbilities();
|
|
const { showToast } = useToast();
|
|
const [query, setQuery] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
const [regionId, setRegionId] = useState("");
|
|
const [page, setPage] = useState(1);
|
|
const [activeAction, setActiveAction] = useState("");
|
|
const [form, setForm] = useState(emptyGiftForm);
|
|
const [selectedGift, setSelectedGift] = useState(null);
|
|
const [loadingAction, setLoadingAction] = useState("");
|
|
const [resourceOptions, setResourceOptions] = useState([]);
|
|
const [resourceOptionsLoading, setResourceOptionsLoading] = useState(false);
|
|
const { loadingRegions, regionOptions } = useRegionOptions();
|
|
const filters = useMemo(
|
|
() => ({
|
|
keyword: query,
|
|
region_id: regionId,
|
|
status,
|
|
}),
|
|
[query, regionId, status],
|
|
);
|
|
const result = usePaginatedQuery({
|
|
errorMessage: "加载礼物列表失败",
|
|
fetcher: listGifts,
|
|
filters,
|
|
page,
|
|
pageSize,
|
|
queryKey: ["gifts", filters, page],
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (activeAction !== "create" && activeAction !== "edit") {
|
|
return undefined;
|
|
}
|
|
let ignore = false;
|
|
setResourceOptionsLoading(true);
|
|
listResources({ page: 1, page_size: 100, resource_type: "gift" })
|
|
.then((data) => {
|
|
if (!ignore) {
|
|
setResourceOptions(toGiftResourceOptions(data.items || [], selectedGift));
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
if (!ignore) {
|
|
showToast(err.message || "加载礼物资源失败", "error");
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (!ignore) {
|
|
setResourceOptionsLoading(false);
|
|
}
|
|
});
|
|
return () => {
|
|
ignore = true;
|
|
};
|
|
}, [activeAction, selectedGift, showToast]);
|
|
|
|
const openCreateGift = () => {
|
|
setForm(emptyGiftForm());
|
|
setSelectedGift(null);
|
|
setActiveAction("create");
|
|
};
|
|
|
|
const openEditGift = (gift) => {
|
|
if (!gift?.giftId) {
|
|
return;
|
|
}
|
|
setForm(emptyGiftForm(gift));
|
|
setSelectedGift(gift);
|
|
setActiveAction("edit");
|
|
};
|
|
|
|
const closeAction = () => {
|
|
setActiveAction("");
|
|
setSelectedGift(null);
|
|
};
|
|
|
|
const submitGift = async (event) => {
|
|
event.preventDefault();
|
|
const editing = activeAction === "edit";
|
|
if (editing && !selectedGift?.giftId) {
|
|
return;
|
|
}
|
|
setLoadingAction(editing ? "gift-edit" : "gift-create");
|
|
try {
|
|
const payload = buildGiftPayload(parseForm(giftFormSchema, form));
|
|
if (editing) {
|
|
await updateGift(selectedGift.giftId, payload);
|
|
} else {
|
|
await createGift(payload);
|
|
}
|
|
showToast(editing ? "礼物已更新" : "礼物已创建", "success");
|
|
closeAction();
|
|
await result.reload();
|
|
} catch (err) {
|
|
showToast(err.message || "操作失败", "error");
|
|
} finally {
|
|
setLoadingAction("");
|
|
}
|
|
};
|
|
|
|
const toggleGift = async (gift, nextEnabled = gift.status !== "active") => {
|
|
if (!abilities.canStatusGift || !gift?.giftId) {
|
|
return;
|
|
}
|
|
if ((gift.status === "active") === nextEnabled) {
|
|
return;
|
|
}
|
|
setLoadingAction(`gift-status-${gift.giftId}`);
|
|
try {
|
|
if (nextEnabled) {
|
|
await enableGift(gift.giftId);
|
|
} else {
|
|
await disableGift(gift.giftId);
|
|
}
|
|
showToast(nextEnabled ? "礼物已启用" : "礼物已禁用", "success");
|
|
await result.reload();
|
|
} catch (err) {
|
|
showToast(err.message || "操作失败", "error");
|
|
} finally {
|
|
setLoadingAction("");
|
|
}
|
|
};
|
|
|
|
return {
|
|
...sharedPageState({ page, query, result, setPage, setQuery }),
|
|
abilities,
|
|
activeAction,
|
|
changeRegionId: resetSetter(setRegionId, setPage),
|
|
changeStatus: resetSetter(setStatus, setPage),
|
|
closeAction,
|
|
form,
|
|
loadingAction,
|
|
loadingRegions,
|
|
openCreateGift,
|
|
openEditGift,
|
|
regionId,
|
|
regionOptions,
|
|
resourceOptions,
|
|
resourceOptionsLoading,
|
|
selectedGift,
|
|
setForm,
|
|
status,
|
|
submitGift,
|
|
toggleGift,
|
|
};
|
|
}
|
|
|
|
export function useResourceGrantListPage() {
|
|
const abilities = useResourceAbilities();
|
|
const { showToast } = useToast();
|
|
const [query, setQuery] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
const [page, setPage] = useState(1);
|
|
const [activeAction, setActiveAction] = useState("");
|
|
const [form, setForm] = useState(emptyGrantForm);
|
|
const [resourceOptions, setResourceOptions] = useState([]);
|
|
const [groupOptions, setGroupOptions] = useState([]);
|
|
const [optionsLoading, setOptionsLoading] = useState(false);
|
|
const [loadingAction, setLoadingAction] = useState("");
|
|
const filters = useMemo(
|
|
() => ({
|
|
status,
|
|
target_user_id: query.trim(),
|
|
}),
|
|
[query, status],
|
|
);
|
|
const result = usePaginatedQuery({
|
|
errorMessage: "加载资源赠送记录失败",
|
|
fetcher: listResourceGrants,
|
|
filters,
|
|
page,
|
|
pageSize,
|
|
queryKey: ["resource-grants", filters, page],
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (activeAction !== "create") {
|
|
return undefined;
|
|
}
|
|
let ignore = false;
|
|
setOptionsLoading(true);
|
|
Promise.all([
|
|
listResources({ page: 1, page_size: 100, status: "active" }),
|
|
listResourceGroups({ page: 1, page_size: 100, status: "active" }),
|
|
])
|
|
.then(([resources, groups]) => {
|
|
if (!ignore) {
|
|
setResourceOptions((resources.items || []).filter((resource) => resource.grantable !== false));
|
|
setGroupOptions(groups.items || []);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
if (!ignore) {
|
|
showToast(err.message || "加载资源选项失败", "error");
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (!ignore) {
|
|
setOptionsLoading(false);
|
|
}
|
|
});
|
|
return () => {
|
|
ignore = true;
|
|
};
|
|
}, [activeAction, showToast]);
|
|
|
|
const openCreateGrant = () => {
|
|
setForm(emptyGrantForm());
|
|
setActiveAction("create");
|
|
};
|
|
|
|
const closeAction = () => {
|
|
setActiveAction("");
|
|
};
|
|
|
|
const submitGrant = async (event) => {
|
|
event.preventDefault();
|
|
if (!abilities.canCreateGrant) {
|
|
return;
|
|
}
|
|
setLoadingAction("grant-create");
|
|
try {
|
|
const payload = buildGrantPayload(parseForm(resourceGrantFormSchema, form));
|
|
if (payload.subjectType === "resource") {
|
|
await grantResource(payload.resource);
|
|
} else {
|
|
await grantResourceGroup(payload.group);
|
|
}
|
|
showToast("资源已赠送", "success");
|
|
closeAction();
|
|
setForm(emptyGrantForm());
|
|
await result.reload();
|
|
} catch (err) {
|
|
showToast(err.message || "操作失败", "error");
|
|
} finally {
|
|
setLoadingAction("");
|
|
}
|
|
};
|
|
|
|
return {
|
|
...sharedPageState({ page, query, result, setPage, setQuery }),
|
|
abilities,
|
|
activeAction,
|
|
changeStatus: resetSetter(setStatus, setPage),
|
|
closeAction,
|
|
form,
|
|
groupOptions,
|
|
loadingAction,
|
|
openCreateGrant,
|
|
optionsLoading,
|
|
resourceOptions,
|
|
setForm,
|
|
status,
|
|
submitGrant,
|
|
};
|
|
}
|
|
|
|
function sharedPageState({ page, query, result, setPage, setQuery }) {
|
|
return {
|
|
data: result.data || emptyData,
|
|
error: result.error,
|
|
loading: result.loading,
|
|
page,
|
|
query,
|
|
reload: result.reload,
|
|
setPage,
|
|
changeQuery: resetSetter(setQuery, setPage),
|
|
};
|
|
}
|
|
|
|
function resetSetter(setValue, setPage) {
|
|
return (value) => {
|
|
setValue(value);
|
|
setPage(1);
|
|
};
|
|
}
|
|
|
|
function buildResourcePayload(form) {
|
|
const resourceType = form.resourceType;
|
|
const isCoin = resourceType === "coin";
|
|
return {
|
|
amount: isCoin ? Number(form.walletAssetAmount) : 0,
|
|
assetUrl: form.assetUrl.trim(),
|
|
name: form.name.trim(),
|
|
previewUrl: form.previewUrl.trim(),
|
|
resourceCode: form.resourceCode.trim(),
|
|
resourceType,
|
|
sortOrder: 0,
|
|
status: form.enabled ? "active" : "disabled",
|
|
};
|
|
}
|
|
|
|
function buildResourceGroupPayload(form) {
|
|
return {
|
|
description: (form.description || "").trim(),
|
|
groupCode: form.groupCode.trim(),
|
|
items: form.items.map((item, index) => {
|
|
const sortOrder = Number(item.sortOrder || index);
|
|
if (item.itemType === "wallet_asset") {
|
|
return {
|
|
amount: Number(item.walletAssetAmount),
|
|
assetType: item.walletAssetType,
|
|
itemType: "wallet_asset",
|
|
sortOrder: Number.isInteger(sortOrder) ? sortOrder : index,
|
|
};
|
|
}
|
|
return {
|
|
durationDays: Number(item.durationDays),
|
|
itemType: "resource",
|
|
resourceId: Number(item.resourceId),
|
|
sortOrder: Number.isInteger(sortOrder) ? sortOrder : index,
|
|
};
|
|
}),
|
|
name: form.name.trim(),
|
|
sortOrder: Number(form.sortOrder || 0),
|
|
status: form.enabled ? "active" : "disabled",
|
|
};
|
|
}
|
|
|
|
function buildGiftPayload(form) {
|
|
return {
|
|
coinPrice: Number(form.coinPrice),
|
|
effectiveAtMs: Date.now(),
|
|
giftId: form.giftId.trim(),
|
|
giftPointAmount: Number(form.giftPointAmount || 0),
|
|
heatValue: Number(form.heatValue || 0),
|
|
name: form.name.trim(),
|
|
presentationJson: form.presentationJson?.trim() || "{}",
|
|
priceVersion: form.priceVersion.trim(),
|
|
regionIds: form.regionIds.map(Number),
|
|
resourceId: Number(form.resourceId),
|
|
sortOrder: Number(form.sortOrder || 0),
|
|
status: form.enabled ? "active" : "disabled",
|
|
};
|
|
}
|
|
|
|
function buildGrantPayload(form) {
|
|
const targetUserId = Number(form.targetUserId);
|
|
const reason = form.reason.trim();
|
|
const commandId = makeCommandId(form.subjectType === "resource" ? "resource-grant" : "resource-group-grant");
|
|
if (form.subjectType === "resource") {
|
|
return {
|
|
resource: {
|
|
commandId,
|
|
durationMs: Math.round(Number(form.durationDays || 0) * dayMillis),
|
|
quantity: Number(form.quantity || 1),
|
|
reason,
|
|
resourceId: Number(form.resourceId),
|
|
targetUserId,
|
|
},
|
|
subjectType: "resource",
|
|
};
|
|
}
|
|
return {
|
|
group: {
|
|
commandId,
|
|
groupId: Number(form.groupId),
|
|
reason,
|
|
targetUserId,
|
|
},
|
|
subjectType: "resource_group",
|
|
};
|
|
}
|
|
|
|
function makeCommandId(prefix) {
|
|
return `${prefix}-${Date.now()}`;
|
|
}
|
|
|
|
function toGroupResourceOptions(items, selectedGroup) {
|
|
const selectedResources = (selectedGroup?.items || [])
|
|
.map((item) => item.resource)
|
|
.filter((resource) => resource?.resourceId);
|
|
return uniqueResources([...items, ...selectedResources]).filter(
|
|
(resource) => resource.resourceType !== "coin" && resource.status === "active" && resource.grantable !== false,
|
|
);
|
|
}
|
|
|
|
function toGiftResourceOptions(items, selectedGift) {
|
|
const selectedResource = selectedGift?.resource?.resourceId
|
|
? [selectedGift.resource]
|
|
: selectedGift?.resourceId
|
|
? [{ name: String(selectedGift.resourceId), resourceId: selectedGift.resourceId, resourceType: "gift" }]
|
|
: [];
|
|
return uniqueResources([...items, ...selectedResource]).filter(
|
|
(resource) => resource.resourceType === "gift" || resource.resourceId === selectedGift?.resourceId,
|
|
);
|
|
}
|
|
|
|
function uniqueResources(resources) {
|
|
const seen = new Set();
|
|
return resources.filter((resource) => {
|
|
if (!resource?.resourceId || seen.has(resource.resourceId)) {
|
|
return false;
|
|
}
|
|
seen.add(resource.resourceId);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function formatDurationDays(durationMs) {
|
|
const days = Number(durationMs || 0) / dayMillis;
|
|
return Number.isInteger(days) ? String(days) : String(days.toFixed(1));
|
|
}
|