trend
This commit is contained in:
@@ -138,6 +138,20 @@ interface DashboardCardsResponse {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加留言趋势API响应接口
|
||||||
|
interface CommentTrendResponse {
|
||||||
|
success: boolean;
|
||||||
|
data: {
|
||||||
|
date: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
metadata: {
|
||||||
|
max_count: number;
|
||||||
|
total_count: number;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const Analytics: React.FC = () => {
|
const Analytics: React.FC = () => {
|
||||||
const [timeRange, setTimeRange] = useState('30'); // 修改默认值为'30'与API匹配
|
const [timeRange, setTimeRange] = useState('30'); // 修改默认值为'30'与API匹配
|
||||||
const [selectedKOL, setSelectedKOL] = useState('all');
|
const [selectedKOL, setSelectedKOL] = useState('all');
|
||||||
@@ -159,6 +173,9 @@ const Analytics: React.FC = () => {
|
|||||||
const [kolError, setKolError] = useState<string | null>(null); // 新增KOL数据错误状态
|
const [kolError, setKolError] = useState<string | null>(null); // 新增KOL数据错误状态
|
||||||
const [filteredEngagementData, setFilteredEngagementData] = useState<EngagementData[]>([]);
|
const [filteredEngagementData, setFilteredEngagementData] = useState<EngagementData[]>([]);
|
||||||
const [postDataLoading, setPostDataLoading] = useState(true); // 添加贴文数据加载状态
|
const [postDataLoading, setPostDataLoading] = useState(true); // 添加贴文数据加载状态
|
||||||
|
const [trendLoading, setTrendLoading] = useState(true);
|
||||||
|
const [trendError, setTrendError] = useState<string | null>(null);
|
||||||
|
const [maxTimelineCount, setMaxTimelineCount] = useState(1); // 设置默认值为1避免除以零
|
||||||
|
|
||||||
// 添加项目相关状态
|
// 添加项目相关状态
|
||||||
const [projects, setProjects] = useState<Project[]>([
|
const [projects, setProjects] = useState<Project[]>([
|
||||||
@@ -281,6 +298,9 @@ const Analytics: React.FC = () => {
|
|||||||
// 获取概览卡片数据
|
// 获取概览卡片数据
|
||||||
fetchDashboardCards();
|
fetchDashboardCards();
|
||||||
|
|
||||||
|
// 获取留言趋势数据
|
||||||
|
fetchCommentTrend();
|
||||||
|
|
||||||
const fetchAnalyticsData = async () => {
|
const fetchAnalyticsData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -294,17 +314,6 @@ const Analytics: React.FC = () => {
|
|||||||
{ name: 'YouTube', value: 5, color: '#FF0000' }
|
{ name: 'YouTube', value: 5, color: '#FF0000' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Set mock timeline data
|
|
||||||
setTimelineData([
|
|
||||||
{ date: '2023-01-01', comments: 10 },
|
|
||||||
{ date: '2023-01-02', comments: 15 },
|
|
||||||
{ date: '2023-01-03', comments: 8 },
|
|
||||||
{ date: '2023-01-04', comments: 12 },
|
|
||||||
{ date: '2023-01-05', comments: 20 },
|
|
||||||
{ date: '2023-01-06', comments: 18 },
|
|
||||||
{ date: '2023-01-07', comments: 25 }
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Set mock sentiment data
|
// Set mock sentiment data
|
||||||
setSentimentData({ positive: 65, neutral: 20, negative: 15 });
|
setSentimentData({ positive: 65, neutral: 20, negative: 15 });
|
||||||
|
|
||||||
@@ -652,8 +661,6 @@ const Analytics: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const maxTimelineCount = Math.max(...timelineData.map(item => item.comments));
|
|
||||||
|
|
||||||
// Add new function to handle influencer tracking form submission
|
// Add new function to handle influencer tracking form submission
|
||||||
const handleTrackInfluencer = async (e: React.FormEvent) => {
|
const handleTrackInfluencer = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -930,6 +937,68 @@ const Analytics: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取留言趋势数据
|
||||||
|
const fetchCommentTrend = async () => {
|
||||||
|
try {
|
||||||
|
setTrendLoading(true);
|
||||||
|
|
||||||
|
// 构建留言趋势API URL
|
||||||
|
const url = `http://localhost:4000/api/analytics/comment-trend?timeRange=${timeRange}`;
|
||||||
|
|
||||||
|
// 添加项目过滤参数(如果选择了特定项目)
|
||||||
|
const urlWithFilters = selectedProject !== 'all'
|
||||||
|
? `${url}&projectId=${selectedProject}`
|
||||||
|
: url;
|
||||||
|
|
||||||
|
// 添加平台过滤参数(如果选择了特定平台)
|
||||||
|
const finalUrl = selectedPlatform !== 'all'
|
||||||
|
? `${urlWithFilters}&platform=${selectedPlatform}`
|
||||||
|
: urlWithFilters;
|
||||||
|
|
||||||
|
console.log('请求留言趋势数据URL:', finalUrl);
|
||||||
|
|
||||||
|
const response = await fetch(finalUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json() as CommentTrendResponse;
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// 将API返回的数据映射到TimelineData结构
|
||||||
|
const mappedData = result.data.map((item: { date: string; count: number }) => ({
|
||||||
|
date: item.date,
|
||||||
|
comments: item.count
|
||||||
|
}));
|
||||||
|
|
||||||
|
setTimelineData(mappedData);
|
||||||
|
setMaxTimelineCount(result.metadata.max_count || 1); // 避免除以零
|
||||||
|
|
||||||
|
// 如果API返回了模拟数据标志
|
||||||
|
if ('is_mock_data' in result && result.is_mock_data) {
|
||||||
|
console.info('注意: 使用的是模拟留言趋势数据');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTrendError(result.error || '获取留言趋势数据失败');
|
||||||
|
console.error('API调用失败:', result.error || '未知错误');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorText = await response.text();
|
||||||
|
setTrendError(`获取失败 (${response.status}): ${errorText}`);
|
||||||
|
console.error('获取留言趋势数据失败,HTTP状态:', response.status, errorText);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setTrendError(`获取留言趋势数据时发生错误: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
console.error('获取留言趋势数据时发生错误:', error);
|
||||||
|
} finally {
|
||||||
|
setTrendLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -1470,26 +1539,45 @@ const Analytics: React.FC = () => {
|
|||||||
{/* 留言趋势图 */}
|
{/* 留言趋势图 */}
|
||||||
<div className="p-6 mb-8 bg-white rounded-lg shadow">
|
<div className="p-6 mb-8 bg-white rounded-lg shadow">
|
||||||
<h3 className="mb-4 text-lg font-medium text-gray-800">留言趋势</h3>
|
<h3 className="mb-4 text-lg font-medium text-gray-800">留言趋势</h3>
|
||||||
<div className="h-64">
|
{trendLoading ? (
|
||||||
<div className="flex items-end space-x-2 h-52">
|
<div className="flex flex-col items-center justify-center h-64">
|
||||||
{timelineData.map((item, index) => (
|
<div className="w-12 h-12 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
|
||||||
<div key={index} className="flex flex-col items-center justify-end flex-1">
|
<p className="mt-4 text-gray-600">加载留言趋势数据中...</p>
|
||||||
<div
|
|
||||||
className="w-full transition-all duration-500 ease-in-out bg-blue-500 rounded-t-md hover:bg-blue-600"
|
|
||||||
style={{
|
|
||||||
height: `${(item.comments / maxTimelineCount) * 100}%`,
|
|
||||||
minHeight: '10%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="invisible py-1 text-xs text-center text-white group-hover:visible">
|
|
||||||
{item.comments}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="mt-2 text-xs text-center">{item.date}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : trendError ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-64 p-4 rounded-lg bg-red-50">
|
||||||
|
<AlertTriangle className="w-10 h-10 mb-2 text-red-500" />
|
||||||
|
<p className="text-center text-red-600">{trendError}</p>
|
||||||
|
</div>
|
||||||
|
) : timelineData.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-64 p-4 rounded-lg bg-gray-50">
|
||||||
|
<MessageSquare className="w-10 h-10 mb-2 text-gray-400" />
|
||||||
|
<p className="text-center text-gray-600">没有找到留言趋势数据</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="h-64">
|
||||||
|
<div className="flex items-end space-x-0.5 h-52 overflow-x-auto">
|
||||||
|
{timelineData.map((item, index) => (
|
||||||
|
<div key={index} className="flex flex-col items-center justify-end min-w-[15px] group">
|
||||||
|
<div
|
||||||
|
className="w-full transition-all duration-300 ease-in-out bg-blue-500 rounded-t-md hover:bg-blue-600 group-hover:relative"
|
||||||
|
style={{
|
||||||
|
height: `${(item.comments / maxTimelineCount) * 100}%`,
|
||||||
|
minHeight: '4px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute invisible px-2 py-1 mb-1 text-xs text-white transform -translate-x-1/2 bg-gray-800 rounded bottom-full left-1/2 group-hover:visible whitespace-nowrap">
|
||||||
|
{item.comments} 留言
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-xs text-center truncate" style={{ writingMode: 'vertical-rl', textOrientation: 'mixed', transform: 'rotate(180deg)', maxHeight: '80px' }}>
|
||||||
|
{new Date(item.date).toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-6 mb-8 lg:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 mb-8 lg:grid-cols-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user