"use client"; import { useState } from "react"; import { useSession } from "next-auth/react"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { reportsApi } from "@/lib/api/reports"; import { fetchWithAuth } from "@/lib/api/client"; import type { QueryListResponse } from "@/lib/api/queries"; import type { CitationListResponse, CitationStats } from "@/lib/api/citations"; import { useApi } from "@/lib/hooks/use-api"; import { clsx } from "clsx"; import { Loader2, FileDown, FileText, Info, CheckCircle, AlertTriangle, Search, Quote, Percent, BarChart3, } from "lucide-react"; export default function ReportsPage() { const { data: session } = useSession(); const [selectedQuery, setSelectedQuery] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); const { data: queriesData } = useApi("/api/v1/queries/"); const { data: statsData, isLoading: statsLoading } = useApi("/api/v1/citations/stats/"); const { data: citationsData, isLoading: citationsLoading } = useApi("/api/v1/citations/?limit=10"); const queries = queriesData?.items ?? []; const recentCitations = citationsData?.items ?? []; async function handleExportCSV() { if (!session?.accessToken) return; if (!selectedQuery) { setError("请先选择要导出的查询词"); return; } await handleExport("csv"); } async function handleExportPDF() { if (!session?.accessToken) return; if (!selectedQuery) { setError("请先选择要导出的查询词"); return; } await handleExport("pdf"); } async function handleExport(format: "csv" | "pdf") { if (!session?.accessToken) return; try { setLoading(true); setError(null); setSuccess(false); const queryId = selectedQuery; let blob: Blob; let filename: string; if (format === "csv") { const query = `?query_id=${queryId}`; blob = await fetchWithAuth(`/api/v1/reports/export/csv${query}`, {}, session.accessToken, "blob") as unknown as Blob; filename = `report_${queryId}_${new Date().toISOString().split("T")[0]}.csv`; } else { blob = await reportsApi.exportPDF(session.accessToken, queryId); filename = `report_${queryId}_${new Date().toISOString().split("T")[0]}.pdf`; } const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = downloadUrl; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(downloadUrl); setSuccess(true); setTimeout(() => setSuccess(false), 3000); } catch (err) { setError(err instanceof Error ? err.message : "导出失败"); } finally { setLoading(false); } } const statCards = [ { title: "总查询", value: statsData?.total_queries ?? 0, icon: Search, color: "text-blue-600", bg: "bg-blue-50", }, { title: "引用次数", value: statsData?.total_citations ?? 0, icon: Quote, color: "text-emerald-600", bg: "bg-emerald-50", }, { title: "引用率", value: statsData ? `${statsData.citation_rate.toFixed(1)}%` : "0%", icon: Percent, color: "text-violet-600", bg: "bg-violet-50", }, { title: "平均位置", value: statsData?.avg_position ? statsData.avg_position.toFixed(1) : "-", icon: BarChart3, color: "text-amber-600", bg: "bg-amber-50", }, ]; function formatDate(dateStr: string) { if (!dateStr) return "-"; const d = new Date(dateStr); return d.toLocaleDateString("zh-CN") + " " + d.toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }); } return (

报告导出

导出引用检测数据为报告文件

导出设置 选择查询词和导出格式
{error && (
{error}
)} {success && (
导出成功,文件已自动下载
)}
使用说明

1. 选择要导出的查询词

2. 支持 CSV 和 PDF 两种格式导出

3. 导出文件包含以下字段:

  • 查询关键词
  • 检测平台
  • 是否被引用
  • 引用位置
  • 引用文本
  • 竞争品牌
  • 查询时间

4. 文件将自动下载到您的设备

{/* Stats Preview */}

数据概览

{statCards.map((card) => (

{card.title}

{statsLoading ? : card.value}

))}
{/* Recent Citations */} 最近引用记录 最近 10 条引用检测结果
平台 状态 位置 引用文本 竞争品牌 检测时间 {citationsLoading ? ( ) : recentCitations.length === 0 ? ( 暂无引用记录 ) : ( recentCitations.map((c) => ( {c.platform} {c.cited ? ( 已引用 ) : ( 未引用 )} {c.citation_position ?? "-"} {c.citation_text || "-"} {c.competitor_brands?.length > 0 ? c.competitor_brands.join(", ") : "-"} {formatDate(c.queried_at)} )) )}
); }