更新首页部分内容和其他后台相关功能
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { prisma } from "@/lib/db";
|
||||
import { AddonCard } from "@/components/public/AddonCard";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Link from "next/link";
|
||||
import { Package, Search, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
general: "通用",
|
||||
@@ -16,16 +16,20 @@ const categoryLabels: Record<string, string> = {
|
||||
|
||||
export const metadata = {
|
||||
title: "插件列表 - Nanami",
|
||||
description: "浏览和下载 Turtle WoW 插件",
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 30;
|
||||
|
||||
const PAGE_SIZE = 12;
|
||||
|
||||
export default async function AddonsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ category?: string; search?: string }>;
|
||||
searchParams: Promise<{ category?: string; search?: string; page?: string }>;
|
||||
}) {
|
||||
const { category, search } = await searchParams;
|
||||
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;
|
||||
@@ -36,66 +40,144 @@ export default async function AddonsPage({
|
||||
];
|
||||
}
|
||||
|
||||
const addons = await prisma.addon.findMany({
|
||||
where,
|
||||
include: {
|
||||
releases: {
|
||||
where: { isLatest: true },
|
||||
select: { version: true },
|
||||
const [addons, total, categories] = await Promise.all([
|
||||
prisma.addon.findMany({
|
||||
where,
|
||||
include: {
|
||||
releases: {
|
||||
where: { isLatest: true },
|
||||
select: { version: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { totalDownloads: "desc" },
|
||||
});
|
||||
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 categories = await 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 (
|
||||
<div className="mx-auto max-w-6xl px-4 py-12">
|
||||
<h1 className="text-3xl font-bold">插件列表</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<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="mt-6 flex flex-wrap gap-2">
|
||||
<Link href="/addons">
|
||||
<Badge
|
||||
variant={!category ? "default" : "outline"}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
全部
|
||||
</Badge>
|
||||
<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}`}>
|
||||
<Badge
|
||||
variant={category === cat.category ? "default" : "outline"}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{categoryLabels[cat.category] || cat.category} ({cat._count.id})
|
||||
</Badge>
|
||||
<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 */}
|
||||
<div className="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{addons.map((addon) => (
|
||||
<AddonCard key={addon.id} addon={addon} />
|
||||
))}
|
||||
</div>
|
||||
{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>
|
||||
|
||||
{addons.length === 0 && (
|
||||
<div className="mt-16 text-center">
|
||||
<p className="text-lg text-muted-foreground">
|
||||
{/* 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>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user