184 lines
6.4 KiB
TypeScript
184 lines
6.4 KiB
TypeScript
import { prisma } from "@/lib/db";
|
|
import { AddonCard } from "@/components/public/AddonCard";
|
|
import Link from "next/link";
|
|
import { Package, Search, ChevronLeft, ChevronRight } from "lucide-react";
|
|
|
|
const categoryLabels: Record<string, string> = {
|
|
general: "通用",
|
|
gameplay: "游戏玩法",
|
|
ui: "界面增强",
|
|
combat: "战斗",
|
|
raid: "团队副本",
|
|
pvp: "PvP",
|
|
tradeskill: "专业技能",
|
|
utility: "实用工具",
|
|
};
|
|
|
|
export const metadata = {
|
|
title: "插件列表 - Nanami",
|
|
description: "浏览和下载 Turtle WoW 插件",
|
|
};
|
|
|
|
export const revalidate = 30;
|
|
|
|
const PAGE_SIZE = 12;
|
|
|
|
export default async function AddonsPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ category?: string; search?: string; page?: string }>;
|
|
}) {
|
|
const { category, search, page: pageStr } = await searchParams;
|
|
const page = Math.max(1, parseInt(pageStr || "1", 10) || 1);
|
|
|
|
const where: Record<string, unknown> = { published: true };
|
|
if (category) where.category = category;
|
|
if (search) {
|
|
where.OR = [
|
|
{ name: { contains: search, mode: "insensitive" } },
|
|
{ summary: { contains: search, mode: "insensitive" } },
|
|
];
|
|
}
|
|
|
|
const [addons, total, categories] = await Promise.all([
|
|
prisma.addon.findMany({
|
|
where,
|
|
include: {
|
|
releases: {
|
|
where: { isLatest: true },
|
|
select: { version: true },
|
|
},
|
|
},
|
|
orderBy: { totalDownloads: "desc" },
|
|
skip: (page - 1) * PAGE_SIZE,
|
|
take: PAGE_SIZE,
|
|
}),
|
|
prisma.addon.count({ where }),
|
|
prisma.addon.groupBy({
|
|
by: ["category"],
|
|
where: { published: true },
|
|
_count: { id: true },
|
|
}),
|
|
]);
|
|
|
|
const totalPages = Math.ceil(total / PAGE_SIZE);
|
|
|
|
const buildHref = (p: number) => {
|
|
const params = new URLSearchParams();
|
|
if (category) params.set("category", category);
|
|
if (search) params.set("search", search);
|
|
if (p > 1) params.set("page", String(p));
|
|
const qs = params.toString();
|
|
return `/addons${qs ? `?${qs}` : ""}`;
|
|
};
|
|
|
|
return (
|
|
<section className="mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
|
|
<div className="mb-2 flex items-center gap-2 sm:gap-3">
|
|
<Package className="h-5 w-5 text-amber-400 sm:h-6 sm:w-6" />
|
|
<h1 className="text-2xl font-bold text-amber-100 sm:text-3xl">
|
|
插件列表
|
|
</h1>
|
|
</div>
|
|
<p className="mb-6 text-sm text-gray-400 sm:mb-10 sm:text-base">
|
|
浏览和下载 World of Warcraft 插件
|
|
</p>
|
|
|
|
{/* Category Filter */}
|
|
<div className="mb-6 flex flex-wrap gap-2 sm:mb-8">
|
|
<Link
|
|
href="/addons"
|
|
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all sm:text-sm ${
|
|
!category
|
|
? "bg-amber-500/20 text-amber-200 ring-1 ring-amber-500/30"
|
|
: "bg-white/[0.04] text-gray-400 hover:bg-white/[0.08] hover:text-gray-300"
|
|
}`}
|
|
>
|
|
全部
|
|
</Link>
|
|
{categories.map((cat) => (
|
|
<Link
|
|
key={cat.category}
|
|
href={`/addons?category=${cat.category}`}
|
|
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all sm:text-sm ${
|
|
category === cat.category
|
|
? "bg-amber-500/20 text-amber-200 ring-1 ring-amber-500/30"
|
|
: "bg-white/[0.04] text-gray-400 hover:bg-white/[0.08] hover:text-gray-300"
|
|
}`}
|
|
>
|
|
{categoryLabels[cat.category] || cat.category} ({cat._count.id})
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
{/* Addon Grid */}
|
|
{addons.length > 0 ? (
|
|
<>
|
|
<div className="grid gap-4 sm:gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
{addons.map((addon) => (
|
|
<AddonCard key={addon.id} addon={addon} />
|
|
))}
|
|
</div>
|
|
|
|
{/* Pagination */}
|
|
{totalPages > 1 && (
|
|
<div className="mt-10 flex items-center justify-center gap-2">
|
|
{page > 1 ? (
|
|
<Link
|
|
href={buildHref(page - 1)}
|
|
className="flex h-9 w-9 items-center justify-center rounded-lg border border-amber-500/15 bg-white/[0.03] text-gray-400 transition-colors hover:border-amber-500/30 hover:text-amber-200"
|
|
>
|
|
<ChevronLeft className="h-4 w-4" />
|
|
</Link>
|
|
) : (
|
|
<span className="flex h-9 w-9 items-center justify-center rounded-lg border border-white/5 text-gray-600">
|
|
<ChevronLeft className="h-4 w-4" />
|
|
</span>
|
|
)}
|
|
|
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
|
|
<Link
|
|
key={p}
|
|
href={buildHref(p)}
|
|
className={`flex h-9 w-9 items-center justify-center rounded-lg text-sm font-medium transition-colors ${
|
|
p === page
|
|
? "bg-amber-500/20 text-amber-200 ring-1 ring-amber-500/30"
|
|
: "border border-amber-500/10 bg-white/[0.03] text-gray-400 hover:border-amber-500/25 hover:text-amber-200"
|
|
}`}
|
|
>
|
|
{p}
|
|
</Link>
|
|
))}
|
|
|
|
{page < totalPages ? (
|
|
<Link
|
|
href={buildHref(page + 1)}
|
|
className="flex h-9 w-9 items-center justify-center rounded-lg border border-amber-500/15 bg-white/[0.03] text-gray-400 transition-colors hover:border-amber-500/30 hover:text-amber-200"
|
|
>
|
|
<ChevronRight className="h-4 w-4" />
|
|
</Link>
|
|
) : (
|
|
<span className="flex h-9 w-9 items-center justify-center rounded-lg border border-white/5 text-gray-600">
|
|
<ChevronRight className="h-4 w-4" />
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</>
|
|
) : (
|
|
<div className="flex flex-col items-center py-20">
|
|
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-amber-500/10">
|
|
<Search className="h-7 w-7 text-amber-400/60" />
|
|
</div>
|
|
<p className="text-lg font-medium text-gray-400">
|
|
{search ? `没有找到"${search}"相关的插件` : "暂无插件"}
|
|
</p>
|
|
<p className="mt-1 text-sm text-gray-500">
|
|
{search ? "尝试更换关键词搜索" : "稍后再来查看吧"}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|