"use client"; import { useState, useEffect, useRef } from 'react'; import { DeviceAnalytics } from '../../api/types'; import { Chart, PieController, ArcElement, Tooltip, Legend, CategoryScale, LinearScale } from 'chart.js'; // 注册Chart.js组件 Chart.register(PieController, ArcElement, Tooltip, Legend, CategoryScale, LinearScale); export default function DeviceAnalyticsPage() { const [deviceData, setDeviceData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [dateRange, setDateRange] = useState({ from: new Date('2024-02-01'), to: new Date('2025-03-05') }); // 创建图表引用 const deviceTypesChartRef = useRef(null); const browsersChartRef = useRef(null); const osChartRef = useRef(null); // 图表实例引用 const deviceTypesChartInstance = useRef(null); const browsersChartInstance = useRef(null); const osChartInstance = useRef(null); // 颜色配置 const COLORS = { deviceTypes: ['rgba(59, 130, 246, 0.8)', 'rgba(96, 165, 250, 0.8)', 'rgba(147, 197, 253, 0.8)', 'rgba(191, 219, 254, 0.8)', 'rgba(219, 234, 254, 0.8)'], browsers: ['rgba(16, 185, 129, 0.8)', 'rgba(52, 211, 153, 0.8)', 'rgba(110, 231, 183, 0.8)', 'rgba(167, 243, 208, 0.8)', 'rgba(209, 250, 229, 0.8)'], os: ['rgba(239, 68, 68, 0.8)', 'rgba(248, 113, 113, 0.8)', 'rgba(252, 165, 165, 0.8)', 'rgba(254, 202, 202, 0.8)', 'rgba(254, 226, 226, 0.8)'] }; useEffect(() => { const fetchDeviceData = async () => { try { setIsLoading(true); setError(null); const response = await fetch(`/api/events/devices?startTime=${dateRange.from.toISOString().split('T')[0]}T00:00:00Z&endTime=${dateRange.to.toISOString().split('T')[0]}T23:59:59Z`); if (!response.ok) throw new Error('Failed to fetch device data'); const data = await response.json(); setDeviceData(data.data); } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred'); } finally { setIsLoading(false); } }; fetchDeviceData(); }, [dateRange]); // 初始化和更新图表 useEffect(() => { if (!deviceData || isLoading) return; // 销毁旧的图表实例 if (deviceTypesChartInstance.current) { deviceTypesChartInstance.current.destroy(); } if (browsersChartInstance.current) { browsersChartInstance.current.destroy(); } if (osChartInstance.current) { osChartInstance.current.destroy(); } // 创建设备类型图表 if (deviceTypesChartRef.current && deviceData.deviceTypes.length > 0) { const ctx = deviceTypesChartRef.current.getContext('2d'); if (ctx) { deviceTypesChartInstance.current = new Chart(ctx, { type: 'pie', data: { labels: deviceData.deviceTypes.map(item => item.type), datasets: [{ data: deviceData.deviceTypes.map(item => item.count), backgroundColor: COLORS.deviceTypes, borderColor: COLORS.deviceTypes.map(color => color.replace('0.8', '1')), borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { color: 'white' } }, tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.raw as number; const total = (context.chart.data.datasets[0].data as number[]).reduce((a, b) => (a as number) + (b as number), 0); const percentage = Math.round((value * 100) / total); return `${label}: ${value} (${percentage}%)`; } } } } } }); } } // 创建浏览器图表 if (browsersChartRef.current && deviceData.browsers.length > 0) { const ctx = browsersChartRef.current.getContext('2d'); if (ctx) { browsersChartInstance.current = new Chart(ctx, { type: 'pie', data: { labels: deviceData.browsers.map(item => item.name), datasets: [{ data: deviceData.browsers.map(item => item.count), backgroundColor: COLORS.browsers, borderColor: COLORS.browsers.map(color => color.replace('0.8', '1')), borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { color: 'white' } }, tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.raw as number; const total = (context.chart.data.datasets[0].data as number[]).reduce((a, b) => (a as number) + (b as number), 0); const percentage = Math.round((value * 100) / total); return `${label}: ${value} (${percentage}%)`; } } } } } }); } } // 创建操作系统图表 if (osChartRef.current && deviceData.operatingSystems.length > 0) { const ctx = osChartRef.current.getContext('2d'); if (ctx) { osChartInstance.current = new Chart(ctx, { type: 'pie', data: { labels: deviceData.operatingSystems.map(item => item.name), datasets: [{ data: deviceData.operatingSystems.map(item => item.count), backgroundColor: COLORS.os, borderColor: COLORS.os.map(color => color.replace('0.8', '1')), borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { position: 'bottom', labels: { color: 'white' } }, tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.raw as number; const total = (context.chart.data.datasets[0].data as number[]).reduce((a, b) => (a as number) + (b as number), 0); const percentage = Math.round((value * 100) / total); return `${label}: ${value} (${percentage}%)`; } } } } } }); } } // 清理函数 return () => { if (deviceTypesChartInstance.current) { deviceTypesChartInstance.current.destroy(); } if (browsersChartInstance.current) { browsersChartInstance.current.destroy(); } if (osChartInstance.current) { osChartInstance.current.destroy(); } }; }, [deviceData, isLoading]); return (
{/* 页面标题 */}

Device Analytics

Analyze visitor distribution by devices, browsers, and operating systems

{/* 时间范围选择器 */}
setDateRange(prev => ({ ...prev, from: new Date(e.target.value) }))} />
setDateRange(prev => ({ ...prev, to: new Date(e.target.value) }))} />
{/* 设备类型分析 */}
{/* 设备类型 */}

Device Types

{deviceData && deviceData.deviceTypes.length > 0 ? (
) : (
No data available
)}
{/* 浏览器 */}

Browsers

{deviceData && deviceData.browsers.length > 0 ? (
) : (
No data available
)}
{/* 操作系统 */}

Operating Systems

{deviceData && deviceData.operatingSystems.length > 0 ? (
) : (
No data available
)}
{/* 加载状态 */} {isLoading && (
)} {/* 错误状态 */} {error && (

{error}

)} {/* 无数据状态 */} {!isLoading && !error && !deviceData && (

No device data available

)}
); }