183 lines
9.8 KiB
JavaScript
183 lines
9.8 KiB
JavaScript
import { CoinIcon, ActiveUsersIcon, CrownIcon, StarUserIcon, TrendIcon, UserPlusIcon } from "../components/MetricIcons.jsx";
|
|
import { countryFlag, resolveCountryMeta } from "./countryMeta.js";
|
|
import { formatMoney, formatMoneyFull, formatNumber, formatPercent, formatWholeMoney } from "../utils/format.js";
|
|
import { numberValue, ratio } from "../utils/number.js";
|
|
import { formatDateTime } from "../utils/time.js";
|
|
import { sampleOverview } from "./sampleOverview.js";
|
|
|
|
export function createDashboardModel(overview, { appCode, countryId, preview }) {
|
|
const source = overview || sampleOverview;
|
|
const recharge = numberValue(source.recharge_usd_minor);
|
|
const activeUsers = numberValue(source.active_users);
|
|
const paidUsers = numberValue(source.paid_users);
|
|
const newUsers = numberValue(source.new_users);
|
|
const countryBreakdown = normalizeCountries(source, countryId);
|
|
const revenueSeries = normalizeRevenueSeries(source);
|
|
const payoutDistribution = normalizeDistribution(source);
|
|
const gameRanking = normalizeGameRanking(source);
|
|
|
|
return {
|
|
appCode,
|
|
businessKpis: [
|
|
{ caption: "近 7 日", delta: preview ? "+16.80%" : "", label: "礼物消费", value: formatWholeMoney(source.gift_coin_spent) },
|
|
{ caption: "近 7 日", delta: preview ? "+12.93%" : "", label: "幸运礼物流水", value: formatWholeMoney(source.room_lucky_gift_turnover ?? source.lucky_gift_turnover) },
|
|
{ caption: "近 7 日", delta: preview ? "+8.15%" : "", label: "幸运礼物返奖", value: formatWholeMoney(source.lucky_gift_payout) },
|
|
{ caption: "近 7 日", delta: preview ? "+4.66%" : "", label: "幸运礼物利润", value: formatWholeMoney(source.lucky_gift_profit) },
|
|
{ caption: "近 7 日", delta: preview ? "+14.20%" : "", label: "游戏流水", value: formatWholeMoney(source.game_turnover) },
|
|
{ caption: "近 7 日", delta: preview ? "+18.18%" : "", label: "游戏利润", value: formatWholeMoney(source.game_profit) }
|
|
],
|
|
countryBreakdown,
|
|
funnel: [
|
|
{ name: "访客", rate: 1, value: newUsers },
|
|
{ name: "活跃用户", rate: ratio(activeUsers, newUsers), value: activeUsers },
|
|
{ name: "付费用户", rate: ratio(paidUsers, activeUsers), value: paidUsers },
|
|
{ name: "充值用户", rate: ratio(numberValue(source.lucky_gift_payers), activeUsers), value: numberValue(source.lucky_gift_payers) }
|
|
],
|
|
gameMetrics: [
|
|
{ caption: "近 7 日 +14.20%", label: "流水", value: formatWholeMoney(source.game_turnover) },
|
|
{ caption: "近 7 日 +9.63%", label: "派奖", value: formatWholeMoney(source.game_payout) },
|
|
{ caption: "近 7 日 -3.58%", label: "退款", value: formatWholeMoney(source.game_refund) },
|
|
{ caption: "近 7 日 +18.18%", label: "利润", value: formatWholeMoney(source.game_profit) }
|
|
],
|
|
gameRanking,
|
|
giftRanking: normalizeGiftRanking(source),
|
|
kpis: [
|
|
{ caption: "近 7 日", delta: preview ? "+18.10%" : "", icon: CoinIcon, label: "总充值", value: formatMoneyFull(recharge) },
|
|
{ caption: "近 7 日", delta: preview ? "+16.80%" : "", icon: UserPlusIcon, label: "新用户充值", value: formatMoneyFull(source.new_user_recharge_usd_minor) },
|
|
{ caption: "近 7 日", delta: preview ? "+10.77%" : "", icon: ActiveUsersIcon, label: "活跃用户", value: formatNumber(activeUsers) },
|
|
{ caption: "近 7 日", delta: preview ? "+12.92%" : "", icon: CrownIcon, label: "付费用户", value: formatNumber(paidUsers) },
|
|
{ caption: "近 7 日", delta: preview ? "-0.80%" : "", deltaTone: "down", icon: TrendIcon, label: "ARPU", value: formatMoney(source.arpu_usd_minor) },
|
|
{ caption: "近 7 日", delta: preview ? "+3.06%" : "", icon: StarUserIcon, label: "ARPPU", value: formatMoney(source.arppu_usd_minor) }
|
|
],
|
|
luckyMetrics: [
|
|
{ caption: "近 7 日 +10.62%", label: "流水", value: formatWholeMoney(source.lucky_gift_turnover) },
|
|
{ caption: "近 7 日 +8.15%", label: "返奖", value: formatWholeMoney(source.lucky_gift_payout) },
|
|
{ caption: "近 7 日 +4.66%", label: "利润", value: formatWholeMoney(source.lucky_gift_profit) },
|
|
{ caption: "近 7 日 -2.01pp", label: "返奖率", value: formatPercent(source.lucky_gift_payout_rate) },
|
|
{ caption: "近 7 日 +0.76pp", label: "利润率", value: formatPercent(source.lucky_gift_profit_rate) }
|
|
],
|
|
payoutDistribution,
|
|
revenueSeries,
|
|
roomMetrics: [
|
|
{ caption: "近 7 日 +16.80%", label: "礼物消费(充值)", value: formatWholeMoney(source.gift_coin_spent) },
|
|
{ caption: "近 7 日 +12.93%", label: "幸运礼物流水", value: formatWholeMoney(source.room_lucky_gift_turnover ?? source.lucky_gift_turnover) }
|
|
],
|
|
sideMetrics: [
|
|
{ caption: "近 7 日 -0.80%", deltaTone: "down", label: "ARPU", value: formatMoney(source.arpu_usd_minor) },
|
|
{ caption: "近 7 日 +0.18pp", label: "付费转化率", value: formatPercent(ratio(paidUsers, newUsers)) },
|
|
{ caption: "近 7 日 +3.06%", label: "ARPPU", value: formatMoney(source.arppu_usd_minor) }
|
|
],
|
|
topCountries: countryBreakdown.slice(0, 5),
|
|
updatedAt: formatDateTime(source.updated_at_ms || source.updatedAtMs || Date.now())
|
|
};
|
|
}
|
|
|
|
function normalizeRevenueSeries(source) {
|
|
if (Array.isArray(source.daily_series) && source.daily_series.length) {
|
|
return source.daily_series.map((item) => ({
|
|
coin_seller: numberValue(item.coin_seller ?? item.coinSeller),
|
|
google: numberValue(item.google),
|
|
label: localizeSeriesLabel(item.label || item.stat_day || item.day || "-"),
|
|
lineTotal: numberValue(item.line_total ?? item.lineTotal ?? item.trend_total ?? item.trendTotal ?? item.total ?? item.recharge_usd_minor),
|
|
mifapay: numberValue(item.mifapay ?? item.mifa_pay),
|
|
total: numberValue(item.total ?? item.recharge_usd_minor)
|
|
}));
|
|
}
|
|
return [
|
|
{
|
|
coin_seller: numberValue(source.coin_seller_recharge_usd_minor),
|
|
google: numberValue(source.google_recharge_usd_minor),
|
|
label: "当前",
|
|
lineTotal: numberValue(source.recharge_usd_minor),
|
|
mifapay: numberValue(source.mifapay_recharge_usd_minor),
|
|
total: numberValue(source.recharge_usd_minor)
|
|
}
|
|
];
|
|
}
|
|
|
|
function normalizeCountries(source, countryId) {
|
|
const raw = Array.isArray(source.country_breakdown) && source.country_breakdown.length ? source.country_breakdown : null;
|
|
const rows =
|
|
raw ||
|
|
[
|
|
{
|
|
active_users: source.active_users,
|
|
arppu_usd_minor: source.arppu_usd_minor,
|
|
country: countryId ? `国家 ${countryId}` : "全部区域",
|
|
paid_users: source.paid_users,
|
|
recharge_usd_minor: source.recharge_usd_minor,
|
|
x: 56,
|
|
y: 52
|
|
}
|
|
];
|
|
const totalRecharge = rows.reduce((sum, item) => sum + numberValue(item.recharge_usd_minor), 0) || 1;
|
|
return rows
|
|
.map((item, index) => ({
|
|
...normalizeCountryRow(item, index, totalRecharge)
|
|
}))
|
|
.sort((left, right) => right.recharge_usd_minor - left.recharge_usd_minor);
|
|
}
|
|
|
|
function normalizeCountryRow(item, index, totalRecharge) {
|
|
const code = item.country_code || item.countryCode || item.iso_code || item.isoCode;
|
|
const country = item.country || item.country_name || code || `国家 ${index + 1}`;
|
|
const meta = resolveCountryMeta({ code, name: country });
|
|
const recharge = numberValue(item.recharge_usd_minor);
|
|
return {
|
|
active_users: numberValue(item.active_users),
|
|
arpu_usd_minor: numberValue(item.arpu_usd_minor ?? item.arpuUsdMinor),
|
|
arppu_usd_minor: numberValue(item.arppu_usd_minor),
|
|
avg_recharge_usd_minor: numberValue(item.avg_recharge_usd_minor ?? item.avgRechargeUsdMinor),
|
|
country: meta?.label || country,
|
|
country_code: code || meta?.code || "",
|
|
flag: countryFlag(code || meta?.code),
|
|
geo_point: Array.isArray(item.geo_point) ? item.geo_point : meta?.point,
|
|
new_user_recharge_usd_minor: numberValue(item.new_user_recharge_usd_minor ?? item.newUserRechargeUsdMinor),
|
|
paid_users: numberValue(item.paid_users),
|
|
payer_rate: ratio(item.paid_users, item.active_users),
|
|
recharge_usd_minor: recharge,
|
|
recharge_conversion_rate: numberValue(item.recharge_conversion_rate ?? item.rechargeConversionRate),
|
|
recharge_users: numberValue(item.recharge_users ?? item.rechargeUsers),
|
|
share: recharge / totalRecharge,
|
|
trend_rate: numberValue(item.trend_rate ?? item.trendRate),
|
|
visitors: numberValue(item.visitors ?? item.new_users ?? item.newUsers)
|
|
};
|
|
}
|
|
|
|
function normalizeGiftRanking(source) {
|
|
if (Array.isArray(source.gift_ranking) && source.gift_ranking.length) {
|
|
return source.gift_ranking.map((item) => ({ name: item.name || item.gift_name || item.gift_id || "-", value: numberValue(item.value ?? item.amount ?? item.coin) }));
|
|
}
|
|
return [{ name: "礼物消费", value: numberValue(source.gift_coin_spent) }];
|
|
}
|
|
|
|
function normalizeDistribution(source) {
|
|
if (Array.isArray(source.payout_distribution) && source.payout_distribution.length) {
|
|
return source.payout_distribution.map((item) => ({ name: item.name || "-", value: numberValue(item.value) }));
|
|
}
|
|
return [
|
|
{ name: "幸运礼物返奖", value: numberValue(source.lucky_gift_payout) },
|
|
{ name: "幸运礼物利润", value: Math.max(numberValue(source.lucky_gift_profit), 0) }
|
|
];
|
|
}
|
|
|
|
function normalizeGameRanking(source) {
|
|
if (Array.isArray(source.game_ranking) && source.game_ranking.length) {
|
|
return source.game_ranking.map((item) => ({
|
|
game_id: item.game_id || item.gameId || "-",
|
|
platform_code: item.platform_code || item.platformCode || "",
|
|
profit_rate: numberValue(item.profit_rate ?? item.profitRate),
|
|
turnover_coin: numberValue(item.turnover_coin ?? item.turnoverCoin)
|
|
}));
|
|
}
|
|
return [{ game_id: "全部游戏", platform_code: "", profit_rate: numberValue(source.game_profit_rate), turnover_coin: numberValue(source.game_turnover) }];
|
|
}
|
|
|
|
function localizeSeriesLabel(label) {
|
|
const match = String(label).match(/^May\s+(\d{1,2})$/i);
|
|
if (match) {
|
|
return `5月${match[1]}日`;
|
|
}
|
|
return label;
|
|
}
|