197 lines
7.9 KiB
JavaScript
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);
|
|
}
|