This commit is contained in:
2025-03-14 15:16:00 +08:00
parent b3d3c7cb3b
commit 2033adfc67

View File

@@ -118,6 +118,26 @@ interface KOLOverviewResponse {
error?: string;
}
// 添加概览卡片API响应接口
interface DashboardCardsResponse {
success: boolean;
data: {
comments_count: {
current: number;
change_percentage: number;
};
engagement_rate: {
current: number;
change_percentage: number;
};
sentiment_score: {
current: number;
change_percentage: number;
};
};
error?: string;
}
const Analytics: React.FC = () => {
const [timeRange, setTimeRange] = useState('30'); // 修改默认值为'30'与API匹配
const [selectedKOL, setSelectedKOL] = useState('all');
@@ -170,6 +190,19 @@ const Analytics: React.FC = () => {
const [trackingSuccess, setTrackingSuccess] = useState<string | null>(null);
const [trackingError, setTrackingError] = useState<string | null>(null);
// 添加概览卡片数据状态
const [dashboardCards, setDashboardCards] = useState<{
commentsCount: { current: number; changePercentage: number };
engagementRate: { current: number; changePercentage: number };
sentimentScore: { current: number; changePercentage: number };
}>({
commentsCount: { current: 0, changePercentage: 0 },
engagementRate: { current: 0, changePercentage: 0 },
sentimentScore: { current: 0, changePercentage: 0 }
});
const [cardsLoading, setCardsLoading] = useState(true);
const [cardsError, setCardsError] = useState<string | null>(null);
// 获取KOL概览数据
const fetchKolOverviewData = async () => {
setKolLoading(true);
@@ -245,6 +278,9 @@ const Analytics: React.FC = () => {
// 获取贴文表现数据
fetchPostPerformanceData();
// 获取概览卡片数据
fetchDashboardCards();
const fetchAnalyticsData = async () => {
try {
setLoading(true);
@@ -802,6 +838,98 @@ const Analytics: React.FC = () => {
</div>
);
// 获取概览卡片数据
const fetchDashboardCards = async () => {
try {
setCardsLoading(true);
setCardsError(null);
// 构建API URL
let url = `http://localhost:4000/api/analytics/dashboard-cards`;
// 添加时间范围参数
if (timeRange) {
url += `?timeRange=${timeRange}`;
}
// 添加项目过滤参数(如果选择了特定项目)
if (selectedProject !== 'all') {
url += `${timeRange ? '&' : '?'}projectId=${selectedProject}`;
}
// 添加平台过滤参数(如果选择了特定平台)
if (selectedPlatform !== 'all') {
url += `${(timeRange || selectedProject !== 'all') ? '&' : '?'}platform=${selectedPlatform}`;
}
console.log('请求概览卡片数据URL:', url);
// 添加认证头
const authToken = 'eyJhbGciOiJIUzI1NiIsImtpZCI6Inl3blNGYnRBOGtBUnl4UmUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3h0cWhsdXpvcm5hemxta29udWNyLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiI1YjQzMThiZi0yMWE4LTQ3YWMtOGJmYS0yYThmOGVmOWMwZmIiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzQxNjI3ODkyLCJpYXQiOjE3NDE2MjQyOTIsImVtYWlsIjoidml0YWxpdHltYWlsZ0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7ImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc0MTYyNDI5Mn1dLCJzZXNzaW9uX2lkIjoiODlmYjg0YzktZmEzYy00YmVlLTk0MDQtNjI1MjE0OGIyMzVlIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.VuUX2yhqN-FZseKL8fQG91i1cohfRqW2m1Z8CIWhZuk';
const response = await fetch(url, {
headers: {
'accept': 'application/json',
'Authorization': `Bearer ${authToken}`
}
});
if (response.ok) {
const result = await response.json() as DashboardCardsResponse;
console.log('成功获取概览卡片数据:', result);
if (result.success) {
// 更新状态
setDashboardCards({
commentsCount: {
current: result.data.comments_count.current,
changePercentage: result.data.comments_count.change_percentage
},
engagementRate: {
current: result.data.engagement_rate.current,
changePercentage: result.data.engagement_rate.change_percentage
},
sentimentScore: {
current: result.data.sentiment_score.current,
changePercentage: result.data.sentiment_score.change_percentage
}
});
// 更新情感分析数据 - 基于情感分数计算正面、中性、负面比例
const sentimentScore = result.data.sentiment_score.current;
// 假设情感分数范围是0-10是负面0.5是中性1是正面
// 根据情感分数生成模拟的正面、中性、负面百分比
const positive = Math.round(Math.min(1, Math.max(0, sentimentScore)) * 100);
const negative = Math.round(Math.min(1, Math.max(0, 1 - sentimentScore)) * 100);
const neutral = Math.max(0, 100 - positive - negative);
setSentimentData({
positive,
neutral,
negative
});
// 如果API返回了模拟数据标志
if ('is_mock_data' in result && result.is_mock_data) {
console.info('注意: 使用的是模拟概览卡片数据');
}
} else {
setCardsError(result.error || '获取概览卡片数据失败');
console.error('API调用失败:', result.error || '未知错误');
}
} else {
const errorText = await response.text();
setCardsError(`获取失败 (${response.status}): ${errorText}`);
console.error('获取概览卡片数据失败HTTP状态:', response.status, errorText);
}
} catch (error) {
setCardsError(`获取概览卡片数据时发生错误: ${error instanceof Error ? error.message : String(error)}`);
console.error('获取概览卡片数据时发生错误:', error);
} finally {
setCardsLoading(false);
}
};
return (
<div className="flex-1 overflow-auto">
<div className="p-6">
@@ -1250,11 +1378,30 @@ const Analytics: React.FC = () => {
<h3 className="text-lg font-medium text-gray-800"></h3>
<MessageSquare className="w-6 h-6 text-blue-600" />
</div>
<p className="mb-2 text-3xl font-bold text-gray-900">{platformData.reduce((sum, item) => sum + item.value, 0)}</p>
<div className="flex items-center text-sm">
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
<span className="text-green-500"> 12% </span>
{cardsLoading ? (
<div className="flex items-center justify-center h-20">
<div className="w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
</div>
) : cardsError ? (
<div className="text-sm text-red-500">{cardsError}</div>
) : (
<>
<p className="mb-2 text-3xl font-bold text-gray-900">{dashboardCards.commentsCount.current.toLocaleString()}</p>
<div className="flex items-center text-sm">
{dashboardCards.commentsCount.changePercentage > 0 ? (
<>
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
<span className="text-green-500"> {dashboardCards.commentsCount.changePercentage.toFixed(1)}% </span>
</>
) : (
<>
<TrendingDown className="w-4 h-4 mr-1 text-red-500" />
<span className="text-red-500"> {Math.abs(dashboardCards.commentsCount.changePercentage).toFixed(1)}% </span>
</>
)}
</div>
</>
)}
</div>
<div className="p-6 bg-white rounded-lg shadow">
@@ -1262,11 +1409,30 @@ const Analytics: React.FC = () => {
<h3 className="text-lg font-medium text-gray-800"></h3>
<Users className="w-6 h-6 text-blue-600" />
</div>
<p className="mb-2 text-3xl font-bold text-gray-900">4.8%</p>
<div className="flex items-center text-sm">
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
<span className="text-green-500"> 0.5% </span>
{cardsLoading ? (
<div className="flex items-center justify-center h-20">
<div className="w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
</div>
) : cardsError ? (
<div className="text-sm text-red-500">{cardsError}</div>
) : (
<>
<p className="mb-2 text-3xl font-bold text-gray-900">{(dashboardCards.engagementRate.current / 100).toFixed(1)}%</p>
<div className="flex items-center text-sm">
{dashboardCards.engagementRate.changePercentage > 0 ? (
<>
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
<span className="text-green-500"> {dashboardCards.engagementRate.changePercentage.toFixed(1)}% </span>
</>
) : (
<>
<TrendingDown className="w-4 h-4 mr-1 text-red-500" />
<span className="text-red-500"> {Math.abs(dashboardCards.engagementRate.changePercentage).toFixed(1)}% </span>
</>
)}
</div>
</>
)}
</div>
<div className="p-6 bg-white rounded-lg shadow">
@@ -1274,11 +1440,30 @@ const Analytics: React.FC = () => {
<h3 className="text-lg font-medium text-gray-800"></h3>
<PieChart className="w-6 h-6 text-blue-600" />
</div>
{cardsLoading ? (
<div className="flex items-center justify-center h-20">
<div className="w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
</div>
) : cardsError ? (
<div className="text-sm text-red-500">{cardsError}</div>
) : (
<>
<p className="mb-2 text-3xl font-bold text-gray-900">{sentimentData.positive}% </p>
<div className="flex items-center text-sm">
{dashboardCards.sentimentScore.changePercentage > 0 ? (
<>
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
<span className="text-green-500"> 5% </span>
<span className="text-green-500"> {dashboardCards.sentimentScore.changePercentage.toFixed(1)}% </span>
</>
) : (
<>
<TrendingDown className="w-4 h-4 mr-1 text-red-500" />
<span className="text-red-500"> {Math.abs(dashboardCards.sentimentScore.changePercentage).toFixed(1)}% </span>
</>
)}
</div>
</>
)}
</div>
</div>