feat: Banner UI美化 & 新增文章/公告/图库/媒体管理等功能
- Banner: Ken Burns缩放动效、左右导航箭头、进度条指示器、hover暂停、暗角遮罩、shimmer按钮动画 - 新增文章管理(CRUD)与公开文章页 - 新增Banner/Gallery图片管理API - 新增媒体管理页面 - 新增更新日志页面 - 新增页面访问追踪 - 新增Markdown渲染组件 - .gitignore排除.cursor目录 Made-with: Cursor
This commit is contained in:
@@ -3,51 +3,73 @@ import { prisma } from "@/lib/db";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AddonCard } from "@/components/public/AddonCard";
|
||||
import { HeroBanner } from "@/components/public/HeroBanner";
|
||||
import { Sparkles, Shield, Zap } from "lucide-react";
|
||||
import { GameGallery } from "@/components/public/GameGallery";
|
||||
import { Sparkles, Shield, Zap, Calendar } from "lucide-react";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function HomePage() {
|
||||
const [featuredAddons, totalDownloads, launcher] = await Promise.all([
|
||||
prisma.addon.findMany({
|
||||
where: { published: true },
|
||||
include: {
|
||||
releases: {
|
||||
where: { isLatest: true },
|
||||
select: { version: true },
|
||||
const [featuredAddons, launcher, launcherDownloads, banners, galleryImages, latestArticles] =
|
||||
await Promise.all([
|
||||
prisma.addon.findMany({
|
||||
where: { published: true },
|
||||
include: {
|
||||
releases: {
|
||||
where: { isLatest: true },
|
||||
select: { version: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { totalDownloads: "desc" },
|
||||
take: 6,
|
||||
}),
|
||||
prisma.addon.aggregate({
|
||||
_sum: { totalDownloads: true },
|
||||
}),
|
||||
prisma.software.findUnique({
|
||||
where: { slug: "nanami-launcher" },
|
||||
include: {
|
||||
versions: {
|
||||
where: { isLatest: true },
|
||||
take: 1,
|
||||
orderBy: { totalDownloads: "desc" },
|
||||
take: 6,
|
||||
}),
|
||||
prisma.software.findUnique({
|
||||
where: { slug: "nanami-launcher" },
|
||||
include: {
|
||||
versions: {
|
||||
where: { isLatest: true },
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}),
|
||||
prisma.softwareVersion.aggregate({
|
||||
where: { software: { slug: "nanami-launcher" } },
|
||||
_sum: { downloadCount: true },
|
||||
}),
|
||||
prisma.bannerImage.findMany({
|
||||
where: { enabled: true },
|
||||
orderBy: { sortOrder: "asc" },
|
||||
select: { imageUrl: true },
|
||||
}),
|
||||
prisma.galleryImage.findMany({
|
||||
where: { enabled: true },
|
||||
orderBy: { sortOrder: "asc" },
|
||||
select: { imageUrl: true, title: true },
|
||||
}),
|
||||
prisma.article.findMany({
|
||||
where: { published: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 3,
|
||||
}),
|
||||
]);
|
||||
|
||||
const launcherVersion = launcher?.versions[0]?.version ?? null;
|
||||
const totalDownloads = launcherDownloads._sum.downloadCount ?? 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeroBanner
|
||||
totalDownloads={totalDownloads._sum.totalDownloads ?? undefined}
|
||||
totalDownloads={totalDownloads || undefined}
|
||||
launcherVersion={launcherVersion}
|
||||
banners={banners}
|
||||
/>
|
||||
|
||||
<GameGallery items={galleryImages} />
|
||||
|
||||
{/* Features */}
|
||||
<section className="relative border-t border-amber-900/20 bg-gradient-to-b from-[#0d0b15] to-[#110f1a] dark:from-[#0d0b15] dark:to-[#110f1a]">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_center,rgba(168,85,247,0.06)_0%,transparent_70%)]" />
|
||||
<div className="relative mx-auto max-w-6xl px-4 py-16">
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
<div className="relative mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
|
||||
<div className="grid gap-4 sm:gap-8 md:grid-cols-3">
|
||||
<div className="flex flex-col items-center rounded-xl border border-amber-500/10 bg-white/5 p-6 text-center backdrop-blur transition-colors hover:border-amber-500/25">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-amber-500/10">
|
||||
<Sparkles className="h-6 w-6 text-amber-400" />
|
||||
@@ -83,11 +105,62 @@ export default async function HomePage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Latest Articles */}
|
||||
{latestArticles.length > 0 && (
|
||||
<section className="border-t border-amber-900/20 bg-gradient-to-b from-[#110f1a] to-[#0d0b15] dark:from-[#110f1a] dark:to-[#0d0b15]">
|
||||
<div className="mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
|
||||
<div className="mb-6 flex items-center justify-between sm:mb-8">
|
||||
<h2 className="text-2xl font-bold text-amber-100">最新公告</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-amber-500/20 text-amber-200 hover:border-amber-500/40 hover:bg-amber-500/10 hover:text-amber-100"
|
||||
render={<Link href="/articles" />}
|
||||
>
|
||||
查看更多
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:gap-6 md:grid-cols-3">
|
||||
{latestArticles.map((article) => (
|
||||
<Link
|
||||
key={article.id}
|
||||
href={`/articles/${article.slug}`}
|
||||
className="group overflow-hidden rounded-xl border border-amber-500/10 bg-white/[0.03] transition-colors hover:border-amber-500/25"
|
||||
>
|
||||
{article.coverImage && (
|
||||
<div className="aspect-[16/9] overflow-hidden">
|
||||
<img
|
||||
src={article.coverImage}
|
||||
alt={article.title}
|
||||
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4">
|
||||
<h3 className="mb-1.5 font-semibold text-amber-100 group-hover:text-amber-200 line-clamp-1">
|
||||
{article.title}
|
||||
</h3>
|
||||
{article.summary && (
|
||||
<p className="mb-2 line-clamp-2 text-sm text-gray-400">
|
||||
{article.summary}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5 text-xs text-gray-500">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{new Date(article.createdAt).toLocaleDateString("zh-CN")}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Featured Addons */}
|
||||
{featuredAddons.length > 0 && (
|
||||
<section className="border-t border-amber-900/20 bg-gradient-to-b from-[#110f1a] to-[#0d0b15] dark:from-[#110f1a] dark:to-[#0d0b15]">
|
||||
<div className="mx-auto max-w-6xl px-4 py-16">
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div className="mx-auto max-w-6xl px-3 py-10 sm:px-4 sm:py-16">
|
||||
<div className="mb-6 flex items-center justify-between sm:mb-8">
|
||||
<h2 className="text-2xl font-bold text-amber-100">热门插件</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
Reference in New Issue
Block a user