From 464d8dd1831d828a0f83713c3c507574ddc09fb7 Mon Sep 17 00:00:00 2001
From: lowmaster-chen <1119780489@qq.com>
Date: Tue, 30 Jun 2026 10:23:49 +0800
Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E8=A1=A5=E9=BD=90=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E5=A4=A7=E7=9B=98=E4=BA=AC=E4=B8=9C=20CPS=20=E4=B8=8E?=
=?UTF-8?q?=E9=87=91=E5=B8=81=E5=B1=95=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
改了什么:数据大盘总收入/CPS 收入纳入京东 CPS;京东 CPS 卡片展示有效/无效订单;刷新按钮改为同步全部 CPS 订单;ARPU 展示产品确认的昨日活跃用户分母;激励视频金币展示真实值并标注已计入领券奖励。
验证方式:npx tsc --noEmit;浏览器验证 /dashboard 已显示京东 CPS、ARPU 分母和激励视频金币。
---
src/app/(main)/dashboard/page.tsx | 49 ++++++++++++++++++++-----------
src/lib/types.ts | 5 ++++
2 files changed, 37 insertions(+), 17 deletions(-)
diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx
index 3fe8427..c2d03de 100644
--- a/src/app/(main)/dashboard/page.tsx
+++ b/src/app/(main)/dashboard/page.tsx
@@ -381,19 +381,25 @@ export default function DashboardPage() {
const regularTaskCoinTotal =
periodData?.coins.regular_task_coin_total ??
((signinCoinTotal ?? 0) + (signinBoostCoinTotal ?? 0) + (taskCoinTotal ?? 0));
- const cpsCommissionCents = data?.cps.meituan_commission_cents;
const cpsAvailable = data?.cps.available === true;
+ const meituanCpsCommissionCents = data?.cps.meituan_commission_cents;
+ const jdCpsCommissionCents = data?.cps.jd_commission_cents;
+ const totalCpsCommissionCents = cpsAvailable
+ ? (meituanCpsCommissionCents ?? 0) + (jdCpsCommissionCents ?? 0)
+ : null;
const totalRevenueYuan =
- adReport?.total_revenue_yuan == null && (!cpsAvailable || cpsCommissionCents == null)
+ adReport?.total_revenue_yuan == null && totalCpsCommissionCents == null
? null
- : (adReport?.total_revenue_yuan ?? 0) + (cpsAvailable ? (cpsCommissionCents ?? 0) / 100 : 0);
+ : (adReport?.total_revenue_yuan ?? 0) + ((totalCpsCommissionCents ?? 0) / 100);
const yesterdayTrendPoint = periodData?.trend?.[periodData.trend.length - 1];
const yesterdayActiveUsers = yesterdayTrendPoint?.active_users ?? null;
const arpuYuan =
totalRevenueYuan == null || yesterdayActiveUsers == null || yesterdayActiveUsers <= 0
? null
: totalRevenueYuan / yesterdayActiveUsers;
- const cpsRevenueText = cpsAvailable ? fmtCents(cpsCommissionCents) : '--';
+ const cpsRevenueText = cpsAvailable ? fmtCents(totalCpsCommissionCents) : '--';
+ const meituanCpsRevenueText = cpsAvailable ? fmtCents(meituanCpsCommissionCents) : '--';
+ const jdCpsRevenueText = cpsAvailable ? fmtCents(jdCpsCommissionCents) : '--';
const cpsHitRate = !cpsAvailable || data?.cps.meituan_hit_rate == null ? '--' : pct(data.cps.meituan_hit_rate);
const retentionValue =
periodData?.users.retention_rate == null
@@ -464,7 +470,7 @@ export default function DashboardPage() {
return ;
}
- const syncMeituanCps = async () => {
+ const syncCpsOrders = async () => {
setCpsSyncing(true);
try {
const resp = await api.post<{
@@ -476,12 +482,13 @@ export default function DashboardPage() {
params: {
date_from: range.start.format('YYYY-MM-DD'),
date_to: range.end.format('YYYY-MM-DD'),
+ platform: 'all',
},
});
message.success(`已同步 ${resp.data.fetched} 单`);
setRefreshKey((v) => v + 1);
} catch {
- message.error('美团订单刷新失败');
+ message.error('CPS 订单刷新失败');
} finally {
setCpsSyncing(false);
}
@@ -597,7 +604,7 @@ export default function DashboardPage() {
变现
{cpsAvailable
- ? (adError ? '广告收益接口当前后端不可用,美团 CPS 已接入' : '广告收益与美团 CPS 已接入')
+ ? (adError ? '广告收益接口当前后端不可用,美团/京东 CPS 已接入' : '广告收益与美团/京东 CPS 已接入')
: (adError ? '广告收益接口当前后端不可用,订单/佣金数据待接' : '广告收益已接入,订单/佣金数据待接')}
@@ -620,33 +627,33 @@ export default function DashboardPage() {
CPS 收益
-
-
-
+
+
广告收益
@@ -719,7 +734,7 @@ export default function DashboardPage() {
value={fmtInt(comparisonRewardCoinTotal)}
status="比价相关发放"
/>
-
+
Date: Tue, 30 Jun 2026 15:50:32 +0800
Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E5=A4=A7=E7=9B=98=E7=8E=AF=E6=AF=94=E5=B1=95=E7=A4=BA?=
=?UTF-8?q?=E5=B9=B6=E7=A7=BB=E9=99=A4=E6=80=BB=E7=94=A8=E6=88=B7=E5=8D=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/(main)/dashboard/page.tsx | 386 +++++++++++++++++++++++-------
1 file changed, 304 insertions(+), 82 deletions(-)
diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx
index c2d03de..2919ba4 100644
--- a/src/app/(main)/dashboard/page.tsx
+++ b/src/app/(main)/dashboard/page.tsx
@@ -1,7 +1,7 @@
'use client';
-import { useEffect, useMemo, useState } from 'react';
-import { Alert, Select, Spin, Tag, Tooltip, message } from 'antd';
+import { useEffect, useMemo, useState, type ReactNode } from 'react';
+import { Alert, App, Select, Spin, Tag, Tooltip } from 'antd';
import { CalendarOutlined, InfoCircleOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import { api } from '@/lib/api';
@@ -10,6 +10,8 @@ import type { AdRevenueReport, DashboardOverview } from '@/lib/types';
type PeriodKey = 'yesterday' | '7d' | '30d';
type TrendSeriesKey = 'dau' | 'newUsers' | 'comparisons';
type DashboardTrendPoint = DashboardOverview['period']['trend'][number];
+type DeltaTone = 'muted' | 'up' | 'down' | 'flat';
+type DeltaInfo = { value: ReactNode; tone: DeltaTone };
const PERIODS: { key: PeriodKey; label: string; days: number }[] = [
{ key: 'yesterday', label: '昨日', days: 1 },
@@ -35,6 +37,72 @@ const fmtYuan = (yuan: number | null | undefined) => (yuan == null ? '--' : `¥$
const fmtMsAsSeconds = (ms: number | null | undefined) => (ms == null ? '--' : (ms / 1000).toFixed(1));
const pct = (v: number | null | undefined) => (v == null ? '--' : `${(v * 100).toFixed(1)}%`);
const compact = (v: number) => (v >= 1000 ? `${(v / 1000).toFixed(v >= 10000 ? 0 : 1)}k` : String(v));
+const fmtCoinAmount = (v: number | null | undefined) => {
+ if (v == null) return '--';
+ if (Math.abs(v) >= 10000) return `${(v / 10000).toFixed(2).replace(/\.?0+$/, '')} 万`;
+ return numberFmt.format(v);
+};
+const CMP_LABEL = 'vs 上周期';
+
+function withCompare(main: string) {
+ return (
+ <>
+ {main}
+ {CMP_LABEL}
+ >
+ );
+}
+
+function percentDelta(current: number | null | undefined, previous: number | null | undefined): DeltaInfo {
+ if (current == null || previous == null) return { value: '暂无上期', tone: 'flat' };
+ if (previous === 0) {
+ if (current === 0) return { value: withCompare('持平'), tone: 'flat' };
+ return { value: '上期为 0', tone: current > 0 ? 'up' : 'down' };
+ }
+ const diff = (current - previous) / Math.abs(previous);
+ if (Math.abs(diff) < 0.0005) return { value: withCompare('持平'), tone: 'flat' };
+ const arrow = diff > 0 ? '▲' : '▼';
+ return {
+ value: withCompare(`${arrow} ${(Math.abs(diff) * 100).toFixed(1)}%`),
+ tone: diff > 0 ? 'up' : 'down',
+ };
+}
+
+function pointDelta(current: number | null | undefined, previous: number | null | undefined): DeltaInfo {
+ if (current == null || previous == null) return { value: '暂无上期', tone: 'flat' };
+ const diff = (current - previous) * 100;
+ if (Math.abs(diff) < 0.05) return { value: withCompare('持平'), tone: 'flat' };
+ const arrow = diff > 0 ? '▲' : '▼';
+ return {
+ value: withCompare(`${arrow} ${Math.abs(diff).toFixed(1)}pt`),
+ tone: diff > 0 ? 'up' : 'down',
+ };
+}
+
+function durationDelta(currentMs: number | null | undefined, previousMs: number | null | undefined): DeltaInfo {
+ if (currentMs == null || previousMs == null) return { value: '暂无上期', tone: 'flat' };
+ const diffSeconds = (currentMs - previousMs) / 1000;
+ if (Math.abs(diffSeconds) < 0.05) return { value: withCompare('持平'), tone: 'flat' };
+ return diffSeconds < 0
+ ? { value: `▼ ${Math.abs(diffSeconds).toFixed(1)}s 更快`, tone: 'up' }
+ : { value: `▲ ${Math.abs(diffSeconds).toFixed(1)}s 更慢`, tone: 'down' };
+}
+
+function moneyDeltaCents(current: number | null | undefined, previous: number | null | undefined): DeltaInfo {
+ if (current == null || previous == null) return { value: '暂无上期', tone: 'flat' };
+ const diff = current - previous;
+ if (Math.abs(diff) < 0.5) return { value: withCompare('持平'), tone: 'flat' };
+ const arrow = diff > 0 ? '▲' : '▼';
+ return {
+ value: withCompare(`${arrow} ${fmtCents(Math.abs(diff))}`),
+ tone: diff > 0 ? 'up' : 'down',
+ };
+}
+
+function ratioBadge(value: number | null | undefined, total: number | null | undefined): DeltaInfo {
+ if (value == null || total == null || total <= 0) return { value: '占 --', tone: 'flat' };
+ return { value: `占 ${((value / total) * 100).toFixed(1)}%`, tone: 'flat' };
+}
function periodRange(period: PeriodKey) {
const p = PERIODS.find((it) => it.key === period) ?? PERIODS[0];
@@ -43,6 +111,12 @@ function periodRange(period: PeriodKey) {
return { start, end, label: `${start.format('MM-DD')} ~ ${end.format('MM-DD')}`, days: p.days };
}
+function previousPeriodRange(range: ReturnType) {
+ const end = range.start.subtract(1, 'day');
+ const start = end.subtract(range.days - 1, 'day');
+ return { start, end, label: `${start.format('MM-DD')} ~ ${end.format('MM-DD')}` };
+}
+
function retentionTitle(period: PeriodKey) {
if (period === '7d') return '近 7 日新增留存';
if (period === '30d') return '近 30 日新增留存';
@@ -92,6 +166,16 @@ function sceneAdEcpm(summary: ReturnType) {
return fmtYuan((summary.revenue_yuan / summary.impressions) * 1000);
}
+function cpsCommissionCents(data: DashboardOverview | null | undefined) {
+ if (!data?.cps.available) return null;
+ return (data.cps.meituan_commission_cents ?? 0) + (data.cps.jd_commission_cents ?? 0);
+}
+
+function revenueYuan(ad: AdRevenueReport | null | undefined, cpsCents: number | null | undefined) {
+ if (ad?.total_revenue_yuan == null && cpsCents == null) return null;
+ return (ad?.total_revenue_yuan ?? 0) + ((cpsCents ?? 0) / 100);
+}
+
function niceMax(v: number) {
if (v <= 5) return 5;
if (v <= 10) return 10;
@@ -99,7 +183,7 @@ function niceMax(v: number) {
return Math.ceil(v / base) * base;
}
-function StatusText({ children, tone = 'muted' }: { children: React.ReactNode; tone?: 'muted' | 'ok' | 'warn' }) {
+function StatusText({ children, tone = 'muted' }: { children: ReactNode; tone?: 'muted' | 'ok' | 'warn' }) {
return {children};
}
@@ -107,8 +191,8 @@ function DeltaBadge({
children,
tone = 'muted',
}: {
- children: React.ReactNode;
- tone?: 'muted' | 'up' | 'down';
+ children: ReactNode;
+ tone?: DeltaTone;
}) {
return {children};
}
@@ -116,11 +200,15 @@ function DeltaBadge({
function MiniSpark({
color = '#2F6BFF',
down,
+ flat,
}: {
color?: string;
down?: boolean;
+ flat?: boolean;
}) {
- const points = down
+ const points = flat
+ ? '0,16 13,16 26,15 39,16 52,15 65,16 78,15'
+ : down
? '0,8 13,10 26,9 39,14 52,12 65,18 78,20'
: '0,24 13,22 26,20 39,15 52,13 65,8 78,5';
return (
@@ -149,9 +237,9 @@ function StatCard({
status?: string;
tone?: 'muted' | 'ok' | 'warn';
emphasis?: boolean;
- spark?: 'up' | 'down';
- delta?: string;
- deltaTone?: 'muted' | 'up' | 'down';
+ spark?: 'up' | 'down' | 'flat';
+ delta?: ReactNode;
+ deltaTone?: DeltaTone;
}) {
return (
@@ -168,7 +256,13 @@ function StatCard({
{value}
{unit && value !== '--' && {unit}}
- {spark && }
+ {spark && (
+
+ )}
{delta ? {delta} : status && {status}}
@@ -181,18 +275,20 @@ function RevenueCard({
meta,
accent,
delta,
+ deltaTone = 'up',
}: {
title: string;
value: string;
meta?: { label: string; value: string }[];
- accent?: 'blue' | 'green' | 'yellow' | 'red';
- delta?: string;
+ accent?: 'blue' | 'green' | 'yellow' | 'red' | 'jd';
+ delta?: ReactNode;
+ deltaTone?: DeltaTone;
}) {
return (
{title}
- {delta &&
{delta}}
+ {delta &&
{delta}}
{value}
{meta && meta.length > 0 && (
@@ -349,9 +445,12 @@ function TrendChart({ rangeLabel, points }: { rangeLabel: string; points: Dashbo
}
export default function DashboardPage() {
+ const { message } = App.useApp();
const [period, setPeriod] = useState
('yesterday');
const [data, setData] = useState(null);
+ const [previousData, setPreviousData] = useState(null);
const [adReport, setAdReport] = useState(null);
+ const [previousAdReport, setPreviousAdReport] = useState(null);
const [loading, setLoading] = useState(true);
const [adLoading, setAdLoading] = useState(false);
const [overviewError, setOverviewError] = useState(null);
@@ -361,7 +460,9 @@ export default function DashboardPage() {
const [cpsSyncing, setCpsSyncing] = useState(false);
const range = useMemo(() => periodRange(period), [period]);
+ const previousRange = useMemo(() => previousPeriodRange(range), [range]);
const periodData = data?.period ?? null;
+ const previousPeriodData = previousData?.period ?? null;
const adAvgEcpm = avgEcpm(adReport);
const rewardVideoAdSummary = adTypeSummary(adReport, 'reward_video');
const couponAdSummary = adSceneSummary(adReport, 'coupon');
@@ -384,13 +485,10 @@ export default function DashboardPage() {
const cpsAvailable = data?.cps.available === true;
const meituanCpsCommissionCents = data?.cps.meituan_commission_cents;
const jdCpsCommissionCents = data?.cps.jd_commission_cents;
- const totalCpsCommissionCents = cpsAvailable
- ? (meituanCpsCommissionCents ?? 0) + (jdCpsCommissionCents ?? 0)
- : null;
- const totalRevenueYuan =
- adReport?.total_revenue_yuan == null && totalCpsCommissionCents == null
- ? null
- : (adReport?.total_revenue_yuan ?? 0) + ((totalCpsCommissionCents ?? 0) / 100);
+ const totalCpsCommissionCents = cpsCommissionCents(data);
+ const previousTotalCpsCommissionCents = cpsCommissionCents(previousData);
+ const totalRevenueYuan = revenueYuan(adReport, totalCpsCommissionCents);
+ const previousTotalRevenueYuan = revenueYuan(previousAdReport, previousTotalCpsCommissionCents);
const yesterdayTrendPoint = periodData?.trend?.[periodData.trend.length - 1];
const yesterdayActiveUsers = yesterdayTrendPoint?.active_users ?? null;
const arpuYuan =
@@ -405,26 +503,63 @@ export default function DashboardPage() {
periodData?.users.retention_rate == null
? '--'
: (periodData.users.retention_rate * 100).toFixed(1);
+ const activeUserDelta = percentDelta(periodData?.users.active, previousPeriodData?.users.active);
+ const newUserDelta = percentDelta(periodData?.users.new, previousPeriodData?.users.new);
+ const retentionDelta = pointDelta(periodData?.users.retention_rate, previousPeriodData?.users.retention_rate);
+ const retentionSpark: 'up' | 'down' | 'flat' | undefined =
+ periodData?.users.retention_rate == null
+ ? undefined
+ : retentionDelta.tone === 'down'
+ ? 'down'
+ : retentionDelta.tone === 'up' || periodData.users.retention_rate >= 1
+ ? 'up'
+ : 'flat';
+ const comparisonTotalDelta = percentDelta(periodData?.comparison.total, previousPeriodData?.comparison.total);
+ const comparisonSuccessDelta = percentDelta(periodData?.comparison.success, previousPeriodData?.comparison.success);
+ const comparisonOrderedDelta = percentDelta(periodData?.comparison.ordered, previousPeriodData?.comparison.ordered);
+ const durationDeltaInfo = durationDelta(
+ periodData?.comparison.average_duration_ms,
+ previousPeriodData?.comparison.average_duration_ms,
+ );
+ const averageSavedDelta = moneyDeltaCents(
+ periodData?.comparison.average_saved_cents,
+ previousPeriodData?.comparison.average_saved_cents,
+ );
+ const totalRevenueDelta = percentDelta(totalRevenueYuan, previousTotalRevenueYuan);
+ const adRevenueDelta = percentDelta(adReport?.total_revenue_yuan, previousAdReport?.total_revenue_yuan);
+ const cpsRevenueDelta = percentDelta(totalCpsCommissionCents, previousTotalCpsCommissionCents);
+ const grantedCoinDelta = percentDelta(periodData?.coins.granted_total, previousPeriodData?.coins.granted_total);
+ const couponCoinRatio = ratioBadge(couponRewardCoinTotal, periodData?.coins.granted_total);
+ const comparisonCoinRatio = ratioBadge(comparisonRewardCoinTotal, periodData?.coins.granted_total);
+ const rewardVideoCoinRatio = ratioBadge(periodData?.coins.reward_video_coin_total, periodData?.coins.granted_total);
+ const regularTaskCoinRatio = ratioBadge(regularTaskCoinTotal, periodData?.coins.granted_total);
useEffect(() => {
let alive = true;
setLoading(true);
setOverviewError(null);
- api
- .get('/admin/api/stats/overview', {
- params: {
- date_from: range.start.format('YYYY-MM-DD'),
- date_to: range.end.format('YYYY-MM-DD'),
- },
- })
- .then((r) => {
+ const currentParams = {
+ date_from: range.start.format('YYYY-MM-DD'),
+ date_to: range.end.format('YYYY-MM-DD'),
+ };
+ const previousParams = {
+ date_from: previousRange.start.format('YYYY-MM-DD'),
+ date_to: previousRange.end.format('YYYY-MM-DD'),
+ };
+ Promise.allSettled([
+ api.get('/admin/api/stats/overview', { params: currentParams }),
+ api.get('/admin/api/stats/overview', { params: previousParams }),
+ ])
+ .then(([current, previous]) => {
if (!alive) return;
- setData(r.data);
- setUpdatedAt(dayjs().format('YYYY-MM-DD HH:mm'));
- })
- .catch(() => {
- if (!alive) return;
- setOverviewError('总览接口加载失败');
+ if (current.status === 'fulfilled') {
+ setData(current.value.data);
+ setUpdatedAt(dayjs().format('YYYY-MM-DD HH:mm'));
+ } else {
+ setData(null);
+ setOverviewError('总览接口加载失败');
+ }
+ setPreviousData(previous.status === 'fulfilled' ? previous.value.data : null);
})
.finally(() => {
if (alive) setLoading(false);
@@ -432,29 +567,37 @@ export default function DashboardPage() {
return () => {
alive = false;
};
- }, [range.start, range.end, refreshKey]);
+ }, [range.start, range.end, previousRange.start, previousRange.end, refreshKey]);
useEffect(() => {
let alive = true;
setAdLoading(true);
setAdError(null);
- api
- .get('/admin/api/ad-revenue-report', {
- params: {
- date_from: range.start.format('YYYY-MM-DD'),
- date_to: range.end.format('YYYY-MM-DD'),
- granularity: 'day',
- limit: 1000,
- },
- })
- .then((r) => {
+ const currentParams = {
+ date_from: range.start.format('YYYY-MM-DD'),
+ date_to: range.end.format('YYYY-MM-DD'),
+ granularity: 'day',
+ limit: 1000,
+ };
+ const previousParams = {
+ date_from: previousRange.start.format('YYYY-MM-DD'),
+ date_to: previousRange.end.format('YYYY-MM-DD'),
+ granularity: 'day',
+ limit: 1000,
+ };
+ Promise.allSettled([
+ api.get('/admin/api/ad-revenue-report', { params: currentParams }),
+ api.get('/admin/api/ad-revenue-report', { params: previousParams }),
+ ])
+ .then(([current, previous]) => {
if (!alive) return;
- setAdReport(r.data);
- })
- .catch(() => {
- if (!alive) return;
- setAdReport(null);
- setAdError('广告收益接口加载失败');
+ if (current.status === 'fulfilled') {
+ setAdReport(current.value.data);
+ } else {
+ setAdReport(null);
+ setAdError('广告收益接口加载失败');
+ }
+ setPreviousAdReport(previous.status === 'fulfilled' ? previous.value.data : null);
})
.finally(() => {
if (alive) setAdLoading(false);
@@ -462,7 +605,7 @@ export default function DashboardPage() {
return () => {
alive = false;
};
- }, [range.start, range.end]);
+ }, [range.start, range.end, previousRange.start, previousRange.end]);
if (loading) return ;
@@ -534,20 +677,20 @@ export default function DashboardPage() {
核心趋势
先看用户、留存与比价活跃度
-
-
+
@@ -555,16 +698,16 @@ export default function DashboardPage() {
title={retentionTitle(period)}
value={retentionValue}
unit="%"
- status={`${fmtInt(periodData?.users.retained_new_users)} / ${fmtInt(periodData?.users.new)} 新用户`}
- tone="warn"
+ delta={retentionDelta.value}
+ deltaTone={retentionDelta.tone}
hint={periodData?.users.retention_note ?? '暂无留存数据'}
- spark="down"
+ spark={retentionSpark}
/>
@@ -578,23 +721,43 @@ export default function DashboardPage() {
发起、成功、下单与效率
-
-
+
+
-
+
@@ -614,6 +777,8 @@ export default function DashboardPage() {
title="总收入"
value={adLoading ? '--' : fmtYuan(totalRevenueYuan)}
accent="blue"
+ delta={totalRevenueDelta.value}
+ deltaTone={totalRevenueDelta.tone}
meta={[
{ label: '广告收入', value: adLoading ? '--' : fmtYuan(adReport?.total_revenue_yuan) },
{ label: 'CPS 收入', value: cpsRevenueText },
@@ -622,12 +787,32 @@ export default function DashboardPage() {
0 && adReport?.total_revenue_yuan != null
+ ? `${((adReport.total_revenue_yuan / totalRevenueYuan) * 100).toFixed(1)}%`
+ : '--',
+ },
+ ]}
/>
0 && totalCpsCommissionCents != null
+ ? `${(((totalCpsCommissionCents / 100) / totalRevenueYuan) * 100).toFixed(1)}%`
+ : '--',
+ },
+ ]}
/>
@@ -720,32 +905,58 @@ export default function DashboardPage() {
金币经济
风控重点
- 发放是账面负债,提现是真实现金成本
+ 发放 = 平台负债,提现 = 真实现金成本
-
+
+
-
+
-
- 本期已发放 {fmtInt(periodData?.coins.granted_total)} 金币,成功提现 {fmtCents(periodData?.cash.withdraw_success_cents)};
- 累计已发放 {fmtInt(data.coins.granted_total)} 金币,累计成功提现 {fmtCents(data.cash.withdraw_success_cents)}。
- 金币分类已按产品口径归类;邀请、后台手动加金币、福利页广告、无法判断来源的广告不进入上述分类。
+ ⚠️
+
+ 本期 4 项奖励合计发放 {fmtCoinAmount(periodData?.coins.granted_total)} 金币
+ (账面负债 ≈ {fmtYuan((periodData?.coins.granted_total ?? 0) / 1000)}),
+ 实际提现兑付 {fmtCents(periodData?.cash.withdraw_success_cents)}。
+ 累计已发放 {fmtCoinAmount(data.coins.granted_total)} 金币但累计真实提现仅
+ {fmtCents(data.cash.withdraw_success_cents)},发放与现金兑付背离属正常(金币沉淀/过期);
+ 需盯紧各来源占比突变与异常领取,防薅羊毛。
+
@@ -787,7 +998,7 @@ export default function DashboardPage() {
--coin: #ffb300;
--p-mt-waimai: #ffc300;
--p-taobao: #ff4400;
- --p-jd: #e1251b;
+ --p-jd: #b91c1c;
--radius: 10px;
--shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.04);
color: var(--ink);
@@ -982,10 +1193,17 @@ export default function DashboardPage() {
font-weight: 600;
}
.status-muted,
- .delta-muted {
+ .delta-muted,
+ .delta-flat {
color: var(--ink-3);
background: #f1f3f7;
}
+ .delta .cmp {
+ margin-left: 3px;
+ color: var(--ink-3);
+ font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
+ font-weight: 400;
+ }
.status-ok,
.delta-up {
color: var(--up);
@@ -1159,6 +1377,10 @@ export default function DashboardPage() {
background: var(--p-taobao);
color: #fff;
}
+ .rev-jd .revenue-tag {
+ background: var(--p-jd);
+ color: #fff;
+ }
.revenue-value {
color: var(--ink);
font-family: "DIN Alternate", "Helvetica Neue", sans-serif;
--
2.52.0
From 1db0c3f834d7dbfe4abdba38284a15b44d4ebb9a Mon Sep 17 00:00:00 2001
From: lowmaster-chen <1119780489@qq.com>
Date: Tue, 30 Jun 2026 19:49:49 +0800
Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E5=90=8C=E6=AD=A5=E6=95=B0=E6=8D=AE?=
=?UTF-8?q?=E5=A4=A7=E7=9B=98=E6=B4=BB=E8=B7=83=E7=94=A8=E6=88=B7=E5=8F=A3?=
=?UTF-8?q?=E5=BE=84=E6=96=87=E6=A1=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/app/(main)/dashboard/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx
index 2919ba4..6fc1804 100644
--- a/src/app/(main)/dashboard/page.tsx
+++ b/src/app/(main)/dashboard/page.tsx
@@ -683,7 +683,7 @@ export default function DashboardPage() {
value={fmtInt(periodData?.users.active)}
delta={activeUserDelta.value}
deltaTone={activeUserDelta.tone}
- hint="当前按本期内进入 App、完成比价记录、开始领券流程任一满足去重;未完成上报的比价开始事件待后续补埋点。"
+ hint="当前按本期内登录、开始比价、开始领券任一满足去重;比价按 real_compare_start,领券按 real_coupon_start/claim_started。"
spark="up"
/>