"use client"; import { useEffect, useState } from "react"; import dynamic from "next/dynamic"; import { RefreshCcw } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { opsApi } from "@/lib/ops-api"; const AnalyticsFunnelChart = dynamic( () => import("./AnalyticsFunnelChart").then((mod) => mod.AnalyticsFunnelChart), { ssr: false, loading: () =>
, }, ); type FunnelStep = { key: string; label: string; count: number; uniqueActors: number; pct_of_prev?: number; }; type TopItem = { name: string; count: number }; type AuthDiagnostic = { total?: number; unique_actors?: number; by_reason?: TopItem[]; }; export function AnalyticsPageClient() { const [loading, setLoading] = useState(true); const [funnel, setFunnel] = useState([]); const [diagnostics, setDiagnostics] = useState>({}); const [rates, setRates] = useState | null>(null); const [traffic, setTraffic] = useState>({}); const [days, setDays] = useState(30); const load = async () => { setLoading(true); try { const data = await opsApi.funnel(days); setFunnel(data.steps); setDiagnostics(data.diagnostics ?? {}); setRates(data.rates ?? null); setTraffic(data.traffic ?? {}); } catch { /* */ } setLoading(false); }; useEffect(() => { void load(); }, [days]); const chartData = [...funnel].reverse().map((s) => ({ name: s.label, count: s.count, pct: s.pct_of_prev, })); const stepByKey = Object.fromEntries(funnel.map((step) => [step.key, step])); const degradedAuth = diagnostics.degraded_auth_profile ?? {}; const landingView = stepByKey.landing_view; const terminalEntry = stepByKey.enter_terminal; const signupSuccess = stepByKey.signup_success; const trialCreated = stepByKey.trial_created; const paymentStart = stepByKey.payment_start; const paymentSuccess = stepByKey.payment_success; const overallRate = landingView?.uniqueActors && paymentSuccess?.uniqueActors ? ((paymentSuccess.uniqueActors / landingView.uniqueActors) * 100).toFixed(1) : "—"; if (loading) return
加载中...
; return (

转化分析

{[7, 14, 30].map((d) => ( ))}
{funnel.length > 0 && (
落地页访问
{landingView?.count ?? 0}
独立 {landingView?.uniqueActors ?? 0}
进入终端
{terminalEntry?.count ?? 0}
独立 {terminalEntry?.uniqueActors ?? 0}
注册成功
{signupSuccess?.count ?? 0}
试用 {trialCreated?.count ?? 0}
发起支付
{paymentStart?.count ?? 0}
支付成功
{paymentSuccess?.count ?? 0}
总体转化 {overallRate === "—" ? "—" : `${overallRate}%`}
鉴权降级
{degradedAuth.total ?? 0}
独立 {degradedAuth.unique_actors ?? 0}
)}
来源与设备
{[ ["来源", traffic.referrers ?? []], ["国家/地区", traffic.countries ?? []], ["设备", traffic.devices ?? []], ["落地页路径", traffic.landing_paths ?? []], ].map(([title, rows]) => (
{String(title)}
{(rows as TopItem[]).length === 0 ? (
暂无数据
) : (
    {(rows as TopItem[]).slice(0, 5).map((item) => (
  • {item.name} {item.count}
  • ))}
)}
))}
鉴权降级原因 {(degradedAuth.by_reason ?? []).length === 0 ? (

暂无 degraded_auth_profile

) : (
    {(degradedAuth.by_reason ?? []).map((item) => (
  • {item.name} {item.count}
  • ))}
)}
转化漏斗 {chartData.length === 0 ? (

暂无数据

) : ( )}
各阶段详情
{funnel.map((step, i) => { const pct = step.pct_of_prev; const dropPct = pct != null ? Math.max(0, 100 - pct) : 0; return ( ); })}
阶段 次数 独立用户/访客 转化率 流失率
{step.label} {step.count} {step.uniqueActors} {pct != null ? `${pct}%` : "—"} {i > 0 ? `${dropPct}%` : "—"}
{rates ? (

rates 以 unique actors 计算,次数用于观察重复尝试和重试行为。

) : null}
); }