"use client"; import { useEffect, useState, type ReactNode } from "react"; import { onDocumentVisible } from "@/lib/visibilityReconcile"; /** 事件类型中文标签 */ export function kindLabel(kind: string): string { switch (kind) { case "pageview": return "浏览"; case "bot": return "爬虫"; case "probe": return "探测"; default: return kind || "-"; } } /** 来源类别中文标签 */ export function refKindLabel(kind: string): string { switch (kind) { case "direct": return "直接访问"; case "internal": return "站内"; case "search": return "搜索引擎"; case "social": return "社交"; case "external": return "外部链接"; default: return kind || "-"; } } export function deviceLabel(d: string): string { switch (d) { case "computer": return "电脑"; case "mobile": return "手机"; case "tablet": return "平板"; default: return d || "未知"; } } export function formatBytes(n: number): string { if (!Number.isFinite(n) || n <= 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let v = n; let i = 0; while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; } return `${v >= 100 ? Math.round(v) : v.toFixed(1)} ${units[i]}`; } export function formatInt(n: number): string { return new Intl.NumberFormat("zh-CN").format(n); } /** 简单加载/错误占位(居中卡片内文本) */ export function StatsState({ loading, error }: { loading?: boolean; error?: string }) { if (loading) { return (
正在加载…
); } if (error) { return (
{error}
); } return null; } export function TableEmpty({ text = "暂无数据" }: { text?: string }) { return (

{text}

); } /** 15 秒轮询 + 回前台即拉;返回手动 reload */ export function usePolling(load: () => void, intervalMs = 15_000) { const [, setTick] = useState(0); useEffect(() => { load(); const timer = setInterval(load, intervalMs); const off = onDocumentVisible(load); return () => { clearInterval(timer); off(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [intervalMs]); return () => setTick((t) => t + 1); } /** 7/30 日区间切换段控件(各 Tab 复用) */ export function RangeSegmented({ value, onChange, }: { value: "7d" | "30d"; onChange: (v: "7d" | "30d") => void; }) { return (
{( [ { key: "7d", label: "近 7 日" }, { key: "30d", label: "近 30 日" }, ] as const ).map((o) => ( ))}
); } export function CardShell({ title, hint, actions, children, }: { title: string; hint?: string; actions?: ReactNode; children: ReactNode; }) { return (

{title}

{hint ?

{hint}

: null}
{actions}
{children}
); }