kol overview frontend
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
PieChart,
|
||||
MessageSquare,
|
||||
Facebook,
|
||||
@@ -21,7 +22,8 @@ import {
|
||||
Download,
|
||||
AlertTriangle,
|
||||
Save,
|
||||
Eye
|
||||
Eye,
|
||||
Twitter
|
||||
} from 'lucide-react';
|
||||
|
||||
// Define interfaces for analytics data
|
||||
@@ -64,23 +66,20 @@ interface EngagementData {
|
||||
shares: number;
|
||||
}
|
||||
|
||||
// 更新KOL数据接口,与API接口结构匹配
|
||||
interface KOLData {
|
||||
id: string;
|
||||
influencer_id: string;
|
||||
name: string;
|
||||
platform: string;
|
||||
followers: number;
|
||||
engagement: number;
|
||||
posts: number;
|
||||
sentiment: SentimentData;
|
||||
avatar?: string;
|
||||
platforms?: string[];
|
||||
postCount?: number;
|
||||
likeCount?: number;
|
||||
commentCount?: number;
|
||||
engagementRate?: number;
|
||||
engagementTrend?: number;
|
||||
sentimentScore?: number;
|
||||
officialInteractions?: number;
|
||||
profile_url: string;
|
||||
followers_count: number;
|
||||
followers_change: number;
|
||||
followers_change_percentage: number | null | string;
|
||||
likes_change: number;
|
||||
likes_change_percentage: number | null | string;
|
||||
follows_change: number;
|
||||
follows_change_percentage: number | null | string;
|
||||
avatar?: string; // 兼容旧代码
|
||||
}
|
||||
|
||||
interface FunnelData {
|
||||
@@ -105,8 +104,20 @@ interface Project {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 添加API响应接口
|
||||
interface KOLOverviewResponse {
|
||||
success: boolean;
|
||||
data: KOLData[];
|
||||
pagination: {
|
||||
limit: number;
|
||||
offset: number;
|
||||
total: number;
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const Analytics: React.FC = () => {
|
||||
const [timeRange, setTimeRange] = useState('7days');
|
||||
const [timeRange, setTimeRange] = useState('30'); // 修改默认值为'30'与API匹配
|
||||
const [selectedKOL, setSelectedKOL] = useState('all');
|
||||
const [selectedPlatform, setSelectedPlatform] = useState('all');
|
||||
const [platformData, setPlatformData] = useState<AnalyticsData[]>([]);
|
||||
@@ -121,7 +132,9 @@ const Analytics: React.FC = () => {
|
||||
const [kolData, setKolData] = useState<KOLData[]>([]);
|
||||
const [funnelData, setFunnelData] = useState<FunnelData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [kolLoading, setKolLoading] = useState(true); // 新增KOL数据加载状态
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [kolError, setKolError] = useState<string | null>(null); // 新增KOL数据错误状态
|
||||
const [filteredEngagementData, setFilteredEngagementData] = useState<EngagementData[]>([]);
|
||||
|
||||
// 添加项目相关状态
|
||||
@@ -131,7 +144,16 @@ const Analytics: React.FC = () => {
|
||||
{ id: '3', name: '项目 3', description: '示例项目 3' },
|
||||
{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'UUID格式项目', description: 'UUID格式的项目ID示例' }
|
||||
]);
|
||||
const [selectedProject, setSelectedProject] = useState<string>('1');
|
||||
const [selectedProject, setSelectedProject] = useState<string>('all');
|
||||
|
||||
// 添加分页状态
|
||||
const [kolPage, setKolPage] = useState(0);
|
||||
const [kolPageSize, setKolPageSize] = useState(20);
|
||||
const [kolTotal, setKolTotal] = useState(0);
|
||||
|
||||
// 添加排序状态
|
||||
const [kolSortBy, setKolSortBy] = useState('followers_change');
|
||||
const [kolSortOrder, setKolSortOrder] = useState('desc');
|
||||
|
||||
// Add new state for influencer tracking
|
||||
const [showTrackingForm, setShowTrackingForm] = useState(false);
|
||||
@@ -145,7 +167,78 @@ const Analytics: React.FC = () => {
|
||||
const [trackingSuccess, setTrackingSuccess] = useState<string | null>(null);
|
||||
const [trackingError, setTrackingError] = useState<string | null>(null);
|
||||
|
||||
// 获取KOL概览数据
|
||||
const fetchKolOverviewData = async () => {
|
||||
setKolLoading(true);
|
||||
setKolError(null);
|
||||
|
||||
try {
|
||||
// 构建API URL,包含所有查询参数
|
||||
let url = `http://localhost:4000/api/analytics/kol-overview?timeRange=${timeRange}&sortBy=${kolSortBy}&sortOrder=${kolSortOrder}&limit=${kolPageSize}&offset=${kolPage * kolPageSize}`;
|
||||
|
||||
if (selectedProject !== 'all') {
|
||||
url += `&projectId=${selectedProject}`;
|
||||
}
|
||||
|
||||
// 获取认证令牌 (实际项目中应从身份验证上下文中获取)
|
||||
const authToken = 'eyJhbGciOiJIUzI1NiIsImtpZCI6Inl3blNGYnRBOGtBUnl4UmUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3h0cWhsdXpvcm5hemxta29udWNyLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiI1YjQzMThiZi0yMWE4LTQ3YWMtOGJmYS0yYThmOGVmOWMwZmIiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzQxNjI3ODkyLCJpYXQiOjE3NDE2MjQyOTIsImVtYWlsIjoidml0YWxpdHltYWlsZ0BnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7ImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTc0MTYyNDI5Mn1dLCJzZXNzaW9uX2lkIjoiODlmYjg0YzktZmEzYy00YmVlLTk0MDQtNjI1MjE0OGIyMzVlIiwiaXNfYW5vbnltb3VzIjpmYWxzZX0.VuUX2yhqN-FZseKL8fQG91i1cohfRqW2m1Z8CIWhZuk';
|
||||
|
||||
// 发送请求
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'accept': 'application/json',
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
|
||||
// 处理响应
|
||||
if (response.ok) {
|
||||
const result: KOLOverviewResponse = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
console.log('成功获取KOL概览数据:', result);
|
||||
|
||||
// 更新状态
|
||||
setKolData(result.data);
|
||||
setKolTotal(result.pagination.total);
|
||||
|
||||
// 处理模拟数据标志
|
||||
if ('is_mock_data' in result && result.is_mock_data) {
|
||||
console.info('注意: 使用的是模拟数据');
|
||||
}
|
||||
} else {
|
||||
// API返回了成功状态码但标记为不成功
|
||||
console.error('API返回错误:', result.error);
|
||||
setKolError(result.error || '无法获取KOL数据');
|
||||
|
||||
// 保留现有数据或清空
|
||||
// setKolData([]);
|
||||
}
|
||||
} else {
|
||||
// HTTP错误
|
||||
console.error('获取KOL数据失败,HTTP状态:', response.status);
|
||||
const errorText = await response.text();
|
||||
setKolError(`获取失败 (${response.status}): ${errorText}`);
|
||||
|
||||
// 保留现有数据或清空
|
||||
// setKolData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
// 网络或其他错误
|
||||
console.error('获取KOL数据时发生错误:', error);
|
||||
setKolError(`获取KOL数据时发生错误: ${error instanceof Error ? error.message : String(error)}`);
|
||||
|
||||
// 保留现有数据或清空
|
||||
// setKolData([]);
|
||||
} finally {
|
||||
setKolLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 获取KOL概览数据
|
||||
fetchKolOverviewData();
|
||||
|
||||
const fetchAnalyticsData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -175,9 +268,9 @@ const Analytics: React.FC = () => {
|
||||
|
||||
// Set mock status data
|
||||
setStatusData([
|
||||
{ name: 'Approved', value: 45, color: '#10B981' },
|
||||
{ name: 'Pending', value: 30, color: '#F59E0B' },
|
||||
{ name: 'Rejected', value: 25, color: '#EF4444' }
|
||||
{ name: 'approved', value: 45, color: '#10B981' },
|
||||
{ name: 'pending', value: 30, color: '#F59E0B' },
|
||||
{ name: 'rejected', value: 25, color: '#EF4444' }
|
||||
]);
|
||||
|
||||
// Set mock popular articles
|
||||
@@ -189,64 +282,6 @@ const Analytics: React.FC = () => {
|
||||
{ id: '5', title: 'Content Creation Tips', views: 620, engagement: 57, platform: 'YouTube' }
|
||||
]);
|
||||
|
||||
// Set mock KOL data
|
||||
setKolData([
|
||||
{
|
||||
id: '1',
|
||||
name: 'John Smith',
|
||||
platform: 'Instagram',
|
||||
followers: 120000,
|
||||
engagement: 3.5,
|
||||
posts: 450,
|
||||
sentiment: { positive: 75, neutral: 15, negative: 10 },
|
||||
avatar: 'https://randomuser.me/api/portraits/men/1.jpg',
|
||||
platforms: ['Instagram', 'TikTok'],
|
||||
postCount: 120,
|
||||
likeCount: 45000,
|
||||
commentCount: 2800,
|
||||
engagementRate: 3.8,
|
||||
engagementTrend: 0.5,
|
||||
sentimentScore: 4.2,
|
||||
officialInteractions: 12
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Sarah Johnson',
|
||||
platform: 'YouTube',
|
||||
followers: 250000,
|
||||
engagement: 4.2,
|
||||
posts: 320,
|
||||
sentiment: { positive: 82, neutral: 10, negative: 8 },
|
||||
avatar: 'https://randomuser.me/api/portraits/women/2.jpg',
|
||||
platforms: ['YouTube', 'Instagram'],
|
||||
postCount: 85,
|
||||
likeCount: 120000,
|
||||
commentCount: 8500,
|
||||
engagementRate: 4.5,
|
||||
engagementTrend: 1.2,
|
||||
sentimentScore: 4.5,
|
||||
officialInteractions: 8
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'David Lee',
|
||||
platform: 'Twitter',
|
||||
followers: 180000,
|
||||
engagement: 2.8,
|
||||
posts: 1200,
|
||||
sentiment: { positive: 68, neutral: 22, negative: 10 },
|
||||
avatar: 'https://randomuser.me/api/portraits/men/3.jpg',
|
||||
platforms: ['Twitter', 'LinkedIn'],
|
||||
postCount: 350,
|
||||
likeCount: 28000,
|
||||
commentCount: 4200,
|
||||
engagementRate: 2.5,
|
||||
engagementTrend: -0.3,
|
||||
sentimentScore: 3.8,
|
||||
officialInteractions: 5
|
||||
}
|
||||
]);
|
||||
|
||||
// 尝试从API获取漏斗数据
|
||||
try {
|
||||
// 使用选中的项目ID
|
||||
@@ -318,15 +353,20 @@ const Analytics: React.FC = () => {
|
||||
};
|
||||
|
||||
fetchAnalyticsData();
|
||||
}, [timeRange, selectedProject]); // 添加selectedProject作为依赖项
|
||||
}, [timeRange, selectedProject, kolSortBy, kolSortOrder, kolPage, kolPageSize]); // 更新依赖项
|
||||
|
||||
// 当排序或筛选条件变化时,重置页码
|
||||
useEffect(() => {
|
||||
setKolPage(0);
|
||||
}, [timeRange, selectedProject, kolSortBy, kolSortOrder]);
|
||||
|
||||
// Filter KOLs based on selected platform
|
||||
const filteredKOLs = selectedPlatform === 'all'
|
||||
? kolData
|
||||
: kolData.filter(kol => kol.platform === selectedPlatform);
|
||||
: kolData.filter(kol => kol.platform.toLowerCase() === selectedPlatform.toLowerCase());
|
||||
|
||||
// Sort KOLs by followers count
|
||||
const sortedKOLs = [...filteredKOLs].sort((a, b) => b.followers - a.followers);
|
||||
// 不需要额外排序,API已经排序
|
||||
const sortedKOLs = filteredKOLs;
|
||||
|
||||
// Update filtered engagement data when KOL selection changes
|
||||
useEffect(() => {
|
||||
@@ -338,11 +378,14 @@ const Analytics: React.FC = () => {
|
||||
}, [selectedKOL]);
|
||||
|
||||
const getPlatformIcon = (platform: string) => {
|
||||
switch (platform) {
|
||||
const platformLower = platform.toLowerCase();
|
||||
switch (platformLower) {
|
||||
case 'facebook':
|
||||
return <Facebook className="w-5 h-5 text-blue-600" />;
|
||||
case 'threads':
|
||||
return <Hash className="w-5 h-5 text-black" />;
|
||||
case 'twitter':
|
||||
return <Twitter className="w-5 h-5 text-blue-400" />;
|
||||
case 'instagram':
|
||||
return <Instagram className="w-5 h-5 text-pink-500" />;
|
||||
case 'linkedin':
|
||||
@@ -355,6 +398,43 @@ const Analytics: React.FC = () => {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 帮助函数:获取表示变化趋势的图标和样式
|
||||
const getTrendingIcon = (change: number | null | string) => {
|
||||
// 如果是字符串(可能是"待计算")或者null,返回中性图标
|
||||
if (typeof change !== 'number') {
|
||||
return {
|
||||
icon: <div className="w-4 h-4" />,
|
||||
colorClass: 'text-gray-500'
|
||||
};
|
||||
}
|
||||
|
||||
if (change > 0) {
|
||||
return {
|
||||
icon: <TrendingUp className="w-4 h-4" />,
|
||||
colorClass: 'text-green-500'
|
||||
};
|
||||
} else if (change < 0) {
|
||||
return {
|
||||
icon: <TrendingDown className="w-4 h-4" />,
|
||||
colorClass: 'text-red-500'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
icon: <div className="w-4 h-4" />,
|
||||
colorClass: 'text-gray-500'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 帮助函数:格式化百分比变化
|
||||
const formatPercentageChange = (change: number | null | string) => {
|
||||
if (change === null || typeof change === 'string') {
|
||||
return '0%';
|
||||
}
|
||||
|
||||
return `${change > 0 ? '+' : ''}${change.toFixed(1)}%`;
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
@@ -602,6 +682,7 @@ const Analytics: React.FC = () => {
|
||||
onChange={(e) => setSelectedProject(e.target.value)}
|
||||
className="block w-full py-2 pl-3 pr-10 text-base border-gray-300 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
>
|
||||
<option value="all">全部项目</option>
|
||||
{projects.map((project) => (
|
||||
<option key={project.id} value={project.id}>
|
||||
{project.name}
|
||||
@@ -619,8 +700,6 @@ const Analytics: React.FC = () => {
|
||||
<div className="p-6">
|
||||
<h1 className="mb-6 text-2xl font-bold">Analytics Dashboard</h1>
|
||||
|
||||
{/* 删除这里的项目选择器 */}
|
||||
|
||||
{loading ? (
|
||||
<div className="flex flex-col items-center justify-center h-80">
|
||||
<div className="w-16 h-16 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
|
||||
@@ -651,10 +730,9 @@ const Analytics: React.FC = () => {
|
||||
value={timeRange}
|
||||
onChange={(e) => setTimeRange(e.target.value)}
|
||||
>
|
||||
<option value="7days">過去 7 天</option>
|
||||
<option value="30days">過去 30 天</option>
|
||||
<option value="90days">過去 90 天</option>
|
||||
<option value="1year">過去 1 年</option>
|
||||
<option value="7">過去 7 天</option>
|
||||
<option value="30">過去 30 天</option>
|
||||
<option value="90">過去 90 天</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
@@ -664,7 +742,7 @@ const Analytics: React.FC = () => {
|
||||
>
|
||||
<option value="all">全部 KOL</option>
|
||||
{kolData.map(kol => (
|
||||
<option key={kol.id} value={kol.id}>{kol.name}</option>
|
||||
<option key={kol.influencer_id} value={kol.influencer_id}>{kol.name}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -676,7 +754,7 @@ const Analytics: React.FC = () => {
|
||||
<option value="all">全部平台</option>
|
||||
<option value="facebook">Facebook</option>
|
||||
<option value="instagram">Instagram</option>
|
||||
<option value="threads">Threads</option>
|
||||
<option value="twitter">Twitter</option>
|
||||
<option value="youtube">YouTube</option>
|
||||
<option value="xiaohongshu">小紅書</option>
|
||||
</select>
|
||||
@@ -689,30 +767,198 @@ const Analytics: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KOL 表現概覽 */}
|
||||
{/* KOL 表現概覽 - 使用API数据 */}
|
||||
<div className="p-6 mb-8 bg-white rounded-lg shadow">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-800">KOL 表現概覽</h3>
|
||||
<div className="flex items-center text-sm text-blue-600">
|
||||
<span className="mr-1">查看詳細報告</span>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* 排序控制 */}
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-sm text-gray-600">排序:</span>
|
||||
<select
|
||||
className="px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={kolSortBy}
|
||||
onChange={(e) => setKolSortBy(e.target.value)}
|
||||
>
|
||||
<option value="followers_change">粉丝增长</option>
|
||||
<option value="likes_change">点赞数</option>
|
||||
<option value="follows_change">新增关注</option>
|
||||
<option value="followers_count">粉丝总数</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
className="flex items-center justify-center w-8 h-8 ml-2 text-gray-500 bg-gray-100 rounded-md hover:bg-gray-200"
|
||||
onClick={() => setKolSortOrder(prev => prev === 'asc' ? 'desc' : 'asc')}
|
||||
>
|
||||
{kolSortOrder === 'asc' ? '↑' : '↓'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-sm text-blue-600 cursor-pointer">
|
||||
<span className="mr-1">查看详细报告</span>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden border border-gray-200 rounded-lg shadow-sm">
|
||||
<div className="p-6 text-center">
|
||||
<div className="mb-4 text-gray-500">
|
||||
<MessageSquare className="inline-block w-12 h-12" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium text-gray-900">内容分析功能暂时不可用</h3>
|
||||
<p className="text-gray-500">我们正在努力改进这一功能,请稍后再试。</p>
|
||||
{kolLoading ? (
|
||||
<div className="flex flex-col items-center justify-center h-64">
|
||||
<div className="w-12 h-12 border-4 border-blue-500 rounded-full border-t-transparent animate-spin"></div>
|
||||
<p className="mt-4 text-gray-600">加载KOL数据中...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : kolError ? (
|
||||
<div className="p-6 text-center border border-red-200 rounded-lg bg-red-50">
|
||||
<div className="mb-4 text-red-500">
|
||||
<AlertTriangle className="inline-block w-12 h-12" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium text-red-700">无法加载KOL数据</h3>
|
||||
<p className="text-red-600">{kolError}</p>
|
||||
<button
|
||||
onClick={fetchKolOverviewData}
|
||||
className="px-4 py-2 mt-4 text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
) : sortedKOLs.length === 0 ? (
|
||||
<div className="p-6 text-center border border-gray-200 rounded-lg">
|
||||
<div className="mb-4 text-gray-400">
|
||||
<Users className="inline-block w-12 h-12" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium text-gray-700">没有找到KOL数据</h3>
|
||||
<p className="text-gray-500">请尝试更改筛选条件或检查所选项目是否有KOL数据。</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* KOL卡片网格 */}
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{sortedKOLs.map((kol) => (
|
||||
<div key={kol.influencer_id} className="overflow-hidden transition-shadow duration-300 bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md">
|
||||
<div className="p-4">
|
||||
{/* KOL头部信息 */}
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="w-12 h-12 mr-3 overflow-hidden bg-gray-200 rounded-full">
|
||||
{/* 如果有头像则使用,否则显示占位符 */}
|
||||
{kol.avatar ? (
|
||||
<img src={kol.avatar} alt={kol.name} className="object-cover w-full h-full" />
|
||||
) : (
|
||||
<div className="flex items-center justify-center w-full h-full text-xl font-semibold text-gray-500">
|
||||
{kol.name.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900">{kol.name}</h4>
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
{getPlatformIcon(kol.platform)}
|
||||
<span className="ml-1">{kol.platform}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KOL指标 */}
|
||||
<div className="space-y-3">
|
||||
{/* 粉丝增长 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">粉丝增长:</span>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-base font-semibold">{kol.followers_change.toLocaleString()}</span>
|
||||
<div className={getTrendingIcon(kol.followers_change_percentage).colorClass}>
|
||||
{getTrendingIcon(kol.followers_change_percentage).icon}
|
||||
<span className="ml-1 text-xs">
|
||||
{formatPercentageChange(kol.followers_change_percentage)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 新增点赞 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">新增点赞:</span>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-base font-semibold">{kol.likes_change.toLocaleString()}</span>
|
||||
<div className={getTrendingIcon(kol.likes_change_percentage).colorClass}>
|
||||
{getTrendingIcon(kol.likes_change_percentage).icon}
|
||||
<span className="ml-1 text-xs">
|
||||
{formatPercentageChange(kol.likes_change_percentage)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 新增关注 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-600">新增关注:</span>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-base font-semibold">{kol.follows_change.toLocaleString()}</span>
|
||||
<div className={getTrendingIcon(kol.follows_change_percentage).colorClass}>
|
||||
{getTrendingIcon(kol.follows_change_percentage).icon}
|
||||
<span className="ml-1 text-xs">
|
||||
{formatPercentageChange(kol.follows_change_percentage)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 粉丝总数 */}
|
||||
<div className="flex items-center justify-between pt-2 mt-2 border-t border-gray-100">
|
||||
<span className="text-sm text-gray-600">粉丝总数:</span>
|
||||
<span className="text-base font-semibold">{kol.followers_count.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 链接到详情 */}
|
||||
<div className="py-2 text-center text-blue-600 bg-blue-50 hover:bg-blue-100">
|
||||
<a href={kol.profile_url} target="_blank" rel="noopener noreferrer" className="text-sm">
|
||||
查看详情
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 分页控制 */}
|
||||
{kolTotal > kolPageSize && (
|
||||
<div className="flex items-center justify-between mt-6">
|
||||
<div className="text-sm text-gray-500">
|
||||
显示 {kolPage * kolPageSize + 1} - {Math.min((kolPage + 1) * kolPageSize, kolTotal)} 条,共 {kolTotal} 条
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
onClick={() => setKolPage(prev => Math.max(0, prev - 1))}
|
||||
disabled={kolPage === 0}
|
||||
className={`px-3 py-1 text-sm border rounded ${
|
||||
kolPage === 0
|
||||
? 'text-gray-400 border-gray-200 cursor-not-allowed'
|
||||
: 'text-blue-600 border-blue-200 hover:bg-blue-50'
|
||||
}`}
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setKolPage(prev => prev + 1)}
|
||||
disabled={(kolPage + 1) * kolPageSize >= kolTotal}
|
||||
className={`px-3 py-1 text-sm border rounded ${
|
||||
(kolPage + 1) * kolPageSize >= kolTotal
|
||||
? 'text-gray-400 border-gray-200 cursor-not-allowed'
|
||||
: 'text-blue-600 border-blue-200 hover:bg-blue-50'
|
||||
}`}
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 轉換漏斗 */}
|
||||
{/* 转换漏斗 */}
|
||||
<div className="p-6 mb-8 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-6 text-lg font-medium text-gray-800">KOL 合作轉換漏斗</h3>
|
||||
<h3 className="mb-6 text-lg font-medium text-gray-800">KOL 合作转换漏斗</h3>
|
||||
<div className="flex justify-center">
|
||||
<div className="w-full max-w-3xl">
|
||||
{funnelData.map((stage, index) => (
|
||||
@@ -730,7 +976,7 @@ const Analytics: React.FC = () => {
|
||||
<div className="flex justify-center my-1">
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<ArrowRight className="w-4 h-4 mr-1" />
|
||||
轉換率: {((funnelData[index + 1].count / stage.count) * 100).toFixed(1)}%
|
||||
转换率: {((funnelData[index + 1].count / stage.count) * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -740,34 +986,34 @@ const Analytics: React.FC = () => {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 mt-6 md:grid-cols-3">
|
||||
<div className="p-4 rounded-lg bg-gray-50">
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">平均轉換率</h4>
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">平均转换率</h4>
|
||||
<p className="text-2xl font-bold text-blue-600">
|
||||
{((funnelData[funnelData.length - 1].count / funnelData[0].count) * 100).toFixed(1)}%
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-500">從曝光到轉換的整體效率</p>
|
||||
<p className="mt-1 text-xs text-gray-500">从曝光到转换的整体效率</p>
|
||||
</div>
|
||||
<div className="p-4 rounded-lg bg-gray-50">
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">最高轉換階段</h4>
|
||||
<p className="text-2xl font-bold text-green-600">互動 → 點擊</p>
|
||||
<p className="mt-1 text-xs text-gray-500">此階段轉換率高於平均值 15%</p>
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">最高转换阶段</h4>
|
||||
<p className="text-2xl font-bold text-green-600">互动 → 点击</p>
|
||||
<p className="mt-1 text-xs text-gray-500">此阶段转换率高於平均值 15%</p>
|
||||
</div>
|
||||
<div className="p-4 rounded-lg bg-gray-50">
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">最低轉換階段</h4>
|
||||
<p className="text-2xl font-bold text-red-600">點擊 → 購買</p>
|
||||
<p className="mt-1 text-xs text-gray-500">此階段需要優化,低於平均值 23%</p>
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">最低转换阶段</h4>
|
||||
<p className="text-2xl font-bold text-red-600">点击 → 购买</p>
|
||||
<p className="mt-1 text-xs text-gray-500">此阶段需要优化,低於平均值 23%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KOL 貼文表現 */}
|
||||
{/* KOL 贴文表现 */}
|
||||
<div className="p-6 mb-8 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-6 text-lg font-medium text-gray-800">KOL 貼文表現</h3>
|
||||
<h3 className="mb-6 text-lg font-medium text-gray-800">KOL 贴文表现</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
貼文
|
||||
贴文
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
KOL
|
||||
@@ -776,22 +1022,22 @@ const Analytics: React.FC = () => {
|
||||
平台
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
發布日期
|
||||
发布日期
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
觀看數
|
||||
观看数
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
讚數
|
||||
赞数
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
留言數
|
||||
留言数
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
分享數
|
||||
分享数
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase">
|
||||
情緒指標
|
||||
情绪指标
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -810,13 +1056,13 @@ const Analytics: React.FC = () => {
|
||||
<div className="flex items-center">
|
||||
<div className="w-8 h-8 mr-2 overflow-hidden rounded-full">
|
||||
<img
|
||||
src={kolData.find(k => k.id === post.kolId)?.avatar || ''}
|
||||
alt={kolData.find(k => k.id === post.kolId)?.name || ''}
|
||||
src={kolData.find(k => k.influencer_id === post.kolId)?.avatar || ''}
|
||||
alt={kolData.find(k => k.influencer_id === post.kolId)?.name || ''}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-gray-900">
|
||||
{kolData.find(k => k.id === post.kolId)?.name || ''}
|
||||
{kolData.find(k => k.influencer_id === post.kolId)?.name || ''}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -824,7 +1070,7 @@ const Analytics: React.FC = () => {
|
||||
<div className="flex items-center">
|
||||
{getPlatformIcon(post.platform)}
|
||||
<span className="ml-2 text-sm text-gray-900">
|
||||
{post.platform === 'xiaohongshu' ? '小紅書' : post.platform}
|
||||
{post.platform === 'xiaohongshu' ? '小红书' : post.platform}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
@@ -875,29 +1121,29 @@ const Analytics: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 概覽卡片 */}
|
||||
{/* 概览卡片 */}
|
||||
<div className="grid grid-cols-1 gap-6 mb-8 md:grid-cols-3">
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<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" />
|
||||
</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>
|
||||
<span className="text-green-500">↑ 12% 较上周</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<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" />
|
||||
</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>
|
||||
<span className="text-green-500">↑ 0.5% 较上周</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -909,14 +1155,14 @@ const Analytics: React.FC = () => {
|
||||
<p className="mb-2 text-3xl font-bold text-gray-900">{sentimentData.positive}% 正面</p>
|
||||
<div className="flex items-center text-sm">
|
||||
<TrendingUp className="w-4 h-4 mr-1 text-green-500" />
|
||||
<span className="text-green-500">↑ 5% 較上週</span>
|
||||
<span className="text-green-500">↑ 5% 较上周</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 留言趨勢圖 */}
|
||||
{/* 留言趋势图 */}
|
||||
<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">
|
||||
<div className="flex items-end space-x-2 h-52">
|
||||
{timelineData.map((item, index) => (
|
||||
@@ -940,9 +1186,9 @@ const Analytics: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 mb-8 lg:grid-cols-2">
|
||||
{/* 平台分佈 */}
|
||||
{/* 平台分布 */}
|
||||
<div className="p-6 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="space-y-4">
|
||||
{platformData.map((item, index) => (
|
||||
<div key={index}>
|
||||
@@ -950,11 +1196,11 @@ const Analytics: React.FC = () => {
|
||||
<div className="flex items-center">
|
||||
{getPlatformIcon(item.name)}
|
||||
<span className="ml-2 text-sm font-medium text-gray-700">
|
||||
{item.name === 'xiaohongshu' ? '小紅書' : item.name}
|
||||
{item.name === 'xiaohongshu' ? '小红书' : item.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-sm text-gray-500">{item.value} 則留言</span>
|
||||
<span className="mr-2 text-sm text-gray-500">{item.value} 则留言</span>
|
||||
<span className="text-sm font-medium text-gray-700">{item.percentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -969,13 +1215,13 @@ const Analytics: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 審核狀態分佈 */}
|
||||
{/* 审核状态分布 */}
|
||||
<div className="p-6 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="flex justify-center mb-6">
|
||||
<div className="relative w-48 h-48 rounded-full">
|
||||
{statusData.map((item, index) => {
|
||||
// 計算每個扇形的起始角度和結束角度
|
||||
// 计算每个扇形的起始角度和结束角度
|
||||
const startAngle = index === 0 ? 0 : statusData.slice(0, index).reduce((sum, i) => sum + i.percentage, 0) * 3.6;
|
||||
const endAngle = startAngle + item.percentage * 3.6;
|
||||
|
||||
@@ -1003,7 +1249,7 @@ const Analytics: React.FC = () => {
|
||||
<span className="ml-2 text-sm font-medium text-gray-700">{getStatusName(item.name)}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-sm text-gray-500">{item.value} 則留言</span>
|
||||
<span className="mr-2 text-sm text-gray-500">{item.value} 则留言</span>
|
||||
<span className="text-sm font-medium text-gray-700">{item.percentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1012,128 +1258,49 @@ const Analytics: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 mb-8 lg:grid-cols-2">
|
||||
{/* 情感分析詳情 */}
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-800">情感分析詳情</h3>
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative w-48 h-12 rounded-lg bg-gradient-to-r from-red-500 via-yellow-400 to-green-500">
|
||||
<div
|
||||
className="absolute top-0 w-1 h-full transform -translate-x-1/2 bg-black border-2 border-white rounded-full"
|
||||
style={{ left: `${sentimentData.positive}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">負面</p>
|
||||
<p className="text-lg font-bold text-red-500">{sentimentData.negative}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">中性</p>
|
||||
<p className="text-lg font-bold text-yellow-500">{sentimentData.neutral}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">正面</p>
|
||||
<p className="text-lg font-bold text-green-500">{sentimentData.positive}%</p>
|
||||
</div>
|
||||
{/* 情感分析详情 */}
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-800">情感分析详情</h3>
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="relative w-48 h-12 rounded-lg bg-gradient-to-r from-red-500 via-yellow-400 to-green-500">
|
||||
<div
|
||||
className="absolute top-0 w-1 h-full transform -translate-x-1/2 bg-black border-2 border-white rounded-full"
|
||||
style={{ left: `${sentimentData.positive}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">负面</p>
|
||||
<p className="text-lg font-bold text-red-500">{sentimentData.negative}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">中性</p>
|
||||
<p className="text-lg font-bold text-yellow-500">{sentimentData.neutral}%</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-500">正面</p>
|
||||
<p className="text-lg font-bold text-green-500">{sentimentData.positive}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 熱門文章 */}
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-800">熱門文章</h3>
|
||||
<div className="space-y-4">
|
||||
{popularArticles.map((article: any, index: number) => (
|
||||
<div key={index} className="pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<p className="mb-1 text-sm font-medium text-gray-800">{article.title}</p>
|
||||
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>{article.count} 則留言</span>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 mr-1 bg-green-500 rounded-full"></div>
|
||||
<span>高互動</span>
|
||||
</div>
|
||||
{/* 热门文章 */}
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-800">热门文章</h3>
|
||||
<div className="space-y-4">
|
||||
{popularArticles.map((article: any, index: number) => (
|
||||
<div key={index} className="pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<p className="mb-1 text-sm font-medium text-gray-800">{article.title}</p>
|
||||
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>{article.count} 则留言</span>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2 mr-1 bg-green-500 rounded-full"></div>
|
||||
<span>高互动</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 關鍵字雲 */}
|
||||
<div className="p-6 mb-8 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-800">熱門關鍵字</h3>
|
||||
<div className="flex flex-wrap justify-center gap-3 py-4">
|
||||
<span className="px-4 py-2 text-lg text-blue-800 bg-blue-100 rounded-full">產品</span>
|
||||
<span className="px-6 py-3 text-xl text-green-800 bg-green-100 rounded-full">推薦</span>
|
||||
<span className="px-3 py-1 text-base text-yellow-800 bg-yellow-100 rounded-full">價格</span>
|
||||
<span className="px-5 py-2 text-lg text-purple-800 bg-purple-100 rounded-full">質感</span>
|
||||
<span className="py-3 text-2xl text-red-800 bg-red-100 rounded-full px-7">效果</span>
|
||||
<span className="px-3 py-1 text-base text-indigo-800 bg-indigo-100 rounded-full">服務</span>
|
||||
<span className="px-4 py-2 text-lg text-pink-800 bg-pink-100 rounded-full">美觀</span>
|
||||
<span className="px-5 py-2 text-lg text-blue-800 bg-blue-100 rounded-full">環境</span>
|
||||
<span className="px-3 py-1 text-base text-green-800 bg-green-100 rounded-full">便宜</span>
|
||||
<span className="px-6 py-3 text-xl text-yellow-800 bg-yellow-100 rounded-full">好用</span>
|
||||
<span className="px-4 py-2 text-lg text-purple-800 bg-purple-100 rounded-full">設計</span>
|
||||
<span className="px-3 py-1 text-base text-red-800 bg-red-100 rounded-full">功能</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 用戶互動時間分析 */}
|
||||
<div className="p-6 bg-white rounded-lg shadow">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-800">用戶互動時間分析</h3>
|
||||
<div className="grid h-40 grid-cols-12 gap-1">
|
||||
{Array.from({ length: 24 }).map((_, hour) => {
|
||||
// 模擬不同時段的活躍度
|
||||
let height = '20%';
|
||||
if (hour >= 9 && hour <= 11) height = '60%';
|
||||
if (hour >= 12 && hour <= 14) height = '40%';
|
||||
if (hour >= 19 && hour <= 22) height = '80%';
|
||||
|
||||
return (
|
||||
<div key={hour} className="flex flex-col items-center justify-end">
|
||||
<div
|
||||
className="w-full transition-all bg-blue-500 rounded-t-sm hover:bg-blue-600"
|
||||
style={{ height }}
|
||||
></div>
|
||||
<span className="mt-1 text-xs">{hour}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-center text-gray-500">
|
||||
<p>時間 (24小時制)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 內容表現分析 */}
|
||||
<div className="p-6 bg-white rounded-lg shadow-sm">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h3 className="text-lg font-medium text-gray-800">內容表現分析</h3>
|
||||
<div className="flex space-x-2">
|
||||
<select
|
||||
className="px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={selectedPlatform}
|
||||
onChange={(e) => setSelectedPlatform(e.target.value)}
|
||||
>
|
||||
<option value="all">所有平台</option>
|
||||
<option value="facebook">Facebook</option>
|
||||
<option value="instagram">Instagram</option>
|
||||
<option value="twitter">Twitter</option>
|
||||
<option value="linkedin">LinkedIn</option>
|
||||
<option value="youtube">YouTube</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-hidden border border-gray-200 rounded-lg shadow-sm">
|
||||
<div className="p-6 text-center">
|
||||
<div className="mb-4 text-gray-500">
|
||||
<MessageSquare className="inline-block w-12 h-12" />
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium text-gray-900">內容分析功能暂时不可用</h3>
|
||||
<p className="text-gray-500">我们正在努力改进这一功能,请稍后再试。</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user