149 lines
6.0 KiB
TypeScript
149 lines
6.0 KiB
TypeScript
import Link from "next/link";
|
||
import { BookOpen, Calendar, PlayCircle } from "lucide-react";
|
||
import { MarketingShell } from "@/components/lovart/marketing-shell";
|
||
import { Reveal } from "@/components/lovart/reveal";
|
||
import {
|
||
getNavLinks,
|
||
getPublicFeaturedImages,
|
||
getSiteSettings,
|
||
getTutorialVideos,
|
||
} from "@/lib/db/queries";
|
||
|
||
function formatDate(value: Date | null | undefined) {
|
||
if (!value) {
|
||
return "最近更新";
|
||
}
|
||
|
||
return new Intl.DateTimeFormat("zh-CN", {
|
||
year: "numeric",
|
||
month: "2-digit",
|
||
day: "2-digit",
|
||
}).format(new Date(value));
|
||
}
|
||
|
||
export default async function LovartBlogPage() {
|
||
const [settings, navLinks, videos, featuredImages] = await Promise.all([
|
||
getSiteSettings(),
|
||
getNavLinks(),
|
||
getTutorialVideos(),
|
||
getPublicFeaturedImages(8),
|
||
]);
|
||
|
||
const [headline, ...restVideos] = videos;
|
||
|
||
return (
|
||
<MarketingShell
|
||
active="blog"
|
||
bannerText={settings.bannerEnabled ? settings.bannerText : null}
|
||
navLinks={navLinks}
|
||
siteLogo={settings.siteLogo || settings.siteLogoDark}
|
||
siteName={settings.siteName || "AI Chatbot"}
|
||
stats={[
|
||
{ label: "文章/教程", value: String(videos.length) },
|
||
{ label: "精选灵感", value: String(featuredImages.length) },
|
||
]}
|
||
>
|
||
<Reveal>
|
||
<section className="rounded-3xl border border-white/15 bg-white/[0.04] p-6 sm:p-10">
|
||
<p className="inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/5 px-3 py-1 text-xs text-zinc-200">
|
||
<BookOpen className="h-3.5 w-3.5 text-cyan-300" />
|
||
Design Blog
|
||
</p>
|
||
<h1 className="mt-4 text-3xl font-semibold text-white sm:text-5xl">Lovart 博客</h1>
|
||
<p className="mt-4 max-w-2xl text-sm text-zinc-300 sm:text-base">
|
||
这里聚合后台教程与案例内容,可作为对外 Blog 页持续运营更新。
|
||
</p>
|
||
</section>
|
||
</Reveal>
|
||
|
||
<Reveal delay={0.06}>
|
||
<section className="mt-8 grid gap-4 lg:grid-cols-[1.2fr_1fr]">
|
||
<article className="rounded-2xl border border-white/15 bg-black/30 p-5">
|
||
<div className="mb-3 inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/[0.03] px-2.5 py-1 text-xs text-zinc-300">
|
||
<Calendar className="h-3.5 w-3.5" />
|
||
{formatDate(headline?.updatedAt || headline?.createdAt)}
|
||
</div>
|
||
<h2 className="text-2xl font-semibold text-white">{headline?.title || "等待发布首篇内容"}</h2>
|
||
<p className="mt-3 text-sm leading-6 text-zinc-300">
|
||
{headline?.description || "可在后台维护教程标题、描述与链接,自动同步到博客页。"}
|
||
</p>
|
||
{headline ? (
|
||
<a
|
||
href={headline.videoUrl}
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
className="mt-5 inline-flex items-center gap-2 rounded-xl bg-white px-4 py-2 text-sm font-semibold text-zinc-900 hover:bg-zinc-200"
|
||
>
|
||
<PlayCircle className="h-4 w-4" />
|
||
阅读/观看
|
||
</a>
|
||
) : null}
|
||
</article>
|
||
|
||
<article className="rounded-2xl border border-white/15 bg-black/30 p-5">
|
||
<h3 className="mb-3 text-lg font-semibold text-white">灵感墙</h3>
|
||
{featuredImages.length > 0 ? (
|
||
<div className="grid grid-cols-3 gap-2">
|
||
{featuredImages.slice(0, 6).map((image) => (
|
||
<img
|
||
key={image.id}
|
||
src={image.thumbnailUrl || image.originalUrl}
|
||
alt={image.prompt || "featured"}
|
||
className="h-20 w-full rounded-lg object-cover"
|
||
/>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-zinc-300">暂无灵感素材</p>
|
||
)}
|
||
</article>
|
||
</section>
|
||
</Reveal>
|
||
|
||
<Reveal delay={0.12}>
|
||
<section className="mt-8">
|
||
<h2 className="mb-4 text-xl font-semibold text-white">全部文章</h2>
|
||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||
{restVideos.length > 0 ? (
|
||
restVideos.map((video) => (
|
||
<article key={video.id} className="rounded-2xl border border-white/15 bg-black/30 p-4">
|
||
<p className="text-xs text-zinc-400">{formatDate(video.updatedAt || video.createdAt)}</p>
|
||
<p className="mt-2 line-clamp-2 text-base font-medium text-zinc-100">{video.title}</p>
|
||
<p className="mt-2 line-clamp-3 text-sm text-zinc-300">{video.description || "暂无描述"}</p>
|
||
<a
|
||
href={video.videoUrl}
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
className="mt-4 inline-flex items-center gap-2 text-sm text-cyan-300 hover:text-cyan-200"
|
||
>
|
||
<PlayCircle className="h-4 w-4" />
|
||
查看详情
|
||
</a>
|
||
</article>
|
||
))
|
||
) : (
|
||
<div className="col-span-full rounded-2xl border border-dashed border-white/20 bg-white/[0.03] p-8 text-sm text-zinc-300">
|
||
暂无更多文章内容。
|
||
</div>
|
||
)}
|
||
</div>
|
||
</section>
|
||
</Reveal>
|
||
|
||
<Reveal delay={0.18}>
|
||
<section className="mt-10 rounded-2xl border border-white/15 bg-black/25 p-5">
|
||
<h2 className="text-lg font-semibold text-white">继续联调</h2>
|
||
<div className="mt-3 flex flex-wrap gap-3 text-sm text-zinc-300">
|
||
<Link className="rounded-lg border border-white/20 px-3 py-1.5 hover:border-white/40" href="/lovart/changelog">
|
||
查看更新日志
|
||
</Link>
|
||
<Link className="rounded-lg border border-white/20 px-3 py-1.5 hover:border-white/40" href="/admin/settings">
|
||
后台更新内容
|
||
</Link>
|
||
</div>
|
||
</section>
|
||
</Reveal>
|
||
</MarketingShell>
|
||
);
|
||
}
|