Track public content acquisition events
This commit is contained in:
@@ -25,16 +25,21 @@ export function runTests() {
|
||||
|
||||
assert(
|
||||
opsApi.includes('"landing_view", "enter_terminal", "login_start", "signup_success", "trial_created", "payment_start", "payment_success"') &&
|
||||
opsApi.includes("content_events?:") &&
|
||||
opsApi.includes("contentEvents") &&
|
||||
opsApi.includes("diagnostics?:") &&
|
||||
opsApi.includes("traffic?:") &&
|
||||
opsApi.includes("uniqueActors"),
|
||||
"ops funnel API client must preserve the full standard funnel and expose diagnostics/traffic dimensions",
|
||||
"ops funnel API client must preserve the standard funnel and expose content/diagnostics/traffic dimensions",
|
||||
);
|
||||
assert(
|
||||
analyticsPage.includes("落地页访问") &&
|
||||
analyticsPage.includes("进入终端") &&
|
||||
analyticsPage.includes("注册成功") &&
|
||||
analyticsPage.includes("鉴权降级") &&
|
||||
analyticsPage.includes("公开内容获客") &&
|
||||
analyticsPage.includes("Brief 浏览") &&
|
||||
analyticsPage.includes("briefCtaClick?.total") &&
|
||||
analyticsPage.includes("来源与设备") &&
|
||||
analyticsPage.includes("paymentSuccess?.count") &&
|
||||
!analyticsPage.includes("总注册") &&
|
||||
|
||||
@@ -28,10 +28,17 @@ type AuthDiagnostic = {
|
||||
unique_actors?: number;
|
||||
by_reason?: TopItem[];
|
||||
};
|
||||
type ContentEventSummary = {
|
||||
total?: number;
|
||||
unique_users?: number;
|
||||
unique_actors?: number;
|
||||
};
|
||||
|
||||
export function AnalyticsPageClient() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [funnel, setFunnel] = useState<FunnelStep[]>([]);
|
||||
const [contentEvents, setContentEvents] = useState<Record<string, ContentEventSummary>>({});
|
||||
const [content, setContent] = useState<Record<string, TopItem[]>>({});
|
||||
const [diagnostics, setDiagnostics] = useState<Record<string, AuthDiagnostic>>({});
|
||||
const [rates, setRates] = useState<Record<string, number> | null>(null);
|
||||
const [traffic, setTraffic] = useState<Record<string, TopItem[]>>({});
|
||||
@@ -42,6 +49,8 @@ export function AnalyticsPageClient() {
|
||||
try {
|
||||
const data = await opsApi.funnel(days);
|
||||
setFunnel(data.steps);
|
||||
setContentEvents(data.contentEvents ?? {});
|
||||
setContent(data.content ?? {});
|
||||
setDiagnostics(data.diagnostics ?? {});
|
||||
setRates(data.rates ?? null);
|
||||
setTraffic(data.traffic ?? {});
|
||||
@@ -65,6 +74,10 @@ export function AnalyticsPageClient() {
|
||||
const trialCreated = stepByKey.trial_created;
|
||||
const paymentStart = stepByKey.payment_start;
|
||||
const paymentSuccess = stepByKey.payment_success;
|
||||
const briefView = contentEvents.brief_view;
|
||||
const briefCtaClick = contentEvents.brief_cta_click;
|
||||
const methodologyView = contentEvents.methodology_view;
|
||||
const socialOutboundClick = contentEvents.social_outbound_click;
|
||||
const overallRate =
|
||||
landingView?.uniqueActors && paymentSuccess?.uniqueActors
|
||||
? ((paymentSuccess.uniqueActors / landingView.uniqueActors) * 100).toFixed(1)
|
||||
@@ -141,6 +154,56 @@ export function AnalyticsPageClient() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader><CardTitle>公开内容获客</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
<ContentMetricCard
|
||||
label="Brief 浏览"
|
||||
total={briefView?.total}
|
||||
uniqueActors={briefView?.unique_actors}
|
||||
/>
|
||||
<ContentMetricCard
|
||||
label="Brief CTA"
|
||||
total={briefCtaClick?.total}
|
||||
uniqueActors={briefCtaClick?.unique_actors}
|
||||
/>
|
||||
<ContentMetricCard
|
||||
label="方法论阅读"
|
||||
total={methodologyView?.total}
|
||||
uniqueActors={methodologyView?.unique_actors}
|
||||
/>
|
||||
<ContentMetricCard
|
||||
label="社交外链"
|
||||
total={socialOutboundClick?.total}
|
||||
uniqueActors={socialOutboundClick?.unique_actors}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 text-sm md:grid-cols-2">
|
||||
{[
|
||||
["内容路径", content.paths ?? []],
|
||||
["Brief 城市", content.cities ?? []],
|
||||
].map(([title, rows]) => (
|
||||
<div key={String(title)} className="space-y-2 rounded-lg border border-white/10 p-3">
|
||||
<div className="text-xs font-bold text-slate-500">{String(title)}</div>
|
||||
{(rows as TopItem[]).length === 0 ? (
|
||||
<div className="text-xs text-slate-500">暂无数据</div>
|
||||
) : (
|
||||
<ul className="space-y-1.5">
|
||||
{(rows as TopItem[]).slice(0, 5).map((item) => (
|
||||
<li key={item.name} className="flex items-center gap-2">
|
||||
<span className="min-w-0 flex-1 truncate" title={item.name}>{item.name}</span>
|
||||
<span className="font-mono text-blue-600">{item.count}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1.4fr)_minmax(320px,0.6fr)]">
|
||||
<Card>
|
||||
<CardHeader><CardTitle>来源与设备</CardTitle></CardHeader>
|
||||
@@ -243,3 +306,21 @@ export function AnalyticsPageClient() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ContentMetricCard({
|
||||
label,
|
||||
total,
|
||||
uniqueActors,
|
||||
}: {
|
||||
label: string;
|
||||
total?: number;
|
||||
uniqueActors?: number;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-lg border border-white/10 p-4">
|
||||
<div className="text-xs text-slate-500">{label}</div>
|
||||
<div className="text-xl font-bold text-white">{total ?? 0}</div>
|
||||
<div className="mt-0.5 text-xs text-slate-500">独立 {uniqueActors ?? 0}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user