casualroom/apps/fenghuo/web/app/[locale]/dashboard/page.tsx

363 lines
11 KiB
TypeScript
Raw Normal View History

2025-07-28 07:50:50 +08:00
'use client';
import { useSetPageInfo } from '@/components/providers/dashboard-provider';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@nice/ui/components/card';
import { Badge } from '@nice/ui/components/badge';
import { Button } from '@nice/ui/components/button';
import { Progress } from '@nice/ui/components/progress';
import {
IconFileText,
IconEye,
IconHeart,
IconCloud,
IconTrendingUp,
IconTrendingDown,
IconBell,
IconCalendar,
IconClock,
IconEdit,
IconShare,
IconBookmark,
IconChartBar,
IconDatabase,
IconFolder,
IconDownload,
IconUpload,
} from '@tabler/icons-react';
// 模拟用户个人数据
const userStats = {
articles: {
total: 24,
published: 18,
draft: 6,
totalViews: 15420,
totalLikes: 892,
thisMonth: 3,
growth: '+12%',
},
storage: {
used: '1.2GB',
total: '5GB',
usedBytes: 1288490189,
totalBytes: 5368709120,
filesCount: 156,
foldersCount: 12,
},
engagement: {
avgViews: 642,
avgLikes: 37,
comments: 234,
shares: 89,
bookmarks: 156,
},
};
const recentNotifications = [
{ id: 1, type: 'like', message: '您的文章《Next.js 最佳实践》收到了新的点赞', time: '5分钟前', unread: true },
{ id: 2, type: 'comment', message: '用户李四评论了您的文章《TypeScript 进阶指南》', time: '1小时前', unread: true },
{ id: 3, type: 'follow', message: '用户王五关注了您', time: '2小时前', unread: false },
{ id: 4, type: 'system', message: '您的存储空间使用已达到60%', time: '6小时前', unread: false },
];
const recentArticles = [
{ id: 1, title: 'Next.js 最佳实践指南', status: 'published', views: 1240, likes: 89, publishedAt: '2024-01-15' },
{ id: 2, title: 'TypeScript 进阶技巧', status: 'published', views: 856, likes: 67, publishedAt: '2024-01-12' },
{ id: 3, title: 'React Hooks 深度解析', status: 'draft', views: 0, likes: 0, publishedAt: null },
{ id: 4, title: 'Tailwind CSS 实战教程', status: 'published', views: 1523, likes: 124, publishedAt: '2024-01-08' },
];
const quickActions = [
{ title: '写新文章', icon: IconEdit, href: '/editor', description: '开始创作新的文章' },
{ title: '上传文件', icon: IconUpload, href: '/resource', description: '上传文件到云盘' },
// { title: '查看统计', icon: IconChartBar, href: '/articles/analytics', description: '查看文章数据分析' },
// { title: '管理文件', icon: IconFolder, href: '/drive', description: '管理您的云盘文件' },
];
function StatsCard({
title,
value,
description,
icon: Icon,
trend,
trendValue,
className = '',
}: {
title: string;
value: string | number;
description: string;
icon: any;
trend?: 'up' | 'down';
trendValue?: string;
className?: string;
}) {
return (
<Card className={className}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">{title}</CardTitle>
<Icon className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
<div className="flex items-center space-x-2 text-xs text-muted-foreground">
<span>{description}</span>
{trend && trendValue && (
<div className="flex items-center">
{trend === 'up' ? (
<IconTrendingUp className="h-3 w-3 text-green-500" />
) : (
<IconTrendingDown className="h-3 w-3 text-red-500" />
)}
<span className={trend === 'up' ? 'text-green-500' : 'text-red-500'}>{trendValue}</span>
</div>
)}
</div>
</CardContent>
</Card>
);
}
function StorageCard() {
const usagePercent = Math.round((userStats.storage.usedBytes / userStats.storage.totalBytes) * 100);
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<IconCloud className="h-5 w-5" />
<span>使</span>
</CardTitle>
<CardDescription>使</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex justify-between items-center">
<span className="text-sm font-medium">使</span>
<span className="text-sm text-muted-foreground">
{userStats.storage.used} / {userStats.storage.total}
</span>
</div>
<div className="space-y-2">
<Progress value={usagePercent} className="h-2" />
<div className="flex justify-between text-xs text-muted-foreground">
<span>{usagePercent}% 使</span>
<span>{100 - usagePercent}% </span>
</div>
</div>
<div className="grid grid-cols-2 gap-4 pt-2">
<div className="text-center">
<div className="text-2xl font-bold text-primary">{userStats.storage.filesCount}</div>
<div className="text-xs text-muted-foreground"></div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-primary">{userStats.storage.foldersCount}</div>
<div className="text-xs text-muted-foreground"></div>
</div>
</div>
</CardContent>
</Card>
);
}
function NotificationsCard() {
const getNotificationIcon = (type: string) => {
switch (type) {
case 'like':
return <IconHeart className="h-4 w-4 text-red-500" />;
case 'comment':
return <IconFileText className="h-4 w-4 text-blue-500" />;
case 'follow':
return <IconShare className="h-4 w-4 text-green-500" />;
case 'system':
return <IconBell className="h-4 w-4 text-orange-500" />;
default:
return <IconBell className="h-4 w-4" />;
}
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<IconBell className="h-5 w-5" />
<span></span>
</div>
<Badge variant="secondary">{recentNotifications.filter((n) => n.unread).length}</Badge>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{recentNotifications.slice(0, 4).map((notification) => (
<div key={notification.id} className="flex items-start space-x-3">
<div className="flex-shrink-0 mt-1">{getNotificationIcon(notification.type)}</div>
<div className="flex-1 min-w-0">
<p className={`text-sm ${notification.unread ? 'font-medium' : ''}`}>{notification.message}</p>
<div className="flex items-center space-x-1 text-xs text-muted-foreground">
<IconClock className="h-3 w-3" />
<span>{notification.time}</span>
{notification.unread && <div className="w-2 h-2 bg-blue-500 rounded-full ml-2" />}
</div>
</div>
</div>
))}
</div>
<div className="mt-4 pt-4 border-t">
<Button variant="outline" size="sm" className="w-full">
</Button>
</div>
</CardContent>
</Card>
);
}
function RecentArticlesCard() {
const getStatusBadge = (status: string) => {
switch (status) {
case 'published':
return <Badge variant="default"></Badge>;
case 'draft':
return <Badge variant="secondary">稿</Badge>;
default:
return <Badge variant="outline"></Badge>;
}
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<IconFileText className="h-5 w-5" />
<span></span>
</CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{recentArticles.map((article) => (
<div key={article.id} className="flex items-center justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium truncate">{article.title}</p>
{getStatusBadge(article.status)}
</div>
<div className="flex items-center space-x-4 text-xs text-muted-foreground mt-1">
<div className="flex items-center space-x-1">
<IconEye className="h-3 w-3" />
<span>{article.views}</span>
</div>
<div className="flex items-center space-x-1">
<IconHeart className="h-3 w-3" />
<span>{article.likes}</span>
</div>
{article.publishedAt && (
<div className="flex items-center space-x-1">
<IconCalendar className="h-3 w-3" />
<span>{article.publishedAt}</span>
</div>
)}
</div>
</div>
</div>
))}
</div>
<div className="mt-4 pt-4 border-t">
<Button variant="outline" size="sm" className="w-full">
</Button>
</div>
</CardContent>
</Card>
);
}
function QuickActionsCard() {
return (
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-3">
{quickActions.map((action, index) => (
<Button
key={index}
variant="outline"
className="h-auto p-3 flex flex-col items-center justify-center space-y-2"
asChild
>
<a href={action.href}>
<action.icon className="h-5 w-5" />
<div className="text-center">
<div className="text-xs font-medium">{action.title}</div>
<div className="text-xs text-muted-foreground">{action.description}</div>
</div>
</a>
</Button>
))}
</div>
</CardContent>
</Card>
);
}
export default function DashboardPage() {
// 设置页面信息
useSetPageInfo({
title: '我的仪表盘',
subtitle: '欢迎回来,查看您的创作数据和最新动态',
});
return (
<div className="@container/main flex flex-1 flex-col gap-6 p-6">
{/* 统计卡片 */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<StatsCard
title="发布文章"
value={userStats.articles.published}
description={`总共 ${userStats.articles.total}`}
icon={IconFileText}
trend="up"
trendValue={userStats.articles.growth}
/>
<StatsCard
title="总阅读量"
value={userStats.articles.totalViews.toLocaleString()}
description={`平均 ${userStats.engagement.avgViews} 次/篇`}
icon={IconEye}
/>
<StatsCard
title="获得点赞"
value={userStats.articles.totalLikes.toLocaleString()}
description={`平均 ${userStats.engagement.avgLikes} 个/篇`}
icon={IconHeart}
trend="up"
trendValue="+8%"
/>
<StatsCard
title="存储空间"
value={userStats.storage.used}
description={`总容量 ${userStats.storage.total}`}
icon={IconDatabase}
/>
</div>
{/* 详细信息第一行 */}
<div className="grid gap-6 md:grid-cols-2">
<StorageCard />
<NotificationsCard />
</div>
{/* 详细信息第二行 */}
<div className="grid gap-6 md:grid-cols-2">
<RecentArticlesCard />
<QuickActionsCard />
</div>
</div>
);
}