"use client"; import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { LinkOverviewData, ConversionFunnelData, VisitTrendsData, LinkPerformanceData, PlatformDistributionData, DeviceAnalysisData, PopularReferrersData, QrCodeAnalysisData, DeviceItem, ReferrerItem, } from "@/app/api/types"; import { TimeGranularity } from "@/lib/analytics"; interface LinkDetails { id: string; name: string; shortUrl: string; originalUrl: string; creator: string; createdAt: string; visits: number; visitChange: number; uniqueVisitors: number; uniqueVisitorsChange: number; avgTime: string; avgTimeChange: number; conversionRate: number; conversionChange: number; status: "active" | "inactive" | "expired"; tags: string[]; } export default function LinkDetailsPage({ params, }: { params: { id: string }; }) { const router = useRouter(); const [linkId, setLinkId] = useState(""); const [linkDetails, setLinkDetails] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState< | "overview" | "referrers" | "devices" | "locations" | "performance" | "qrCodes" >("overview"); // 添加state变量存储分析数据 const [overviewData, setOverviewData] = useState( null ); const [funnelData, setFunnelData] = useState( null ); const [trendsData, setTrendsData] = useState(null); // 添加新的state变量存储新API的数据 const [performanceData, setPerformanceData] = useState(null); const [platformData, setPlatformData] = useState(null); const [deviceData, setDeviceData] = useState(null); const [referrersData, setReferrersData] = useState(null); const [qrCodeData, setQrCodeData] = useState(null); const [timeGranularity, setTimeGranularity] = useState( TimeGranularity.DAY ); const [dateRange, setDateRange] = useState({ startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) .toISOString() .split("T")[0], // 30天前 endDate: new Date().toISOString().split("T")[0], // 今天 }); // 定义fetchAnalyticsData函数在所有useEffect前 const fetchAnalyticsData = useCallback(async (id: string) => { try { // 构建查询参数 const queryParams = new URLSearchParams({ linkId: id, startDate: dateRange.startDate, endDate: dateRange.endDate, }); // 并行获取所有数据 const [ overviewResponse, funnelResponse, trendsResponse, performanceResponse, platformResponse, deviceResponse, referrersResponse, qrCodeResponse, ] = await Promise.all([ fetch(`/api/analytics/overview?${queryParams}`), fetch(`/api/analytics/funnel?${queryParams}`), fetch( `/api/analytics/trends?${queryParams}&granularity=${timeGranularity}` ), fetch(`/api/analytics/link-performance?${queryParams}`), fetch(`/api/analytics/platform-distribution?${queryParams}`), fetch(`/api/analytics/device-analysis?${queryParams}`), fetch(`/api/analytics/popular-referrers?${queryParams}`), fetch(`/api/analytics/qr-code-analysis?${queryParams}`), ]); // 检查所有响应 if ( !overviewResponse.ok || !funnelResponse.ok || !trendsResponse.ok || !performanceResponse.ok || !platformResponse.ok || !deviceResponse.ok || !referrersResponse.ok || !qrCodeResponse.ok ) { throw new Error("Failed to fetch analytics data"); } // 解析所有响应数据 const [ overviewResult, funnelResult, trendsResult, performanceResult, platformResult, deviceResult, referrersResult, qrCodeResult, ] = await Promise.all([ overviewResponse.json(), funnelResponse.json(), trendsResponse.json(), performanceResponse.json(), platformResponse.json(), deviceResponse.json(), referrersResponse.json(), qrCodeResponse.json(), ]); // 设置状态 setOverviewData(overviewResult); setFunnelData(funnelResult); setTrendsData(trendsResult); setPerformanceData(performanceResult); setPlatformData(platformResult); setDeviceData(deviceResult); setReferrersData(referrersResult); setQrCodeData(qrCodeResult); // 更新链接详情中的统计数据 setLinkDetails((prev) => { if (!prev) return prev; return { ...prev, visits: overviewResult.totalVisits, uniqueVisitors: overviewResult.uniqueVisitors, avgTime: formatTime(overviewResult.averageTimeSpent), conversionRate: funnelResult.conversionRate, }; }); setLoading(false); } catch (error) { console.error("Failed to fetch analytics data:", error); setLoading(false); } }, [dateRange, timeGranularity]); // 获取并设置linkId useEffect(() => { const loadParams = async () => { const resolvedParams = await params; setLinkId(resolvedParams.id); }; loadParams(); }, [params]); // 获取链接详情数据 useEffect(() => { if (!linkId) return; const fetchLinkDetails = async () => { setLoading(true); try { // 获取链接详情 const response = await fetch(`/api/links/${linkId}/details`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || "Failed to fetch link details"); } const details = await response.json(); setLinkDetails(details); setLoading(false); // 获取分析数据 fetchAnalyticsData(linkId); } catch (error) { console.error("Failed to fetch link details:", error); setLoading(false); } }; fetchLinkDetails(); }, [linkId, fetchAnalyticsData]); // 格式化时间(秒转为分钟和秒) const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds % 60); return `${minutes}m ${remainingSeconds}s`; }; // 更新时间粒度并重新获取趋势数据 const updateTimeGranularity = (granularity: TimeGranularity) => { setTimeGranularity(granularity); if (linkId) { const queryParams = new URLSearchParams({ linkId, startDate: dateRange.startDate, endDate: dateRange.endDate, granularity, }); fetch(`/api/analytics/trends?${queryParams}`) .then((res) => res.json()) .then((data) => setTrendsData(data)) .catch((err) => console.error("Failed to update trends data:", err)); } }; // 更新日期范围并重新获取所有数据 const updateDateRange = (startDate: string, endDate: string) => { setDateRange({ startDate, endDate }); if (linkId) { fetchAnalyticsData(linkId); } }; const goBack = () => { router.back(); }; // 复制链接到剪贴板 const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text); // 可以添加一个复制成功的提示 }; // 添加加载和错误处理 if (loading) { return (

加载中...

正在获取链接详情数据

); } if (!linkDetails) { return (

未找到链接

无法获取该链接的详细信息

返回链接列表
); } // 只有当linkDetails存在时才渲染详情内容 return (
{/* 顶部导航栏 */}
{/* 链接基本信息卡片 */}

{linkDetails.name}

Short URL
{linkDetails.shortUrl}
Created By

{linkDetails.creator}

Created At

{linkDetails.createdAt}

Status
{linkDetails.status ? linkDetails.status.charAt(0).toUpperCase() + linkDetails.status.slice(1) : 'Unknown'}
{linkDetails.tags && linkDetails.tags.length > 0 && (
Tags
{linkDetails.tags.map((tag) => ( {tag} ))}
)}
{/* 性能指标卡片 */}

Performance Metrics

{/* Total Visits */}
Total Visits
= 0 ? "text-accent-green" : "text-accent-red"}`}> = 0 ? "text-accent-green" : "text-accent-red transform rotate-180"}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" > {Math.abs(linkDetails?.visitChange || 0)}%

{linkDetails?.visits !== undefined ? linkDetails.visits.toLocaleString() : '0'}

{/* Unique Visitors */}
Unique Visitors
= 0 ? "text-accent-green" : "text-accent-red"}`}> = 0 ? "text-accent-green" : "text-accent-red transform rotate-180"}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" > {Math.abs(linkDetails?.uniqueVisitorsChange || 0)}%

{linkDetails?.uniqueVisitors !== undefined ? linkDetails.uniqueVisitors.toLocaleString() : '0'}

{/* Average Visit Time */}
Avg. Time
= 0 ? "text-accent-green" : "text-accent-red"}`}> = 0 ? "text-accent-green" : "text-accent-red transform rotate-180"}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" > {Math.abs(linkDetails?.avgTimeChange || 0)}%

{linkDetails?.avgTime || '0s'}

{/* Conversion Rate */}
Conversion
= 0 ? "text-accent-green" : "text-accent-red"}`}> = 0 ? "text-accent-green" : "text-accent-red transform rotate-180"}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" > {Math.abs(linkDetails?.conversionChange || 0)}%

{linkDetails?.conversionRate !== undefined ? `${linkDetails.conversionRate}%` : '0%'}

{/* 图表和详细数据部分 */}
{activeTab === "overview" && (
{/* 日期范围选择器 */}
Analytics Overview
updateDateRange(e.target.value, dateRange.endDate) } className="px-3 py-2 text-sm border rounded-md bg-card-bg border-card-border" />
to
updateDateRange( dateRange.startDate, e.target.value ) } className="px-3 py-2 text-sm border rounded-md bg-card-bg border-card-border" min={dateRange.startDate} />
{/* 设备类型分布 */} {overviewData && (

Device Types

Mobile
{overviewData.deviceTypes.mobile}
{overviewData.totalVisits ? Math.round( (overviewData.deviceTypes.mobile / overviewData.totalVisits) * 100 ) : 0} %
Desktop
{overviewData.deviceTypes.desktop}
{overviewData.totalVisits ? Math.round( (overviewData.deviceTypes.desktop / overviewData.totalVisits) * 100 ) : 0} %
Tablet
{overviewData.deviceTypes.tablet}
{overviewData.totalVisits ? Math.round( (overviewData.deviceTypes.tablet / overviewData.totalVisits) * 100 ) : 0} %
Other
{overviewData.deviceTypes.other}
{overviewData.totalVisits ? Math.round( (overviewData.deviceTypes.other / overviewData.totalVisits) * 100 ) : 0} %
)} {/* 转化漏斗 */} {funnelData && (

Conversion Funnel

Overall Conversion Rate:{" "} {(funnelData.conversionRate || 0).toFixed(2)}%
{funnelData.steps.map((step) => (
{step.name} {step.value} ({(step.percent || 0).toFixed(1)} %)
))}
)} {/* 访问趋势 */} {trendsData && (

Visit Trends

{Object.values(TimeGranularity).map( (granularity) => ( ) )}
Total Visits:{" "} {trendsData.totals.visits}
Unique Visitors:{" "} {trendsData.totals.uniqueVisitors}
{/* 简单趋势表格 */}
{trendsData.trends.map((trend, i) => ( ))}
Time Visits Unique Visitors
{trend.timestamp} {trend.visits} {trend.uniqueVisitors}
)}
)} {activeTab === "performance" && performanceData && (

Link Performance

Total Clicks
{performanceData.totalClicks}
Unique Visitors
{performanceData.uniqueVisitors}
Bounce Rate
{performanceData.bounceRate}%
Conversion Rate
{performanceData.conversionRate}%
Avg. Time Spent
{formatTime(performanceData.averageTimeSpent)}
Active Days
{performanceData.activeDays}
Unique Referrers
{performanceData.uniqueReferrers}
Last Click
{performanceData.lastClickTime ? new Date( performanceData.lastClickTime ).toLocaleString() : "Never"}
)} {activeTab === "referrers" && referrersData && (

Popular Referrers

{referrersData.referrers.map( (referrer: ReferrerItem, i: number) => ( ) )}
Source Visits Unique Visitors Conversion Rate Avg. Time Spent
{referrer.source} {referrer.visitCount} ({referrer.percent}%) {referrer.uniqueVisitors} {referrer.conversionRate}% {formatTime(referrer.averageTimeSpent)}
)} {activeTab === "devices" && deviceData && (

Device Types

{deviceData.deviceTypes.map( (device: DeviceItem, i: number) => (
{device.name}
{device.count}
{device.percent}%
) )}

Device Brands

{deviceData.deviceBrands.map( (brand: DeviceItem, i: number) => ( ) )}
Brand Count Percentage
{brand.name} {brand.count} {brand.percent}%
)} {activeTab === "locations" && platformData && (

Platform Distribution

Operating Systems

{platformData.platforms.map((platform, i) => ( ))}
OS Visits Percentage
{platform.name} {platform.count} {platform.percent}%

Browsers

{platformData.browsers.map((browser, i) => ( ))}
Browser Visits Percentage
{browser.name} {browser.count} {browser.percent}%
)} {activeTab === "qrCodes" && qrCodeData && (

QR Code Analysis

Total Scans
{qrCodeData.overview.totalScans}
Unique Scanners
{qrCodeData.overview.uniqueScanners}
Conversion Rate
{qrCodeData.overview.conversionRate}%
Avg. Time Spent
{formatTime(qrCodeData.overview.averageTimeSpent)}

Scan Locations

{qrCodeData.locations.map((location, i) => ( ))}
Location Scans Percentage
{location.city}, {location.country} {location.scanCount} {location.percent}%

Scan Time Distribution

{qrCodeData.hourlyDistribution.map((hour) => ( ))}
Hour Scans Percentage
{hour.hour}:00 - {hour.hour}:59 {hour.scanCount} {hour.percent.toFixed(1)}%
)}
); }