feat: scaffold headless frontend integration per design doc
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Wagtail API v2 请求客户端。
|
||||
* 详见 documents/设计方案分析与完善版.md §2.7 Headless API 设计规范。
|
||||
*/
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public status: number,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
||||
interface FetchOptions {
|
||||
/** ISR 重新验证秒数,默认 60s。传 0 表示不缓存(用于预览)。 */
|
||||
revalidate?: number;
|
||||
/** 关联的缓存标签,供 revalidateTag 精准失效使用。 */
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export async function fetchAPI<T>(
|
||||
path: string,
|
||||
{ revalidate = 60, tags }: FetchOptions = {},
|
||||
): Promise<T> {
|
||||
const res = await fetch(`${API_BASE_URL}${path}`, {
|
||||
next: { revalidate, tags },
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new ApiError(`API request failed: ${path}`, res.status);
|
||||
}
|
||||
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { fetchAPI } from "./api-client";
|
||||
import type {
|
||||
BlogPageDetail,
|
||||
BlogPageSummary,
|
||||
WagtailListResponse,
|
||||
} from "@/types/wagtail";
|
||||
|
||||
/**
|
||||
* 获取博客列表(按发布时间倒序)。
|
||||
*/
|
||||
export async function getBlogList(): Promise<BlogPageSummary[]> {
|
||||
const res = await fetchAPI<WagtailListResponse<BlogPageSummary>>(
|
||||
"/api/v2/pages/?type=blog.BlogPage&fields=intro,published_at&order=-published_at",
|
||||
{ tags: ["blog-list"] },
|
||||
);
|
||||
return res.items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 slug 获取博客详情。
|
||||
*/
|
||||
export async function getBlogBySlug(slug: string): Promise<BlogPageDetail | null> {
|
||||
const res = await fetchAPI<WagtailListResponse<BlogPageDetail>>(
|
||||
`/api/v2/pages/?type=blog.BlogPage&slug=${encodeURIComponent(slug)}&fields=body,intro,published_at&limit=1`,
|
||||
{ tags: [`blog:${slug}`] },
|
||||
);
|
||||
return res.items[0] ?? null;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { fetchAPI } from "./api-client";
|
||||
import type { HomePageDetail, WagtailListResponse } from "@/types/wagtail";
|
||||
|
||||
/**
|
||||
* 获取首页内容(取页面树中第一条 home.HomePage)。
|
||||
*/
|
||||
export async function getHomePage(): Promise<HomePageDetail | null> {
|
||||
const res = await fetchAPI<WagtailListResponse<HomePageDetail>>(
|
||||
"/api/v2/pages/?type=home.HomePage&fields=body,seo_title_override,seo_description_override&limit=1",
|
||||
{ tags: ["home-page"] },
|
||||
);
|
||||
return res.items[0] ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user