dasboard
This commit is contained in:
@@ -118,6 +118,26 @@ interface KOLOverviewResponse {
|
|||||||
error?: string;
|
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 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');
|
||||||
@@ -170,6 +190,19 @@ const Analytics: React.FC = () => {
|
|||||||
const [trackingSuccess, setTrackingSuccess] = useState<string | null>(null);
|
const [trackingSuccess, setTrackingSuccess] = useState<string | null>(null);
|
||||||
const [trackingError, setTrackingError] = 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概览数据
|
// 获取KOL概览数据
|
||||||
const fetchKolOverviewData = async () => {
|
const fetchKolOverviewData = async () => {
|
||||||
setKolLoading(true);
|
setKolLoading(true);
|
||||||
@@ -245,6 +278,9 @@ const Analytics: React.FC = () => {
|
|||||||
// 获取贴文表现数据
|
// 获取贴文表现数据
|
||||||
fetchPostPerformanceData();
|
fetchPostPerformanceData();
|
||||||
|
|
||||||
|
// 获取概览卡片数据
|
||||||
|
fetchDashboardCards();
|
||||||
|
|
||||||
const fetchAnalyticsData = async () => {
|
const fetchAnalyticsData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -802,6 +838,98 @@ const Analytics: React.FC = () => {
|
|||||||
</div>
|
</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-1,0是负面,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 (
|
return (
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -1250,11 +1378,30 @@ const Analytics: React.FC = () => {
|
|||||||
<h3 className="text-lg font-medium text-gray-800">留言总数</h3>
|
<h3 className="text-lg font-medium text-gray-800">留言总数</h3>
|
||||||
<MessageSquare className="w-6 h-6 text-blue-600" />
|
<MessageSquare className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-2 text-3xl font-bold text-gray-900">{platformData.reduce((sum, item) => sum + item.value, 0)}</p>
|
{cardsLoading ? (
|
||||||
<div className="flex items-center text-sm">
|
<div className="flex items-center justify-center h-20">
|
||||||
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
|
<div className="w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
|
||||||
<span className="text-green-500">↑ 12% 较上周</span>
|
</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>
|
||||||
|
|
||||||
<div className="p-6 bg-white rounded-lg shadow">
|
<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>
|
<h3 className="text-lg font-medium text-gray-800">平均互动率</h3>
|
||||||
<Users className="w-6 h-6 text-blue-600" />
|
<Users className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-2 text-3xl font-bold text-gray-900">4.8%</p>
|
{cardsLoading ? (
|
||||||
<div className="flex items-center text-sm">
|
<div className="flex items-center justify-center h-20">
|
||||||
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
|
<div className="w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
|
||||||
<span className="text-green-500">↑ 0.5% 较上周</span>
|
</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>
|
||||||
|
|
||||||
<div className="p-6 bg-white rounded-lg shadow">
|
<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>
|
<h3 className="text-lg font-medium text-gray-800">情感分析</h3>
|
||||||
<PieChart className="w-6 h-6 text-blue-600" />
|
<PieChart className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-2 text-3xl font-bold text-gray-900">{sentimentData.positive}% 正面</p>
|
{cardsLoading ? (
|
||||||
<div className="flex items-center text-sm">
|
<div className="flex items-center justify-center h-20">
|
||||||
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
|
<div className="w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
|
||||||
<span className="text-green-500">↑ 5% 较上周</span>
|
</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">↑ {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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user