"use client"; import { useEffect, useState } from "react"; import { RefreshCcw, TrendingUp, Users, CreditCard, Database, Activity } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { opsApi } from "@/lib/ops-api"; import Link from "next/link"; import type { SystemStatusPayload, MembershipsPayload, FunnelPayload } from "@/types/ops"; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, } from "recharts"; export function OverviewPageClient() { const [loading, setLoading] = useState(true); const [status, setStatus] = useState(null); const [memberships, setMemberships] = useState(null); const [funnel, setFunnel] = useState(null); const load = async () => { setLoading(true); try { const [s, m, f] = await Promise.all([ opsApi.systemStatus() as Promise, opsApi.memberships() as Promise, opsApi.funnel(7) as Promise, ]); setStatus(s); setMemberships(m); setFunnel(f); } catch { /* */ } setLoading(false); }; useEffect(() => { void load(); }, []); if (loading) return
加载中...
; const cacheAnalysis = status?.cache?.analysis; const cacheData = cacheAnalysis ? [ { name: "命中", value: cacheAnalysis.cache_hits ?? 0, color: "#22c55e" }, { name: "未命中", value: cacheAnalysis.cache_misses ?? 0, color: "#f59e0b" }, { name: "强制刷新", value: cacheAnalysis.force_refresh_requests ?? 0, color: "#3b82f6" }, ].filter((d) => d.value > 0) : []; const mems = memberships?.memberships ?? []; const paid = mems.filter((m) => !(m as Record).is_trial).length; const trials = mems.filter((m) => (m as Record).is_trial).length; const steps = funnel?.steps ?? []; const totalUsers = steps[0]?.count ?? 0; const payingUsers = steps[5]?.count ?? 0; const convRate = totalUsers > 0 ? ((payingUsers / totalUsers) * 100).toFixed(1) : "—"; return (

总览

{/* KPI cards */}
系统状态
{status?.db?.ok ? "OK" : "—"}
付费会员
{paid}
{trials > 0 &&
体验 {trials}
}
支付成功
{payingUsers}
转化率
{convRate}%
训练数据
{status?.training_data?.truth_records?.row_count ?? "—"}
{/* Cache pie chart */} {cacheData.length > 0 && (

缓存命中率

{cacheData.map((d, i) => ( ))}
{cacheData.map((d) => (
{d.name} {d.value}
))} {cacheAnalysis?.hit_rate != null && (
命中率 {(cacheAnalysis.hit_rate * 100).toFixed(1)}%
)}
)}
); }