159 lines
6.2 KiB
TypeScript
159 lines
6.2 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
import { notFound } from "next/navigation";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
interface Props {
|
|
params: { category: string };
|
|
searchParams: { sort?: string; page?: string };
|
|
}
|
|
|
|
export default async function CategoryPage({ params, searchParams }: Props) {
|
|
const category = await prisma.forumCategory.findUnique({
|
|
where: { slug: params.category },
|
|
});
|
|
|
|
if (!category) notFound();
|
|
|
|
const childCategories = await prisma.forumCategory.findMany({
|
|
where: { parentId: category.id },
|
|
select: { id: true, name: true, slug: true },
|
|
});
|
|
|
|
const sort = searchParams.sort || "latest";
|
|
const page = parseInt(searchParams.page || "1", 10);
|
|
const pageSize = 20;
|
|
|
|
const orderBy: any = {};
|
|
if (sort === "latest") orderBy.createdAt = "desc";
|
|
else if (sort === "hot") orderBy.viewCount = "desc";
|
|
else if (sort === "active") orderBy.lastReplyAt = "desc";
|
|
else orderBy.createdAt = "desc";
|
|
|
|
const [topics, totalCount] = await Promise.all([
|
|
prisma.forumTopic.findMany({
|
|
where: { categoryId: category.id },
|
|
include: {
|
|
category: { select: { name: true, slug: true, icon: true } },
|
|
user: { select: { id: true, name: true, email: true, avatarUrl: true, level: true, points: true, isBot: true } },
|
|
_count: { select: { posts: true } },
|
|
},
|
|
orderBy: [{ isPinned: "desc" }, orderBy],
|
|
skip: (page - 1) * pageSize,
|
|
take: pageSize,
|
|
}),
|
|
prisma.forumTopic.count({ where: { categoryId: category.id } }),
|
|
]);
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#f8fafc]">
|
|
<div className="max-w-4xl mx-auto px-4 py-8">
|
|
<div className="mb-6">
|
|
<a href="/community" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
|
|
← 返回社区
|
|
</a>
|
|
</div>
|
|
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold">{category.name}</h1>
|
|
{category.description && (
|
|
<p className="mt-1 text-sm text-muted-foreground">{category.description}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 mb-6">
|
|
{["latest", "hot", "active"].map((s) => (
|
|
<a
|
|
key={s}
|
|
href={`/community/${category.slug}?sort=${s}`}
|
|
className={`px-3 py-1.5 rounded-lg text-sm transition-colors ${
|
|
sort === s ? "bg-primary text-white" : "bg-white border border-border/40 text-muted-foreground hover:border-border/60"
|
|
}`}
|
|
>
|
|
{s === "latest" ? "最新" : s === "hot" ? "热门" : "活跃"}
|
|
</a>
|
|
))}
|
|
</div>
|
|
|
|
{childCategories.length > 0 && (
|
|
<div className="flex flex-wrap items-center gap-2 mb-6">
|
|
<a
|
|
href={`/community/${category.slug}`}
|
|
className="px-3 py-1.5 rounded-lg text-sm bg-primary text-white"
|
|
>
|
|
全部
|
|
</a>
|
|
{childCategories.map((child) => (
|
|
<a
|
|
key={child.id}
|
|
href={`/community/${child.slug}`}
|
|
className="px-3 py-1.5 rounded-lg text-sm bg-white border border-border/40 text-muted-foreground hover:border-border/60 hover:text-foreground transition-colors"
|
|
>
|
|
{child.name}
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-3">
|
|
{topics.map((topic) => {
|
|
const initial = topic.user.name?.charAt(0).toUpperCase() || "?";
|
|
return (
|
|
<a
|
|
key={topic.id}
|
|
href={`/community/topic/${topic.id}`}
|
|
className="flex items-start gap-3 p-4 rounded-xl border border-border/40 bg-white transition-all hover:border-border/60 hover:shadow-sm block"
|
|
>
|
|
{topic.user.avatarUrl ? (
|
|
<img src={topic.user.avatarUrl} alt="" className="h-9 w-9 rounded-full border border-border/40 object-cover flex-shrink-0" />
|
|
) : (
|
|
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary flex-shrink-0">
|
|
{initial}
|
|
</div>
|
|
)}
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-2">
|
|
{topic.isPinned && (
|
|
<span className="text-[10px] font-medium text-primary bg-primary/10 px-1.5 py-0.5 rounded-full">置顶</span>
|
|
)}
|
|
{topic.isLocked && (
|
|
<span className="text-[10px] font-medium text-muted-foreground bg-muted/30 px-1.5 py-0.5 rounded-full">已锁定</span>
|
|
)}
|
|
<h3 className="text-sm font-medium hover:text-primary transition-colors truncate">
|
|
{topic.title}
|
|
</h3>
|
|
</div>
|
|
<div className="mt-1.5 flex items-center gap-3 text-xs text-muted-foreground/50">
|
|
<span>{topic.user.name || topic.user.email?.split("@")[0]}{topic.user.isBot && <span className="ml-1 px-1 py-0.5 text-[9px] font-medium bg-sky-100 text-sky-600 rounded">AI</span>}</span>
|
|
<span>{topic.createdAt?.toLocaleDateString("zh-CN")}</span>
|
|
<span>{topic._count.posts}回复</span>
|
|
<span>{topic.viewCount}浏览</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{totalCount > pageSize && (
|
|
<div className="flex justify-center gap-2 mt-6">
|
|
{Array.from({ length: Math.ceil(totalCount / pageSize) }, (_, i) => i + 1).map((p) => (
|
|
<a
|
|
key={p}
|
|
href={`/community/${category.slug}?sort=${sort}&page=${p}`}
|
|
className={`px-3 py-1.5 rounded-lg text-sm transition-colors ${
|
|
page === p
|
|
? "bg-primary text-white"
|
|
: "bg-white border border-border/40 text-muted-foreground hover:border-border/60"
|
|
}`}
|
|
>
|
|
{p}
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|