2026-05-20 14:34:41 +08:00

197 lines
7.9 KiB
JavaScript

import { useCallback, useEffect, useState } from "react";
import { parseForm } from "@/shared/forms/validation";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import { listResourceGroups } from "@/features/resources/api";
import { getRoomTreasureConfig, updateRoomTreasureConfig } from "@/features/room-treasure/api";
import { useRoomTreasureAbilities } from "@/features/room-treasure/permissions.js";
import { roomTreasureConfigFormSchema } from "@/features/room-treasure/schema";
const defaultThresholds = [1000, 3000, 6000, 10000, 15000, 21000, 28000];
export function useRoomTreasurePage() {
const abilities = useRoomTreasureAbilities();
const { showToast } = useToast();
const [config, setConfig] = useState(null);
const [configError, setConfigError] = useState("");
const [configLoading, setConfigLoading] = useState(false);
const [configSaving, setConfigSaving] = useState(false);
const [form, setForm] = useState(emptyRoomTreasureForm());
const [resourceGroups, setResourceGroups] = useState([]);
const reloadConfig = useCallback(async () => {
setConfigLoading(true);
setConfigError("");
try {
const [nextConfig, groups] = await Promise.all([
getRoomTreasureConfig(),
listResourceGroups({ page: 1, page_size: 100, status: "active" }),
]);
setConfig(nextConfig);
setForm(formFromConfig(nextConfig));
setResourceGroups(groups.items || []);
} catch (err) {
const message = err.message || "加载房间宝箱配置失败";
setConfigError(message);
showToast(message, "error");
} finally {
setConfigLoading(false);
}
}, [showToast]);
useEffect(() => {
void reloadConfig();
}, [reloadConfig]);
const resetForm = useCallback(() => {
setForm(config ? formFromConfig(config) : emptyRoomTreasureForm());
}, [config]);
const submitConfig = async (event, onSaved) => {
event.preventDefault();
if (!abilities.canUpdate) {
return;
}
setConfigSaving(true);
try {
const parsed = parseForm(roomTreasureConfigFormSchema, form);
const saved = await updateRoomTreasureConfig(payloadFromForm(parsed));
setConfig(saved);
setForm(formFromConfig(saved));
showToast("房间宝箱配置已保存", "success");
onSaved?.(saved);
} catch (err) {
showToast(err.message || "保存房间宝箱配置失败", "error");
} finally {
setConfigSaving(false);
}
};
return {
abilities,
config,
configError,
configLoading,
configSaving,
form,
reloadConfig,
resetForm,
resourceGroups,
setForm,
submitConfig,
};
}
export function emptyRoomTreasureForm() {
return {
broadcastDelayMs: "0",
broadcastEnabled: true,
broadcastScope: "region",
enabled: false,
energySource: "gift_point_added",
giftEnergyRules: [],
levels: Array.from({ length: 7 }, (_, index) => ({
animationUrl: "",
coverUrl: "",
energyThreshold: String(defaultThresholds[index]),
igniterRewards: [],
inRoomRewards: [],
level: index + 1,
openedImageUrl: "",
openingAnimationUrl: "",
top1Rewards: [],
})),
openDelayMs: "30000",
rewardStackPolicy: "allow_stack",
};
}
function formFromConfig(config) {
const fallback = emptyRoomTreasureForm();
return {
broadcastDelayMs: String(config?.broadcastDelayMs ?? fallback.broadcastDelayMs),
broadcastEnabled: Boolean(config?.broadcastEnabled),
broadcastScope: config?.broadcastScope || fallback.broadcastScope,
enabled: Boolean(config?.enabled),
energySource: config?.energySource || fallback.energySource,
giftEnergyRules: (config?.giftEnergyRules || []).map((rule, index) => ({
excluded: Boolean(rule.excluded),
fixedEnergy: String(rule.fixedEnergy || 0),
giftId: rule.giftId || "",
giftTypeCode: rule.giftTypeCode || "",
multiplierPpm: String(rule.multiplierPpm || 0),
ruleId: rule.ruleId || `gift_energy_rule_${index + 1}`,
})),
levels: fallback.levels.map((fallbackLevel) => {
const level = config?.levels?.find((item) => Number(item.level) === fallbackLevel.level) || fallbackLevel;
return {
animationUrl: level.animationUrl || "",
coverUrl: level.coverUrl || "",
energyThreshold: String(level.energyThreshold || fallbackLevel.energyThreshold),
igniterRewards: rewardsToForm(level.igniterRewards),
inRoomRewards: rewardsToForm(level.inRoomRewards),
level: fallbackLevel.level,
openedImageUrl: level.openedImageUrl || "",
openingAnimationUrl: level.openingAnimationUrl || "",
top1Rewards: rewardsToForm(level.top1Rewards),
};
}),
openDelayMs: String(config?.openDelayMs ?? fallback.openDelayMs),
rewardStackPolicy: config?.rewardStackPolicy || fallback.rewardStackPolicy,
};
}
function rewardsToForm(rewards = []) {
return rewards.map((reward, index) => ({
displayName: reward.displayName || "",
iconUrl: reward.iconUrl || "",
resourceGroupId: reward.resourceGroupId ? String(reward.resourceGroupId) : "",
rewardItemId: reward.rewardItemId || `reward_${index + 1}`,
weight: String(reward.weight || 100),
}));
}
function payloadFromForm(form) {
return {
broadcastDelayMs: Number(form.broadcastDelayMs || 0),
broadcastEnabled: Boolean(form.broadcastEnabled),
broadcastScope: form.broadcastScope,
enabled: Boolean(form.enabled),
energySource: form.energySource,
giftEnergyRules: form.giftEnergyRules.map((rule, index) => ({
excluded: Boolean(rule.excluded),
fixedEnergy: Number(rule.fixedEnergy || 0),
giftId: String(rule.giftId || "").trim(),
giftTypeCode: String(rule.giftTypeCode || "").trim(),
multiplierPpm: Number(rule.multiplierPpm || 0),
ruleId: String(rule.ruleId || "").trim() || `gift_energy_rule_${index + 1}`,
})),
levels: form.levels
.map((level) => ({
animationUrl: String(level.animationUrl || "").trim(),
coverUrl: String(level.coverUrl || "").trim(),
energyThreshold: Number(level.energyThreshold || 0),
igniterRewards: rewardsFromForm(level.igniterRewards, level.level, "igniter"),
inRoomRewards: rewardsFromForm(level.inRoomRewards, level.level, "in_room"),
level: Number(level.level),
openedImageUrl: String(level.openedImageUrl || "").trim(),
openingAnimationUrl: String(level.openingAnimationUrl || "").trim(),
top1Rewards: rewardsFromForm(level.top1Rewards, level.level, "top1"),
}))
.sort((left, right) => left.level - right.level),
openDelayMs: Number(form.openDelayMs || 0),
rewardStackPolicy: form.rewardStackPolicy,
};
}
function rewardsFromForm(rewards = [], level, role) {
return rewards
.map((reward, index) => ({
displayName: String(reward.displayName || "").trim(),
iconUrl: String(reward.iconUrl || "").trim(),
resourceGroupId: Number(reward.resourceGroupId || 0),
rewardItemId: String(reward.rewardItemId || "").trim() || `level_${level}_${role}_${index + 1}`,
weight: Number(reward.weight || 0),
}))
.filter((reward) => reward.resourceGroupId || reward.weight || reward.displayName || reward.iconUrl);
}