"use client"; import React from "react"; import ReactECharts from "echarts-for-react"; type OverviewResponse = { schema: any; metrics: Record; }; type Winner = { product_id: string; title?: string | null; category?: string | null; units?: number; gmv?: number; potential_score?: number; burst_score?: number; follow_score?: number; lifecycle?: string; }; async function getJSON(path: string): Promise { const r = await fetch(path, { cache: "no-store" }); if (!r.ok) throw new Error(await r.text()); return (await r.json()) as T; } async function postJSON(path: string, body: any): Promise { const r = await fetch(path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) }); if (!r.ok) throw new Error(await r.text()); return (await r.json()) as T; } export default function Dashboard() { const [overview, setOverview] = React.useState(null); const [winners, setWinners] = React.useState([]); const [productId, setProductId] = React.useState(""); const [series, setSeries] = React.useState<{ ds: string; units: number; gmv: number }[]>([]); const [forecast, setForecast] = React.useState<{ ds: string; units_hat: number }[]>([]); const [aiQuery, setAiQuery] = React.useState("给我当前最值得跟卖的3个品类/商品,并说明理由与风险。"); const [aiAnswer, setAiAnswer] = React.useState(""); const [err, setErr] = React.useState(""); React.useEffect(() => { (async () => { try { const o = await getJSON("/api/metrics/overview"); setOverview(o); const w = await getJSON<{ items: Winner[] }>("/api/trend/potential-winners?days=14&limit=30"); setWinners(w.items); } catch (e: any) { setErr(String(e?.message || e)); } })(); }, []); async function loadProduct(p: string) { setErr(""); if (!p.trim()) { setErr("请输入 product_id(不能为空)"); return; } try { const ts = await getJSON<{ product_id: string; points: any[] }>( `/api/metrics/sales/timeseries?product_id=${encodeURIComponent(p)}&days=60` ); setSeries(ts.points as any); const fc = await getJSON<{ forecast: any[] }>( `/api/trend/forecast?product_id=${encodeURIComponent(p)}&days=60&horizon=14` ); setForecast(fc.forecast as any); } catch (e: any) { setErr(String(e?.message || e)); } } async function runAI() { setErr(""); try { const r = await postJSON<{ answer: string }>("/api/ai/insight", { query: aiQuery, product_id: productId || undefined, top_k: 6 }); setAiAnswer(r.answer || ""); } catch (e: any) { setErr(String(e?.message || e)); } } const chartOption = { tooltip: { trigger: "axis" }, legend: { data: ["units", "gmv", "forecast_units"] }, xAxis: { type: "category", data: series.map((p) => p.ds.slice(0, 10)) }, yAxis: [{ type: "value" }, { type: "value" }], series: [ { name: "units", type: "line", smooth: true, data: series.map((p) => p.units) }, { name: "gmv", type: "line", smooth: true, yAxisIndex: 1, data: series.map((p) => p.gmv) }, { name: "forecast_units", type: "line", smooth: true, lineStyle: { type: "dashed" }, data: new Array(Math.max(0, series.length - 1)).fill(null).concat(forecast.map((f) => f.units_hat)) } ] }; return (

爆款闭环 BI 看板

{err ? (
          {err}
        
) : null}
{overview?.metrics?.products ?? "-"} {overview?.metrics?.units_30d ?? "-"} {overview?.metrics?.gmv_30d ?? "-"} {overview?.metrics?.rows_30d ?? "-"}
实时曲线 + 趋势预测 setProductId(e.target.value)} placeholder="输入 product_id" style={{ flex: 1, padding: "8px 10px", borderRadius: 8, border: "1px solid #ddd" }} />
发现爆款(Potential Winners)
{winners.map((w) => (
{ setProductId(w.product_id); loadProduct(w.product_id); }} >
{w.title || w.product_id}
{w.lifecycle}
units={fmt(w.units)} gmv={fmt(w.gmv)} · potential={fmt(w.potential_score)} · burst={fmt(w.burst_score)} · follow={fmt(w.follow_score)}
))}
AI 分析与决策辅助