官网 初版

This commit is contained in:
rucky
2026-03-18 17:13:27 +08:00
parent 879c4bdfc8
commit 241a76caeb
95 changed files with 8889 additions and 113 deletions

View File

@@ -0,0 +1,133 @@
import Link from "next/link";
import { prisma } from "@/lib/db";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Plus } from "lucide-react";
import { SoftwareVersionTable } from "@/components/admin/SoftwareVersionTable";
const SOFTWARE_DEFS = [
{
slug: "nanami-launcher",
name: "Nanami 启动器(全量包)",
description: "Nanami 插件启动器完整安装包,用户首次下载或大版本更新",
badgeLabel: "全量",
},
{
slug: "nanami-launcher-patch",
name: "Nanami 热更新包",
description: "app.asar 热更新包,客户端静默下载替换实现热更新",
badgeLabel: "热更新",
},
];
async function ensureSoftware(slug: string, name: string, description: string) {
let sw = await prisma.software.findUnique({ where: { slug } });
if (!sw) {
sw = await prisma.software.create({ data: { name, slug, description } });
}
return sw;
}
export const dynamic = "force-dynamic";
export default async function AdminSoftwarePage() {
const softwareItems = await Promise.all(
SOFTWARE_DEFS.map(async (def) => {
const sw = await ensureSoftware(def.slug, def.name, def.description);
const versions = await prisma.softwareVersion.findMany({
where: { softwareId: sw.id },
orderBy: { versionCode: "desc" },
});
const totalDownloads = versions.reduce((s, v) => s + v.downloadCount, 0);
const latestVersion = versions.find((v) => v.isLatest);
return { ...def, sw, versions, totalDownloads, latestVersion };
})
);
return (
<div className="space-y-10">
<div>
<h1 className="text-3xl font-bold"></h1>
<p className="mt-1 text-muted-foreground">
slug
</p>
</div>
{softwareItems.map((item) => {
const serializedVersions = item.versions.map((v) => ({
...v,
createdAt: v.createdAt.toISOString(),
}));
return (
<div key={item.slug} className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<h2 className="text-xl font-semibold">{item.name}</h2>
<span className="rounded-md bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
slug: {item.slug}
</span>
</div>
<Button
render={
<Link
href={`/admin/software/${item.sw.id}/versions/new`}
/>
}
>
<Plus className="mr-2 h-4 w-4" />
</Button>
</div>
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="pb-2">
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">
{item.latestVersion
? `v${item.latestVersion.version}`
: "未发布"}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{item.versions.length}</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{item.totalDownloads}</p>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="p-0">
<SoftwareVersionTable versions={serializedVersions} />
</CardContent>
</Card>
</div>
);
})}
</div>
);
}