"use client"; import { useEffect, useState } from "react"; import { AdminChartCard, NamedBarChart, NamedPieChart } from "@/components/admin/charts"; import { apiStatsSources, type VisitSources } from "@/lib/api"; import { RangeSegmented, StatsState, TableEmpty, formatInt, refKindLabel } from "../ui"; /** 来源按日表:固定展示四类主要来源列 */ const TREND_KINDS = ["direct", "search", "social", "external"] as const; export default function SourcesPage() { const [range, setRange] = useState<"7d" | "30d">("7d"); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); useEffect(() => { let alive = true; setLoading(true); apiStatsSources(range) .then((d) => { if (alive) { setData(d); setError(""); } }) .catch(() => alive && setError("加载失败,请稍后重试")) .finally(() => alive && setLoading(false)); return () => { alive = false; }; }, [range]); if (!data) return ; const kinds = data.kinds.map((k) => ({ label: refKindLabel(k.key), count: k.count })); return (

按浏览来源(Referer)自动分类;爬虫与探测请求不计入。

{error ? : null} {kinds.length ? : }
{data.search_terms.length ? (
    {data.search_terms.map((t, i) => (
  • {i + 1} {t.key} {formatInt(t.count)}
  • ))}
) : ( )}
{data.external.length ? ( ({ label: h.key, count: h.count }))} /> ) : ( )}
{data.trend.length ? (
{TREND_KINDS.map((k) => ( ))} {data.trend.map((row) => { const byKey = new Map(row.items.map((i) => [i.key, i.count])); return ( {TREND_KINDS.map((k) => ( ))} ); })}
日期 {refKindLabel(k)}
{row.date.slice(0, 10)} {(byKey.get(k) ?? 0) === 0 ? ( — ) : ( (byKey.get(k) ?? 0).toLocaleString("zh-CN") )}
) : ( )}
); }