feat(home): add homepage hero banner and migrate community stats

- 新增首页欢迎横幅组件,包含时段问候、用户问候语、发帖引导按钮和社区统计数据
- 从CommunityPanels中移除stats相关逻辑,将社区统计迁移到新的HomeHero组件中
- 优化全局样式,添加横幅相关的CSS动画和样式类
This commit is contained in:
2026-09-13 04:39:20 +08:00
parent 52124f2682
commit 95921d24fa
4 changed files with 161 additions and 48 deletions

View File

@@ -1,36 +1,8 @@
import Link from "next/link";
import { BarChart3, Flame, Users, MessagesSquare, MessageCircle, Heart, Sparkles } from "lucide-react";
import type { OverviewStats, PostListItem, ActiveUser } from "@/lib/api";
import { Flame, Users, MessageCircle, Heart } from "lucide-react";
import type { PostListItem, ActiveUser } from "@/lib/api";
import Avatar from "@/components/Avatar";
/* ---------- 统计 2×2 ---------- */
function StatsGrid({ stats }: { stats: OverviewStats }) {
const items = [
{ icon: MessagesSquare, label: "讨论", value: stats.posts },
{ icon: Users, label: "成员", value: stats.users },
{ icon: MessageCircle, label: "评论", value: stats.comments },
{ icon: Sparkles, label: "今日", value: stats.today_posts },
];
return (
<section className="j13-module" aria-label="社区数据">
<p className="j13-module-title mb-3">
<BarChart3 size={15} style={{ color: "var(--accent)" }} /> 社区数据
</p>
<div className="grid grid-cols-2 gap-2">
{items.map(({ icon: Icon, label, value }) => (
<div key={label} className="j13-stat">
<span className="j13-stat-num">{value}</span>
<span className="j13-stat-lab">
<Icon size={12} /> {label}
</span>
</div>
))}
</div>
</section>
);
}
/* ---------- 热门榜 ---------- */
function HotRanking({ posts }: { posts: PostListItem[] }) {
@@ -77,8 +49,8 @@ function HotRanking({ posts }: { posts: PostListItem[] }) {
function ActiveUsers({ users }: { users: ActiveUser[] }) {
return (
<section className="j13-module" aria-labelledby="active-users-title">
<p className="j13-module-title mb-3" id="active-users-title">
<section className="j13-module" aria-label="活跃成员">
<p className="j13-module-title mb-3">
<Users size={15} style={{ color: "var(--accent)" }} /> 活跃成员
</p>
{users.length === 0 ? (
@@ -104,18 +76,15 @@ function ActiveUsers({ users }: { users: ActiveUser[] }) {
/* ---------- 桌面 / 移动两种装配 ---------- */
export function CommunityPanels({
stats,
hot,
activeUsers,
}: {
stats: OverviewStats;
hot: PostListItem[];
activeUsers: ActiveUser[];
}) {
return (
<aside className="hidden lg:block w-[280px] xl:w-[300px] shrink-0" aria-label="社区面板">
<div className="sticky top-24 space-y-4">
<StatsGrid stats={stats} />
<HotRanking posts={hot} />
<ActiveUsers users={activeUsers} />
</div>
@@ -124,17 +93,14 @@ export function CommunityPanels({
}
export function CommunityPanelsMobile({
stats,
hot,
activeUsers,
}: {
stats: OverviewStats;
hot: PostListItem[];
activeUsers: ActiveUser[];
}) {
return (
<div className="lg:hidden space-y-4" aria-label="社区面板">
<StatsGrid stats={stats} />
<HotRanking posts={hot} />
<ActiveUsers users={activeUsers} />
</div>

View File

@@ -0,0 +1,76 @@
import Link from "next/link";
import { PenSquare, LogIn, UserPlus, MessagesSquare, Users, MessageCircle, Sparkles } from "lucide-react";
import type { OverviewStats } from "@/lib/api";
type HeroUser = { username: string; nickname: string } | null;
function greetingOf(h: number): string {
if (h >= 5 && h < 11) return "早上好";
if (h >= 11 && h < 14) return "中午好";
if (h >= 14 && h < 18) return "下午好";
if (h >= 18 && h < 23) return "晚上好";
return "夜深了";
}
// 首页欢迎横幅:时段问候 + 站点主张 + 发帖引导 + 内联社区数据。纯服务端组件。
export default function HomeHero({ user, stats }: { user: HeroUser; stats: OverviewStats | null }) {
const name = user ? user.nickname || user.username : null;
const hello = greetingOf(new Date().getHours());
const today = stats?.today_posts ?? 0;
const statItems = stats
? [
{ icon: MessagesSquare, label: "讨论", value: stats.posts, hot: false },
{ icon: Users, label: "成员", value: stats.users, hot: false },
{ icon: MessageCircle, label: "评论", value: stats.comments, hot: false },
{ icon: Sparkles, label: today > 0 ? "今日新帖" : "今日", value: today, hot: today > 0 },
]
: [];
return (
<section className="j13-hero mb-5 lg:mb-6" aria-label="欢迎">
<span aria-hidden className="j13-hero-glow" />
<div className="relative flex flex-col lg:flex-row lg:items-center lg:justify-between gap-5">
<div className="min-w-0">
<p className="meta text-[13px]">{name ? `${hello},${name}` : `${hello},欢迎来到姜十三`}</p>
<h2
className="mt-1.5 text-[19px] sm:text-[22px] font-extrabold tracking-tight"
style={{ color: "var(--ink)" }}
>
分享技术、讨论问题、交流心得
</h2>
<div className="mt-4 flex flex-wrap gap-2.5">
{user ? (
<Link href="/compose" className="btn btn-primary w-full sm:w-auto justify-center">
<PenSquare size={15} /> 发起讨论
</Link>
) : (
<>
<Link href="/login?redirect=/" className="btn btn-primary w-full sm:w-auto justify-center">
<LogIn size={15} /> 登录后参与讨论
</Link>
<Link href="/register" className="btn btn-line w-full sm:w-auto justify-center">
<UserPlus size={15} /> 注册
</Link>
</>
)}
</div>
</div>
{statItems.length > 0 && (
<dl className="j13-hero-stats shrink-0" aria-label="社区数据">
{statItems.map(({ icon: Icon, label, value, hot }) => (
<div key={label} className="j13-hero-stat">
<dd className={`j13-hero-stat-num${hot ? " is-hot" : ""}`}>{value}</dd>
<dt className="j13-hero-stat-lab">
{hot && <span className="j13-dot-pulse" aria-hidden />}
<Icon size={12} /> {label}
</dt>
</div>
))}
</dl>
)}
</div>
</section>
);
}