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