更新数据大屏金币统计列
This commit is contained in:
parent
f5e1a20dd9
commit
a952b4262e
@ -33,8 +33,10 @@ const countryColumns = [
|
|||||||
{ key: "lucky_gift_turnover", label: "幸运礼物流水/利润率", render: (row) => <FlowRateCell rate={row.lucky_gift_profit_rate} value={row.lucky_gift_turnover} />, sortable: false },
|
{ key: "lucky_gift_turnover", label: "幸运礼物流水/利润率", render: (row) => <FlowRateCell rate={row.lucky_gift_profit_rate} value={row.lucky_gift_turnover} />, sortable: false },
|
||||||
{ key: "super_lucky_gift_turnover", label: "超级幸运礼物流水/利润率", render: (row) => <FlowRateCell rate={row.super_lucky_gift_profit_rate} value={row.super_lucky_gift_turnover} />, sortable: false },
|
{ key: "super_lucky_gift_turnover", label: "超级幸运礼物流水/利润率", render: (row) => <FlowRateCell rate={row.super_lucky_gift_profit_rate} value={row.super_lucky_gift_turnover} />, sortable: false },
|
||||||
{ key: "gift_coin_spent", label: "礼物流水", render: (row) => formatOptionalCoin(row.gift_coin_spent), sortable: false },
|
{ key: "gift_coin_spent", label: "礼物流水", render: (row) => formatOptionalCoin(row.gift_coin_spent), sortable: false },
|
||||||
{ key: "coin_total", label: "金币数量(总和)", render: (row) => formatOptionalCoin(row.coin_total), sortable: false },
|
{ key: "coin_total", label: "用户金币数", render: (row) => formatOptionalCoin(row.coin_total), sortable: false },
|
||||||
{ key: "consumed_coin", label: "消耗金币", render: (row) => formatOptionalCoin(row.consumed_coin), sortable: false },
|
{ key: "consumed_coin", label: "消耗金币", render: (row) => formatOptionalCoin(row.consumed_coin), sortable: false },
|
||||||
|
{ key: "output_coin", label: "产出金币", render: (row) => formatOptionalCoin(row.output_coin), sortable: false },
|
||||||
|
{ key: "consume_output_ratio", label: "消耗产出比", render: (row) => <ConsumeOutputCell row={row} />, sortable: false },
|
||||||
{ key: "platform_grant_coin", label: "平台发放金币", render: (row, context) => <PlatformGrantCoinCell context={context} row={row} />, sortable: false },
|
{ key: "platform_grant_coin", label: "平台发放金币", render: (row, context) => <PlatformGrantCoinCell context={context} row={row} />, sortable: false },
|
||||||
{ key: "manual_grant_coin", label: "人工发放金币", render: (row) => formatOptionalCoin(row.manual_grant_coin), sortable: false },
|
{ key: "manual_grant_coin", label: "人工发放金币", render: (row) => formatOptionalCoin(row.manual_grant_coin), sortable: false },
|
||||||
{ key: "salary_usd_minor", label: "工资总和(主播/代理)", render: (row) => formatOptionalMoney(row.salary_usd_minor), sortable: false },
|
{ key: "salary_usd_minor", label: "工资总和(主播/代理)", render: (row) => formatOptionalMoney(row.salary_usd_minor), sortable: false },
|
||||||
@ -736,6 +738,18 @@ function FlowRateCell({ rate, value }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConsumeOutputCell({ row }) {
|
||||||
|
if (isMissing(row.consume_output_ratio) && isMissing(row.consume_output_delta)) {
|
||||||
|
return <span className="report-placeholder">--</span>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className="report-pair-cell">
|
||||||
|
<b>{formatOptionalPercent(row.consume_output_ratio)}</b>
|
||||||
|
<small>差额 {formatOptionalCoin(row.consume_output_delta)}</small>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function TrendValue({ value }) {
|
function TrendValue({ value }) {
|
||||||
return <span className={Number(value) >= 0 ? "report-trend-up" : "report-trend-down"}>{formatSignedPercent(value)}</span>;
|
return <span className={Number(value) >= 0 ? "report-trend-up" : "report-trend-down"}>{formatSignedPercent(value)}</span>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,10 @@ test("shows daily totals first and expands daily country details on demand", asy
|
|||||||
|
|
||||||
const stickyDateHeader = container.querySelector(".report-table--wide th.is-sticky-left");
|
const stickyDateHeader = container.querySelector(".report-table--wide th.is-sticky-left");
|
||||||
expect(stickyDateHeader).toHaveTextContent("日期");
|
expect(stickyDateHeader).toHaveTextContent("日期");
|
||||||
|
expect(screen.getByRole("columnheader", { name: "用户金币数" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("columnheader", { name: "产出金币" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("columnheader", { name: "消耗产出比" })).toBeInTheDocument();
|
||||||
|
expect(screen.queryByRole("columnheader", { name: "金币数量(总和)" })).not.toBeInTheDocument();
|
||||||
expect(container.querySelector(".report-table--wide td.is-sticky-left .report-date-cell")).toBeTruthy();
|
expect(container.querySelector(".report-table--wide td.is-sticky-left .report-date-cell")).toBeTruthy();
|
||||||
|
|
||||||
const june5Row = screen.getByRole("button", { name: "展开 2026-06-05 国家明细" });
|
const june5Row = screen.getByRole("button", { name: "展开 2026-06-05 国家明细" });
|
||||||
|
|||||||
@ -339,6 +339,7 @@ function summarizeReportRows(rows) {
|
|||||||
"coin_seller_transfer_coin",
|
"coin_seller_transfer_coin",
|
||||||
"coin_total",
|
"coin_total",
|
||||||
"consumed_coin",
|
"consumed_coin",
|
||||||
|
"output_coin",
|
||||||
"game_payout",
|
"game_payout",
|
||||||
"game_refund",
|
"game_refund",
|
||||||
"game_turnover",
|
"game_turnover",
|
||||||
@ -398,6 +399,12 @@ function normalizeCountryRow(item, index, totalRecharge) {
|
|||||||
const micOnlineUsers = readOptionalNumber(item, "mic_online_users", "micOnlineUsers");
|
const micOnlineUsers = readOptionalNumber(item, "mic_online_users", "micOnlineUsers");
|
||||||
const avgMicOnlineMS = readOptionalNumber(item, "avg_mic_online_ms", "avgMicOnlineMs") ??
|
const avgMicOnlineMS = readOptionalNumber(item, "avg_mic_online_ms", "avgMicOnlineMs") ??
|
||||||
(micOnlineMS === null || micOnlineUsers === null ? null : Math.round(ratio(micOnlineMS, micOnlineUsers)));
|
(micOnlineMS === null || micOnlineUsers === null ? null : Math.round(ratio(micOnlineMS, micOnlineUsers)));
|
||||||
|
const consumedCoin = readOptionalNumber(item, "consumed_coin", "consumedCoin");
|
||||||
|
const outputCoin = readOptionalNumber(item, "output_coin", "outputCoin");
|
||||||
|
const consumeOutputRatio = readOptionalNumber(item, "consume_output_ratio", "consumeOutputRatio") ??
|
||||||
|
(consumedCoin === null || outputCoin === null ? null : ratio(outputCoin, consumedCoin));
|
||||||
|
const consumeOutputDelta = readOptionalNumber(item, "consume_output_delta", "consumeOutputDelta") ??
|
||||||
|
(consumedCoin === null || outputCoin === null ? null : consumedCoin - outputCoin);
|
||||||
return {
|
return {
|
||||||
active_users: activeUsers,
|
active_users: activeUsers,
|
||||||
arpu_usd_minor: numberValue(item.arpu_usd_minor ?? item.arpuUsdMinor),
|
arpu_usd_minor: numberValue(item.arpu_usd_minor ?? item.arpuUsdMinor),
|
||||||
@ -408,7 +415,9 @@ function normalizeCountryRow(item, index, totalRecharge) {
|
|||||||
coin_seller_stock_coin: readOptionalNumber(item, "coin_seller_stock_coin", "coinSellerStockCoin"),
|
coin_seller_stock_coin: readOptionalNumber(item, "coin_seller_stock_coin", "coinSellerStockCoin"),
|
||||||
coin_seller_transfer_coin: numberValue(item.coin_seller_transfer_coin ?? item.coinSellerTransferCoin),
|
coin_seller_transfer_coin: numberValue(item.coin_seller_transfer_coin ?? item.coinSellerTransferCoin),
|
||||||
coin_total: readOptionalNumber(item, "coin_total", "coinTotal"),
|
coin_total: readOptionalNumber(item, "coin_total", "coinTotal"),
|
||||||
consumed_coin: readOptionalNumber(item, "consumed_coin", "consumedCoin"),
|
consumed_coin: consumedCoin,
|
||||||
|
consume_output_delta: consumeOutputDelta,
|
||||||
|
consume_output_ratio: consumeOutputRatio,
|
||||||
country: meta?.label || country,
|
country: meta?.label || country,
|
||||||
country_id: countryId,
|
country_id: countryId,
|
||||||
country_code: code || meta?.code || "",
|
country_code: code || meta?.code || "",
|
||||||
@ -432,6 +441,7 @@ function normalizeCountryRow(item, index, totalRecharge) {
|
|||||||
new_user_recharge_usd_minor: numberValue(item.new_user_recharge_usd_minor ?? item.newUserRechargeUsdMinor),
|
new_user_recharge_usd_minor: numberValue(item.new_user_recharge_usd_minor ?? item.newUserRechargeUsdMinor),
|
||||||
paid_users: paidUsers,
|
paid_users: paidUsers,
|
||||||
payer_rate: ratio(paidUsers, activeUsers),
|
payer_rate: ratio(paidUsers, activeUsers),
|
||||||
|
output_coin: outputCoin,
|
||||||
platform_grant_coin: readOptionalNumber(item, "platform_grant_coin", "platformGrantCoin"),
|
platform_grant_coin: readOptionalNumber(item, "platform_grant_coin", "platformGrantCoin"),
|
||||||
recharge_usd_minor: recharge,
|
recharge_usd_minor: recharge,
|
||||||
recharge_conversion_rate: item.recharge_conversion_rate !== undefined || item.rechargeConversionRate !== undefined
|
recharge_conversion_rate: item.recharge_conversion_rate !== undefined || item.rechargeConversionRate !== undefined
|
||||||
@ -487,6 +497,12 @@ function normalizeDailySeries(source) {
|
|||||||
const micOnlineUsers = readOptionalNumber(item, "mic_online_users", "micOnlineUsers");
|
const micOnlineUsers = readOptionalNumber(item, "mic_online_users", "micOnlineUsers");
|
||||||
const avgMicOnlineMS = readOptionalNumber(item, "avg_mic_online_ms", "avgMicOnlineMs") ??
|
const avgMicOnlineMS = readOptionalNumber(item, "avg_mic_online_ms", "avgMicOnlineMs") ??
|
||||||
(micOnlineMS === null || micOnlineUsers === null ? null : Math.round(ratio(micOnlineMS, micOnlineUsers)));
|
(micOnlineMS === null || micOnlineUsers === null ? null : Math.round(ratio(micOnlineMS, micOnlineUsers)));
|
||||||
|
const consumedCoin = readOptionalNumber(item, "consumed_coin", "consumedCoin");
|
||||||
|
const outputCoin = readOptionalNumber(item, "output_coin", "outputCoin");
|
||||||
|
const consumeOutputRatio = readOptionalNumber(item, "consume_output_ratio", "consumeOutputRatio") ??
|
||||||
|
(consumedCoin === null || outputCoin === null ? null : ratio(outputCoin, consumedCoin));
|
||||||
|
const consumeOutputDelta = readOptionalNumber(item, "consume_output_delta", "consumeOutputDelta") ??
|
||||||
|
(consumedCoin === null || outputCoin === null ? null : consumedCoin - outputCoin);
|
||||||
return {
|
return {
|
||||||
active_users: readNumber(item, "active_users", "activeUsers"),
|
active_users: readNumber(item, "active_users", "activeUsers"),
|
||||||
arppu_usd_minor: readNumber(item, "arppu_usd_minor", "arppuUsdMinor"),
|
arppu_usd_minor: readNumber(item, "arppu_usd_minor", "arppuUsdMinor"),
|
||||||
@ -497,7 +513,9 @@ function normalizeDailySeries(source) {
|
|||||||
coin_seller_stock_coin: readOptionalNumber(item, "coin_seller_stock_coin", "coinSellerStockCoin"),
|
coin_seller_stock_coin: readOptionalNumber(item, "coin_seller_stock_coin", "coinSellerStockCoin"),
|
||||||
coin_seller_transfer_coin: readNumber(item, "coin_seller_transfer_coin", "coinSellerTransferCoin"),
|
coin_seller_transfer_coin: readNumber(item, "coin_seller_transfer_coin", "coinSellerTransferCoin"),
|
||||||
coin_total: readOptionalNumber(item, "coin_total", "coinTotal"),
|
coin_total: readOptionalNumber(item, "coin_total", "coinTotal"),
|
||||||
consumed_coin: readOptionalNumber(item, "consumed_coin", "consumedCoin"),
|
consumed_coin: consumedCoin,
|
||||||
|
consume_output_delta: consumeOutputDelta,
|
||||||
|
consume_output_ratio: consumeOutputRatio,
|
||||||
game_profit: gameProfit,
|
game_profit: gameProfit,
|
||||||
game_turnover: gameTurnover,
|
game_turnover: gameTurnover,
|
||||||
gift_coin_spent: readNumber(item, "gift_coin_spent", "giftCoinSpent"),
|
gift_coin_spent: readNumber(item, "gift_coin_spent", "giftCoinSpent"),
|
||||||
@ -514,6 +532,7 @@ function normalizeDailySeries(source) {
|
|||||||
new_paid_users: readNumber(item, "new_paid_users", "newPaidUsers"),
|
new_paid_users: readNumber(item, "new_paid_users", "newPaidUsers"),
|
||||||
new_user_recharge_usd_minor: readNumber(item, "new_user_recharge_usd_minor", "newUserRechargeUsdMinor"),
|
new_user_recharge_usd_minor: readNumber(item, "new_user_recharge_usd_minor", "newUserRechargeUsdMinor"),
|
||||||
new_users: readNumber(item, "new_users", "newUsers"),
|
new_users: readNumber(item, "new_users", "newUsers"),
|
||||||
|
output_coin: outputCoin,
|
||||||
paid_users: readNumber(item, "paid_users", "paidUsers"),
|
paid_users: readNumber(item, "paid_users", "paidUsers"),
|
||||||
platform_grant_coin: readOptionalNumber(item, "platform_grant_coin", "platformGrantCoin"),
|
platform_grant_coin: readOptionalNumber(item, "platform_grant_coin", "platformGrantCoin"),
|
||||||
recharge_usd_minor: recharge,
|
recharge_usd_minor: recharge,
|
||||||
|
|||||||
@ -147,6 +147,7 @@ test("builds report rows with range total, daily totals, and daily country rows"
|
|||||||
manual_grant_coin: 20,
|
manual_grant_coin: 20,
|
||||||
mic_online_ms: 6_000,
|
mic_online_ms: 6_000,
|
||||||
mic_online_users: 3,
|
mic_online_users: 3,
|
||||||
|
output_coin: 120,
|
||||||
platform_grant_coin: 150,
|
platform_grant_coin: 150,
|
||||||
recharge_usd_minor: 100,
|
recharge_usd_minor: 100,
|
||||||
salary_usd_minor: 900,
|
salary_usd_minor: 900,
|
||||||
@ -172,6 +173,7 @@ test("builds report rows with range total, daily totals, and daily country rows"
|
|||||||
manual_grant_coin: 20,
|
manual_grant_coin: 20,
|
||||||
mic_online_ms: 6_000,
|
mic_online_ms: 6_000,
|
||||||
mic_online_users: 3,
|
mic_online_users: 3,
|
||||||
|
output_coin: 120,
|
||||||
platform_grant_coin: 150,
|
platform_grant_coin: 150,
|
||||||
recharge_usd_minor: 100,
|
recharge_usd_minor: 100,
|
||||||
salary_usd_minor: 900,
|
salary_usd_minor: 900,
|
||||||
@ -188,10 +190,13 @@ test("builds report rows with range total, daily totals, and daily country rows"
|
|||||||
avg_mic_online_ms: 2_000,
|
avg_mic_online_ms: 2_000,
|
||||||
coin_total: 1_200,
|
coin_total: 1_200,
|
||||||
consumed_coin: 300,
|
consumed_coin: 300,
|
||||||
|
consume_output_delta: 180,
|
||||||
|
consume_output_ratio: 0.4,
|
||||||
country: "总计",
|
country: "总计",
|
||||||
date_label: "2026-06-05",
|
date_label: "2026-06-05",
|
||||||
manual_grant_coin: 20,
|
manual_grant_coin: 20,
|
||||||
mic_online_ms: 6_000,
|
mic_online_ms: 6_000,
|
||||||
|
output_coin: 120,
|
||||||
platform_grant_coin: 150,
|
platform_grant_coin: 150,
|
||||||
salary_usd_minor: 900
|
salary_usd_minor: 900
|
||||||
}));
|
}));
|
||||||
@ -202,11 +207,14 @@ test("builds report rows with range total, daily totals, and daily country rows"
|
|||||||
coin_seller_transfer_coin: 500,
|
coin_seller_transfer_coin: 500,
|
||||||
coin_total: 1_200,
|
coin_total: 1_200,
|
||||||
consumed_coin: 300,
|
consumed_coin: 300,
|
||||||
|
consume_output_delta: 180,
|
||||||
|
consume_output_ratio: 0.4,
|
||||||
country: "巴西",
|
country: "巴西",
|
||||||
game_turnover: 300,
|
game_turnover: 300,
|
||||||
lucky_gift_turnover: 200,
|
lucky_gift_turnover: 200,
|
||||||
manual_grant_coin: 20,
|
manual_grant_coin: 20,
|
||||||
mic_online_ms: 6_000,
|
mic_online_ms: 6_000,
|
||||||
|
output_coin: 120,
|
||||||
platform_grant_coin: 150,
|
platform_grant_coin: 150,
|
||||||
salary_usd_minor: 900,
|
salary_usd_minor: 900,
|
||||||
salary_transfer_coin: 700,
|
salary_transfer_coin: 700,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user