import { useCallback, useEffect, useMemo, useState } from "react"; import { drawStatusOptions } from "@/features/lucky-gift/constants.js"; import { AdminFilterResetButton, AdminFilterSelect, AdminListToolbar, AdminSearchBox } from "@/shared/ui/AdminListLayout.jsx"; import { DataTable } from "@/shared/ui/DataTable.jsx"; import { TimeText } from "@/shared/ui/TimeText.jsx"; import { fetchLuckyGiftDraws } from "../api.js"; import { formatMultiplierFromPPM, formatNumber } from "../format.js"; import { OpsStatusBadge } from "./OpsStatusBadge.jsx"; const PAGE_SIZE = 20; const initialFilters = { poolId: "", status: "", userId: "" }; export function DrawsView({ apps, defaultAppCode, poolIDsByApp }) { // 抽奖流水量大且服务端分页,必须选定应用后按需拉取,不能像配置那样一次拉全。 const [appCode, setAppCode] = useState(defaultAppCode); const [filters, setFilters] = useState(initialFilters); const [page, setPage] = useState(1); const [state, setState] = useState({ error: "", items: [], loading: false, total: 0 }); useEffect(() => { // 首屏 dashboard 加载完成前 defaultAppCode 为空,就绪后自动选中第一个应用。 setAppCode((current) => current || defaultAppCode); }, [defaultAppCode]); useEffect(() => { if (!appCode) { return undefined; } let cancelled = false; setState((current) => ({ ...current, error: "", loading: true })); fetchLuckyGiftDraws({ appCode, page, pageSize: PAGE_SIZE, poolId: filters.poolId, status: filters.status, userId: filters.userId }) .then((result) => { if (cancelled) { return; } setState((current) => ({ error: "", // 滚动加载:翻页时向已加载列表追加;筛选变化会把 page 重置为 1,从头替换。 items: page > 1 ? [...current.items, ...result.items] : result.items, loading: false, total: result.total })); }) .catch((error) => { if (cancelled) { return; } setState((current) => ({ ...current, error: error.message || "获取抽奖记录失败", loading: false })); }); return () => { cancelled = true; }; }, [appCode, filters, page]); const changeAppCode = useCallback((nextAppCode) => { setAppCode(nextAppCode); // 奖池归属于应用,切换应用后旧奖池筛选一定失效,一并重置。 setFilters(initialFilters); setPage(1); }, []); const changeFilter = useCallback((key, value) => { setFilters((current) => ({ ...current, [key]: value })); setPage(1); }, []); const resetFilters = useCallback(() => { setFilters(initialFilters); setPage(1); }, []); const appOptions = useMemo(() => apps.map((item) => [item.app_code ?? item.appCode, item.app_code ?? item.appCode]), [apps]); const poolOptions = useMemo(() => (poolIDsByApp.get(appCode) || []).map((poolID) => [poolID, poolID]), [appCode, poolIDsByApp]); const hasActiveFilters = Boolean(filters.poolId || filters.status || filters.userId); return (
changeFilter("poolId", value)} /> changeFilter("status", value)} /> changeFilter("userId", value)} /> } /> {state.error ?
{state.error}
: null} item.draw_id ?? item.drawId} title="抽奖记录" total={state.total} />
); } const drawColumns = [ { key: "created_at_ms", label: "抽奖时间", render: (item) => , width: "minmax(150px, 1fr)" }, { key: "app_code", label: "应用", render: (item) => item.app_code ?? item.appCode, width: "minmax(80px, 0.6fr)" }, { key: "user", label: "用户", // 外部接入应用只有 external_user_id,内部应用只有数字 user_id,两者互斥,取有值的一侧。 render: (item) => item.external_user_id || item.externalUserId || item.user_id || item.userId || "-", width: "minmax(120px, 1fr)" }, { key: "pool_id", label: "奖池", render: (item) => item.pool_id ?? item.poolId, width: "minmax(100px, 0.8fr)" }, { key: "gift_id", label: "礼物", render: (item) => item.gift_id ?? item.giftId, width: "minmax(100px, 0.8fr)" }, { key: "multiplier_ppm", label: "命中倍率", render: (item) => formatMultiplierFromPPM(item.multiplier_ppm ?? item.multiplierPpm), width: "minmax(88px, 0.6fr)" }, { key: "effective_reward_coins", label: "中奖金币", render: (item) => formatNumber(item.effective_reward_coins ?? item.effectiveRewardCoins) }, { key: "reward_status", label: "发放状态", render: (item) => renderRewardStatus(item.reward_status ?? item.rewardStatus), width: "minmax(124px, 0.8fr)" }, { key: "rule_version", label: "规则版本", render: (item) => `v${item.rule_version ?? item.ruleVersion ?? "-"}`, width: "minmax(80px, 0.5fr)" } ]; function renderRewardStatus(status) { if (status === "granted") { return ; } if (status === "failed") { return ; } if (status === "pending") { return ; } return ; }