feat: scaffold headless frontend integration per design doc

This commit is contained in:
2026-08-06 15:51:59 +08:00
parent b0919bf8b5
commit 7e30631c68
28 changed files with 708 additions and 126 deletions
+35
View File
@@ -0,0 +1,35 @@
import { notFound } from "next/navigation";
import type { Metadata } from "next";
import { getBlogBySlug } from "@/services/blog.service";
import { BlockRenderer } from "@/blocks/BlockRenderer";
interface BlogDetailPageProps {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({
params,
}: BlogDetailPageProps): Promise<Metadata> {
const { slug } = await params;
const post = await getBlogBySlug(slug).catch(() => null);
return { title: post?.title ?? "文章未找到" };
}
export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
const { slug } = await params;
const post = await getBlogBySlug(slug).catch(() => null);
if (!post) {
notFound();
}
return (
<article className="mx-auto max-w-3xl px-6 py-24">
<h1 className="mb-4 text-4xl font-bold text-slate-900">{post.title}</h1>
{post.published_at && (
<p className="mb-12 text-sm text-slate-500">{post.published_at}</p>
)}
<BlockRenderer blocks={post.body} />
</article>
);
}
+33
View File
@@ -0,0 +1,33 @@
import Link from "next/link";
import type { Metadata } from "next";
import { getBlogList } from "@/services/blog.service";
export const metadata: Metadata = {
title: "技术博客",
};
export default async function BlogListPage() {
const posts = await getBlogList().catch(() => []);
return (
<div className="mx-auto max-w-4xl px-6 py-24">
<h1 className="mb-12 text-4xl font-bold text-slate-900"></h1>
<ul className="space-y-6">
{posts.map((post) => (
<li key={post.id} className="border-b border-slate-100 pb-6">
<Link
href={`/blog/${post.meta.slug}`}
className="text-2xl font-semibold text-slate-900 hover:text-primary"
>
{post.title}
</Link>
{post.intro && <p className="mt-2 text-slate-600">{post.intro}</p>}
</li>
))}
{posts.length === 0 && (
<p className="text-slate-500"></p>
)}
</ul>
</div>
);
}