首次提交:姜十三论坛

后端 Go+Gin:认证/CSRF/限流、板块、帖子、评论、点赞、通知、用户资料、置顶推荐;前端 Next.js 16:发帖/编辑/删除、搜索、分页、点赞、通知中心、设置;基础设施 docker-compose 与配置模板;添加 .gitignore 与专有许可证(保留所有权利)
This commit is contained in:
2026-09-12 01:11:05 +08:00
commit 0c2b0d2d6a
62 changed files with 8289 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
import Link from "next/link";
import { Metadata } from "next";
import { fetchUserProfile, type UserProfile } from "@/lib/api";
import Pagination from "@/components/Pagination";
import UserProfileClient from "./UserProfileClient";
interface UserPageProps {
params: Promise<{ id: string }>;
searchParams: Promise<{ page?: string; tab?: string }>;
}
export async function generateMetadata({ params }: UserPageProps): Promise<Metadata> {
const { id } = await params;
return {
title: `用户资料 - 姜十三论坛`,
description: `查看用户 ${id} 的资料、发帖与评论`,
};
}
export default async function UserPage({ params, searchParams }: UserPageProps) {
const { id } = await params;
const { page: pageStr } = await searchParams;
const page = Math.max(1, parseInt(pageStr || "1", 10) || 1);
let data: UserProfile;
try {
data = await fetchUserProfile(id, page);
} catch {
return (
<div className="text-center py-16" style={{ color: "var(--text-secondary)" }}>
<p className="text-lg">用户不存在</p>
<Link href="/" className="mt-4 inline-block hover:underline" style={{ color: "var(--primary)" }}>
返回首页
</Link>
</div>
);
}
const u = data.user;
const roleLabel = u.role === "admin" ? "管理员" : "用户";
return (
<div className="max-w-4xl mx-auto">
{/* 用户信息卡片 */}
<div
className="rounded-xl border p-6 mb-6 flex items-start gap-5"
style={{ borderColor: "var(--border)", background: "var(--bg)" }}
>
<div
className="w-20 h-20 rounded-full flex items-center justify-center text-2xl font-bold shrink-0"
style={{ background: "var(--bg-secondary)", color: "var(--primary)" }}
>
{u.avatar ? (
<img src={u.avatar} alt={u.nickname} className="w-full h-full rounded-full object-cover" />
) : (
(u.nickname || u.username).charAt(0).toUpperCase()
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h1 className="text-2xl font-bold" style={{ color: "var(--text)" }}>
{u.nickname || u.username}
</h1>
{u.role === "admin" && (
<span
className="text-xs px-2 py-0.5 rounded font-medium"
style={{ background: "var(--primary)", color: "white" }}
>
{roleLabel}
</span>
)}
</div>
<p className="text-sm mt-1" style={{ color: "var(--text-secondary)" }}>
@{u.username} · 加入于 {new Date(u.created_at).toLocaleDateString("zh-CN")}
</p>
<div className="flex gap-6 mt-3 text-sm">
<span style={{ color: "var(--text)" }}>
<strong>{data.stats.post_count}</strong>{" "}
<span style={{ color: "var(--text-secondary)" }}>帖子</span>
</span>
<span style={{ color: "var(--text)" }}>
<strong>{data.stats.comment_count}</strong>{" "}
<span style={{ color: "var(--text-secondary)" }}>评论</span>
</span>
</div>
</div>
</div>
{/* 发帖 / 评论 切换 */}
<UserProfileClient userId={id} initialPosts={data} initialPage={page} />
</div>
);
}