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 = [] } = {}) {
|
export function normalizeDashboard({ apps = [], perApp = [] } = {}) {
|
||||||
const luckyGifts = perApp.flatMap((entry) => entry.luckyGifts || []);
|
const luckyGifts = perApp.flatMap((entry) => entry.luckyGifts || []);
|
||||||
const poolBalances = perApp
|
const normalizedPoolBalances = perApp
|
||||||
.flatMap((entry) => (entry.poolBalances || []).map((item) => normalizePoolBalance(item, entry.appCode)))
|
.flatMap((entry) => (entry.poolBalances || []).map((item) => normalizePoolBalance(item, entry.appCode)))
|
||||||
.filter((item) => item.app_code);
|
.filter((item) => item.app_code);
|
||||||
const appSummaries = perApp
|
const appSummaries = perApp
|
||||||
@ -157,8 +157,32 @@ export function normalizeDashboard({ apps = [], perApp = [] } = {}) {
|
|||||||
.filter((summary) => summary.app_code);
|
.filter((summary) => summary.app_code);
|
||||||
|
|
||||||
const publishedConfigs = luckyGifts.filter((config) => !config.is_default);
|
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 = {
|
const summary = {
|
||||||
activeApps: apps.length,
|
activeApps: apps.length,
|
||||||
configuredPools: publishedConfigs.length,
|
configuredPools: publishedConfigs.length,
|
||||||
@ -166,9 +190,9 @@ export function normalizeDashboard({ apps = [], perApp = [] } = {}) {
|
|||||||
failedDraws: sumBy(appSummaries, "failed_draws"),
|
failedDraws: sumBy(appSummaries, "failed_draws"),
|
||||||
grantedDraws: sumBy(appSummaries, "granted_draws"),
|
grantedDraws: sumBy(appSummaries, "granted_draws"),
|
||||||
pendingDraws: sumBy(appSummaries, "pending_draws"),
|
pendingDraws: sumBy(appSummaries, "pending_draws"),
|
||||||
poolAvailableTotal: sumBy(poolBalances, "available_balance", "availableBalance"),
|
poolAvailableTotal: sumBy(currentPoolBalances, "available_balance", "availableBalance"),
|
||||||
poolBalanceTotal: sumBy(poolBalances, "balance", "Balance"),
|
poolBalanceTotal: sumBy(currentPoolBalances, "balance", "Balance"),
|
||||||
poolReserveTotal: sumBy(poolBalances, "reserve_floor", "reserveFloor"),
|
poolReserveTotal: sumBy(currentPoolBalances, "reserve_floor", "reserveFloor"),
|
||||||
totalDraws: sumBy(appSummaries, "total_draws"),
|
totalDraws: sumBy(appSummaries, "total_draws"),
|
||||||
totalRewardCoins: sumBy(appSummaries, "total_reward_coins"),
|
totalRewardCoins: sumBy(appSummaries, "total_reward_coins"),
|
||||||
totalSpentCoins: sumBy(appSummaries, "total_spent_coins"),
|
totalSpentCoins: sumBy(appSummaries, "total_spent_coins"),
|
||||||
@ -208,6 +232,14 @@ export function luckyGiftPoolKey(item = {}) {
|
|||||||
return `${appCode}:${poolID}:${strategyVersion}`;
|
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 = "") {
|
function normalizeDrawSummary(summary = {}, appCode = "") {
|
||||||
return {
|
return {
|
||||||
actual_rtp_ppm: numberValue(summary.actual_rtp_ppm ?? summary.actualRtpPpm),
|
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 CardGiftcardOutlined from "@mui/icons-material/CardGiftcardOutlined";
|
||||||
import CasinoOutlined from "@mui/icons-material/CasinoOutlined";
|
import CasinoOutlined from "@mui/icons-material/CasinoOutlined";
|
||||||
import SavingsOutlined from "@mui/icons-material/SavingsOutlined";
|
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 { DataTable } from "@/shared/ui/DataTable.jsx";
|
||||||
import { KpiCard } from "@/shared/ui/KpiCard.jsx";
|
import { KpiCard } from "@/shared/ui/KpiCard.jsx";
|
||||||
import { TimeText } from "@/shared/ui/TimeText.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 }) {
|
export function OverviewView({ canCredit, canDebit, data, disabled, onCredit, onDebit }) {
|
||||||
const { appSummaries, poolBalances, summary } = data;
|
const { appSummaries, poolBalances, summary } = data;
|
||||||
|
const [poolScope, setPoolScope] = useState("current");
|
||||||
const poolBalanceColumns = useMemo(
|
const poolBalanceColumns = useMemo(
|
||||||
() => buildPoolBalanceColumns({ canCredit, canDebit, disabled, onCredit, onDebit }),
|
() => buildPoolBalanceColumns({ canCredit, canDebit, disabled, onCredit, onDebit }),
|
||||||
[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 (
|
return (
|
||||||
<div className="ops-view">
|
<div className="ops-view">
|
||||||
@ -43,21 +63,35 @@ export function OverviewView({ canCredit, canDebit, data, disabled, onCredit, on
|
|||||||
/>
|
/>
|
||||||
<KpiCard
|
<KpiCard
|
||||||
icon={SavingsOutlined}
|
icon={SavingsOutlined}
|
||||||
label="奖池金币"
|
label="当前奖池金币"
|
||||||
sub={`可用 ${formatNumber(summary.poolAvailableTotal)} · 保底 ${formatNumber(summary.poolReserveTotal)}`}
|
sub={`可用 ${formatNumber(summary.poolAvailableTotal)} · 保底 ${formatNumber(summary.poolReserveTotal)}`}
|
||||||
tone="success"
|
tone="success"
|
||||||
value={formatNumber(summary.poolBalanceTotal)}
|
value={formatNumber(summary.poolBalanceTotal)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AdminListToolbar
|
||||||
|
filters={
|
||||||
|
<AdminFilterSelect
|
||||||
|
label="奖池范围"
|
||||||
|
options={[
|
||||||
|
["current", `当前奖池 (${poolCounts.current})`],
|
||||||
|
["history", `历史奖池 (${poolCounts.history})`],
|
||||||
|
["all", `全部奖池 (${poolCounts.all})`],
|
||||||
|
]}
|
||||||
|
value={poolScope}
|
||||||
|
onChange={setPoolScope}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={poolBalanceColumns}
|
columns={poolBalanceColumns}
|
||||||
emptyLabel="暂无奖池余额"
|
emptyLabel={poolScope === "history" ? "暂无历史奖池" : "暂无奖池余额"}
|
||||||
items={poolBalances}
|
items={visiblePoolBalances}
|
||||||
minWidth="1480px"
|
minWidth="1480px"
|
||||||
rowKey={luckyGiftPoolKey}
|
rowKey={luckyGiftPoolKey}
|
||||||
title="奖池实时水位"
|
title="奖池实时水位"
|
||||||
total={poolBalances.length}
|
total={visiblePoolBalances.length}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
@ -82,12 +116,16 @@ function buildPoolBalanceColumns({ canCredit, canDebit, disabled, onCredit, onDe
|
|||||||
key: "materialized",
|
key: "materialized",
|
||||||
label: "水位状态",
|
label: "水位状态",
|
||||||
// materialized=false 是 owner 根据当前策略推导的初始水位;仍允许补水或扣水,首次调整由 owner 原子物化该策略奖池。
|
// materialized=false 是 owner 根据当前策略推导的初始水位;仍允许补水或扣水,首次调整由 owner 原子物化该策略奖池。
|
||||||
render: (item) =>
|
render: (item) => {
|
||||||
item.materialized === false ? (
|
if (item.is_historical) {
|
||||||
|
return <OpsStatusBadge label="历史奖池" tone="stopped" />;
|
||||||
|
}
|
||||||
|
return item.materialized === false ? (
|
||||||
<OpsStatusBadge label="默认水位" />
|
<OpsStatusBadge label="默认水位" />
|
||||||
) : (
|
) : (
|
||||||
<OpsStatusBadge label="已入账" tone="succeeded" />
|
<OpsStatusBadge label="已入账" tone="succeeded" />
|
||||||
),
|
);
|
||||||
|
},
|
||||||
width: "minmax(132px, 0.9fr)",
|
width: "minmax(132px, 0.9fr)",
|
||||||
},
|
},
|
||||||
{ key: "balance", label: "当前余额", render: (item) => formatNumber(item.balance) },
|
{ key: "balance", label: "当前余额", render: (item) => formatNumber(item.balance) },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user