"use client"; import { useEffect, useRef, useState, type ReactNode } from "react"; import { BarChart3, Building2, CalendarDays, Loader2, RefreshCw, RotateCcw, Users } from "lucide-react"; import { crossfadeIn, pulseFeedback, revealChildren, runScopedMotion } from "@/lib/ui/motion"; import { usagePresetRange, type AdminUsageReport } from "@/lib/usage"; type UsageFilters = { startDate: string; endDate: string; organizationId: string; ownerId: string; capability: string; provider: string; }; function initialFilters(): UsageFilters { const range = usagePresetRange("month"); return { startDate: range.startDate, endDate: range.endDate, organizationId: "", ownerId: "", capability: "", provider: "" }; } export function UsageManager({ isSuperAdmin }: { isSuperAdmin: boolean }) { const [filters, setFilters] = useState(initialFilters); const [report, setReport] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const managerRef = useRef(null); const contentRef = useRef(null); const feedbackRef = useRef(null); useEffect(() => { const controller = new AbortController(); void loadUsage(controller.signal); return () => controller.abort(); }, [filters]); useEffect(() => runScopedMotion(managerRef, (scope) => revealChildren(scope)), []); useEffect(() => { crossfadeIn(contentRef.current); }, [report]); useEffect(() => { pulseFeedback(feedbackRef.current); }, [error, report?.warnings]); async function loadUsage(signal?: AbortSignal) { setLoading(true); setError(null); try { const params = new URLSearchParams(); for (const [key, value] of Object.entries(filters)) { if (value) params.set(key, value); } const response = await fetch(`/api/admin/usage?${params.toString()}`, { cache: "no-store", signal }); const payload = await response.json() as AdminUsageReport & { error?: string }; if (!response.ok) throw new Error(payload.error || "读取用量数据失败"); setReport(payload); } catch (nextError) { if (nextError instanceof DOMException && nextError.name === "AbortError") return; setError(nextError instanceof Error ? nextError.message : String(nextError)); } finally { if (!signal?.aborted) setLoading(false); } } function updateFilter(key: keyof UsageFilters, value: string) { setFilters((current) => ({ ...current, [key]: value })); } function resetFilters() { setFilters(initialFilters()); } return (

用量管理

按成功任务统计平台内账号与组织的真实服务用量。

北京时间 {report?.range.label || "本月"}
{isSuperAdmin ? ( <> ) : null}
{error || report?.warnings?.length ? (
{error ?
{error}
: null} {!error && report?.warnings?.length ? (
{report.warnings.join(";")}
) : null}
) : null} {loading && !report ? (
) : report ? (
} label="成功任务" value={report.summary.total} suffix="次" /> } label="活跃账号" value={report.summary.activeAccounts} suffix="个" /> } label="活跃组织" value={report.summary.activeOrganizations} suffix="个" /> } label="日均用量" value={report.summary.averagePerDay} suffix="次" />

用量趋势

{report.range.dayCount > 62 ? "按月汇总" : "按日汇总"}

功能分布

每个成功任务计 1 次

服务商分布

组织汇总

{report.organizations.length} 个统计分组

{report.organizations.length ? report.organizations.map((row) => (
{row.organizationName} {row.accountCount} 个 {row.count}
)) : }
{isSuperAdmin ?

账号汇总

{report.accounts.length} 个活跃账号

{report.accounts.length ? report.accounts.map((row) => (
{row.accountName} {row.accountUsername ? {row.accountUsername} : null}
{row.organizationName} {row.count}
)) : }
: null} {isSuperAdmin ?

用量明细

仅显示计量元数据,不展示创作内容

最近 {report.recent.length} 条
{report.recent.length ? report.recent.map((record) => (
{record.accountName} {record.organizationName}
{record.capabilityLabel} {record.providerLabel}{record.reqKey ? ` · ${record.reqKey}` : ""}
1 次
)) : }
: null}
) : null}
); } function UsageMetric({ icon, label, value, suffix }: { icon: ReactNode; label: string; value: number; suffix: string }) { return (
{label} {value}{suffix}
); } function UsageTrend({ report }: { report: AdminUsageReport }) { const max = Math.max(1, ...report.trend.map((point) => point.count)); if (!report.trend.length) return ; return (
{report.trend.map((point) => (
{point.count || ""}
{point.label}
))}
); } function UsageBreakdown({ items, total, compact = false }: { items: AdminUsageReport["byCapability"]; total: number; compact?: boolean }) { if (!items.length) return ; return (
{items.map((item) => (
{item.label}{item.count}
))}
); } function UsageEmpty({ label }: { label: string }) { return
{label}
; } function formatAdminTime(value?: string): string { if (!value) return "—"; return new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false }).format(new Date(value)); }