add path cont
This commit is contained in:
111
app/components/analytics/PathAnalytics.tsx
Normal file
111
app/components/analytics/PathAnalytics.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
interface PathAnalyticsProps {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
linkId?: string;
|
||||
}
|
||||
|
||||
interface PathData {
|
||||
path: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
const PathAnalytics: React.FC<PathAnalyticsProps> = ({ startTime, endTime, linkId }) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [pathData, setPathData] = useState<PathData[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!linkId) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchPathData = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
startTime,
|
||||
endTime,
|
||||
linkId
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/events/path-analytics?${params.toString()}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('获取路径分析数据失败');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.data) {
|
||||
setPathData(result.data);
|
||||
} else {
|
||||
setError(result.error || '加载路径分析失败');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '发生错误');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPathData();
|
||||
}, [startTime, endTime, linkId]);
|
||||
|
||||
if (loading) {
|
||||
return <div className="py-8 flex justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500" />
|
||||
</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="py-4 text-red-500">{error}</div>;
|
||||
}
|
||||
|
||||
if (!linkId) {
|
||||
return <div className="py-4 text-gray-500">选择一个特定链接查看路径分析。</div>;
|
||||
}
|
||||
|
||||
if (pathData.length === 0) {
|
||||
return <div className="py-4 text-gray-500">该链接暂无路径数据。</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="text-sm text-gray-500 mb-4">
|
||||
注意:不同的URL参数组合会被视为不同的路径(例如 /abc?p=1 和 /abc?p=2 属于不同路径)
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">路径</th>
|
||||
<th className="px-6 py-3 bg-gray-50 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">点击数</th>
|
||||
<th className="px-6 py-3 bg-gray-50 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">百分比</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{pathData.map((item, index) => (
|
||||
<tr key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{item.path}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-right">{item.count}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right">
|
||||
<div className="flex items-center justify-end">
|
||||
<span className="text-sm text-gray-500 mr-2">{(item.percentage * 100).toFixed(1)}%</span>
|
||||
<div className="w-32 bg-gray-200 rounded-full h-2.5">
|
||||
<div className="bg-blue-600 h-2.5 rounded-full" style={{ width: `${item.percentage * 100}%` }}></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PathAnalytics;
|
||||
Reference in New Issue
Block a user