lovart3/app/lovart/blog/page.tsx
Your Name f6b7758ec9
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: extend lovart rebuild with blog and changelog experiences
2026-02-12 12:11:47 +08:00

149 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}