merge: current and historical lucky pool view
This commit is contained in:
commit
917a68b9d8
@ -149,7 +149,7 @@ export function buildOpsUrl(path, query = {}) {
|
||||
|
||||
export function normalizeDashboard({ apps = [], perApp = [] } = {}) {
|
||||
const luckyGifts = perApp.flatMap((entry) => entry.luckyGifts || []);
|
||||
const poolBalances = perApp
|
||||
const normalizedPoolBalances = perApp
|
||||
.flatMap((entry) => (entry.poolBalances || []).map((item) => normalizePoolBalance(item, entry.appCode)))
|
||||
.filter((item) => item.app_code);
|
||||
const appSummaries = perApp
|
||||
@ -157,8 +157,32 @@ export function normalizeDashboard({ apps = [], perApp = [] } = {}) {
|
||||
.filter((summary) => summary.app_code);
|
||||
|
||||
const publishedConfigs = luckyGifts.filter((config) => !config.is_default);
|
||||
const currentConfigByPool = new Map();
|
||||
luckyGifts.forEach((config) => {
|
||||
const familyKey = luckyGiftPoolFamilyKey(config);
|
||||
const ruleVersion = numberValue(config.rule_version ?? config.ruleVersion);
|
||||
const current = currentConfigByPool.get(familyKey);
|
||||
// 正常接口只返回每个奖池的最新版本;这里仍按最大 rule_version 收敛,避免兼容接口返回多版本时
|
||||
// 由数组顺序把旧策略误判为当前。版本相同则保留首条,确保结果不随返回顺序抖动。
|
||||
if (!current || ruleVersion > current.ruleVersion) {
|
||||
currentConfigByPool.set(familyKey, {
|
||||
ruleVersion,
|
||||
strategyVersion: normalizeStrategyVersion(config.strategy_version ?? config.strategyVersion),
|
||||
});
|
||||
}
|
||||
});
|
||||
// 账本接口会同时返回同一 app+pool 的 V2/V3 独立水位;最新规则配置才决定哪口是当前奖池。
|
||||
// 没有配置可关联的孤立账本继续按当前展示,避免前端误把仍需运营处理的资金静默藏进历史筛选。
|
||||
const poolBalances = normalizedPoolBalances.map((pool) => {
|
||||
const currentStrategy = currentConfigByPool.get(luckyGiftPoolFamilyKey(pool))?.strategyVersion;
|
||||
return {
|
||||
...pool,
|
||||
is_historical: Boolean(currentStrategy && currentStrategy !== pool.strategy_version),
|
||||
};
|
||||
});
|
||||
const currentPoolBalances = poolBalances.filter((pool) => !pool.is_historical);
|
||||
|
||||
// KPI 一律跨应用聚合。旧版只取第一个应用的汇总,导致 lalu 有大量抽奖时首屏"抽奖次数"仍显示 0。
|
||||
// KPI 一律跨应用聚合;奖池金额只汇总当前策略,避免历史 V2 与当前 V3 的独立账本重复计入运营水位。
|
||||
const summary = {
|
||||
activeApps: apps.length,
|
||||
configuredPools: publishedConfigs.length,
|
||||
@ -166,9 +190,9 @@ export function normalizeDashboard({ apps = [], perApp = [] } = {}) {
|
||||
failedDraws: sumBy(appSummaries, "failed_draws"),
|
||||
grantedDraws: sumBy(appSummaries, "granted_draws"),
|
||||
pendingDraws: sumBy(appSummaries, "pending_draws"),
|
||||
poolAvailableTotal: sumBy(poolBalances, "available_balance", "availableBalance"),
|
||||
poolBalanceTotal: sumBy(poolBalances, "balance", "Balance"),
|
||||
poolReserveTotal: sumBy(poolBalances, "reserve_floor", "reserveFloor"),
|
||||
poolAvailableTotal: sumBy(currentPoolBalances, "available_balance", "availableBalance"),
|
||||
poolBalanceTotal: sumBy(currentPoolBalances, "balance", "Balance"),
|
||||
poolReserveTotal: sumBy(currentPoolBalances, "reserve_floor", "reserveFloor"),
|
||||
totalDraws: sumBy(appSummaries, "total_draws"),
|
||||
totalRewardCoins: sumBy(appSummaries, "total_reward_coins"),
|
||||
totalSpentCoins: sumBy(appSummaries, "total_spent_coins"),
|
||||
@ -208,6 +232,14 @@ export function luckyGiftPoolKey(item = {}) {
|
||||
return `${appCode}:${poolID}:${strategyVersion}`;
|
||||
}
|
||||
|
||||
function luckyGiftPoolFamilyKey(item = {}) {
|
||||
const appCode = String(item.app_code || item.appCode || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const poolID = String(item.pool_id || item.poolId || DEFAULT_POOL_ID).trim() || DEFAULT_POOL_ID;
|
||||
return `${appCode}:${poolID}`;
|
||||
}
|
||||
|
||||
function normalizeDrawSummary(summary = {}, appCode = "") {
|
||||
return {
|
||||
actual_rtp_ppm: numberValue(summary.actual_rtp_ppm ?? summary.actualRtpPpm),
|
||||
|
||||
@ -2,7 +2,8 @@ import AppsOutlined from "@mui/icons-material/AppsOutlined";
|
||||
import CardGiftcardOutlined from "@mui/icons-material/CardGiftcardOutlined";
|
||||
import CasinoOutlined from "@mui/icons-material/CasinoOutlined";
|
||||
import SavingsOutlined from "@mui/icons-material/SavingsOutlined";
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { AdminFilterSelect, AdminListToolbar } from "@/shared/ui/AdminListLayout.jsx";
|
||||
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
||||
import { KpiCard } from "@/shared/ui/KpiCard.jsx";
|
||||
import { TimeText } from "@/shared/ui/TimeText.jsx";
|
||||
@ -13,10 +14,29 @@ import { OpsStatusBadge } from "./OpsStatusBadge.jsx";
|
||||
|
||||
export function OverviewView({ canCredit, canDebit, data, disabled, onCredit, onDebit }) {
|
||||
const { appSummaries, poolBalances, summary } = data;
|
||||
const [poolScope, setPoolScope] = useState("current");
|
||||
const poolBalanceColumns = useMemo(
|
||||
() => buildPoolBalanceColumns({ canCredit, canDebit, disabled, onCredit, onDebit }),
|
||||
[canCredit, canDebit, disabled, onCredit, onDebit],
|
||||
);
|
||||
const poolCounts = useMemo(
|
||||
() => ({
|
||||
all: poolBalances.length,
|
||||
current: poolBalances.filter((pool) => !pool.is_historical).length,
|
||||
history: poolBalances.filter((pool) => pool.is_historical).length,
|
||||
}),
|
||||
[poolBalances],
|
||||
);
|
||||
const visiblePoolBalances = useMemo(() => {
|
||||
if (poolScope === "history") {
|
||||
return poolBalances.filter((pool) => pool.is_historical);
|
||||
}
|
||||
if (poolScope === "all") {
|
||||
return poolBalances;
|
||||
}
|
||||
// 当前策略是默认视图;历史账本只从显式筛选进入,避免同名 V2/V3 被误读为重复奖池。
|
||||
return poolBalances.filter((pool) => !pool.is_historical);
|
||||
}, [poolBalances, poolScope]);
|
||||
|
||||
return (
|
||||
<div className="ops-view">
|
||||
@ -43,21 +63,35 @@ export function OverviewView({ canCredit, canDebit, data, disabled, onCredit, on
|
||||
/>
|
||||
<KpiCard
|
||||
icon={SavingsOutlined}
|
||||
label="奖池金币"
|
||||
label="当前奖池金币"
|
||||
sub={`可用 ${formatNumber(summary.poolAvailableTotal)} · 保底 ${formatNumber(summary.poolReserveTotal)}`}
|
||||
tone="success"
|
||||
value={formatNumber(summary.poolBalanceTotal)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AdminListToolbar
|
||||
filters={
|
||||
<AdminFilterSelect
|
||||
label="奖池范围"
|
||||
options={[
|
||||
["current", `当前奖池 (${poolCounts.current})`],
|
||||
["history", `历史奖池 (${poolCounts.history})`],
|
||||
["all", `全部奖池 (${poolCounts.all})`],
|
||||
]}
|
||||
value={poolScope}
|
||||
onChange={setPoolScope}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DataTable
|
||||
columns={poolBalanceColumns}
|
||||
emptyLabel="暂无奖池余额"
|
||||
items={poolBalances}
|
||||
emptyLabel={poolScope === "history" ? "暂无历史奖池" : "暂无奖池余额"}
|
||||
items={visiblePoolBalances}
|
||||
minWidth="1480px"
|
||||
rowKey={luckyGiftPoolKey}
|
||||
title="奖池实时水位"
|
||||
total={poolBalances.length}
|
||||
total={visiblePoolBalances.length}
|
||||
/>
|
||||
|
||||
<DataTable
|
||||
@ -82,12 +116,16 @@ function buildPoolBalanceColumns({ canCredit, canDebit, disabled, onCredit, onDe
|
||||
key: "materialized",
|
||||
label: "水位状态",
|
||||
// materialized=false 是 owner 根据当前策略推导的初始水位;仍允许补水或扣水,首次调整由 owner 原子物化该策略奖池。
|
||||
render: (item) =>
|
||||
item.materialized === false ? (
|
||||
render: (item) => {
|
||||
if (item.is_historical) {
|
||||
return <OpsStatusBadge label="历史奖池" tone="stopped" />;
|
||||
}
|
||||
return item.materialized === false ? (
|
||||
<OpsStatusBadge label="默认水位" />
|
||||
) : (
|
||||
<OpsStatusBadge label="已入账" tone="succeeded" />
|
||||
),
|
||||
);
|
||||
},
|
||||
width: "minmax(132px, 0.9fr)",
|
||||
},
|
||||
{ key: "balance", label: "当前余额", render: (item) => formatNumber(item.balance) },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user