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); }