feat: 接入预览模式(Next.js Draft Mode)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { draftMode, cookies } from "next/headers";
|
||||
import { fetchAPI } from "./api-client";
|
||||
import type { WagtailPageSummary } from "@/types/wagtail";
|
||||
|
||||
/**
|
||||
* Wagtail 预览模式(wagtail_headless_preview + Next.js Draft Mode)。
|
||||
* 详见 documents/设计方案分析与完善版.md §2.16,后端实现见 apps/api/urls.py
|
||||
* 的 PagePreviewAPIViewSet(挂载在 /api/v2/page_preview/)。
|
||||
*/
|
||||
|
||||
export const PREVIEW_CONTENT_TYPE_COOKIE = "wagtail_preview_content_type";
|
||||
export const PREVIEW_TOKEN_COOKIE = "wagtail_preview_token";
|
||||
|
||||
/** content_type(`app_label.model`,小写)→ 对应前端路由的解析函数 */
|
||||
const PREVIEW_PATH_RESOLVERS: Record<
|
||||
string,
|
||||
(page: WagtailPageSummary) => string
|
||||
> = {
|
||||
"home.homepage": () => "/",
|
||||
"blog.blogpage": (page) => `/blog/${page.meta.slug}`,
|
||||
"products.productpage": (page) => `/products/${page.meta.slug}`,
|
||||
"cases.casestudypage": (page) => `/cases/${page.meta.slug}`,
|
||||
"solutions.solutionpage": (page) => `/solutions/${page.meta.slug}`,
|
||||
};
|
||||
|
||||
/** 根据 content_type 解析预览内容应跳转到的前端路径,未知类型返回 null。 */
|
||||
export function resolvePreviewPath(
|
||||
contentType: string,
|
||||
page: WagtailPageSummary,
|
||||
): string | null {
|
||||
const resolver = PREVIEW_PATH_RESOLVERS[contentType];
|
||||
return resolver ? resolver(page) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取草稿页面数据。`fields` 省略时仅返回 Wagtail API 默认字段(含 meta.slug/meta.type),
|
||||
* 用于 /preview 路由校验 token 是否有效。
|
||||
*/
|
||||
export async function fetchPreviewPage<T extends WagtailPageSummary>(
|
||||
contentType: string,
|
||||
token: string,
|
||||
fields?: string,
|
||||
): Promise<T | null> {
|
||||
const query = new URLSearchParams({ content_type: contentType, token });
|
||||
if (fields) {
|
||||
query.set("fields", fields);
|
||||
}
|
||||
try {
|
||||
return await fetchAPI<T>(`/api/v2/page_preview/?${query.toString()}`, {
|
||||
revalidate: 0,
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在页面组件中调用:若 Draft Mode 已开启且 cookie 中记录的 content_type
|
||||
* 与当前页面类型一致,返回草稿数据;否则回退到 `fallback()`(正常已发布内容)。
|
||||
*/
|
||||
export async function getPreviewOrFallback<T extends WagtailPageSummary>(
|
||||
expectedContentType: string,
|
||||
fields: string,
|
||||
fallback: () => Promise<T | null>,
|
||||
): Promise<T | null> {
|
||||
const { isEnabled } = await draftMode();
|
||||
if (!isEnabled) {
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const contentType = cookieStore.get(PREVIEW_CONTENT_TYPE_COOKIE)?.value;
|
||||
const token = cookieStore.get(PREVIEW_TOKEN_COOKIE)?.value;
|
||||
|
||||
if (contentType !== expectedContentType || !token) {
|
||||
return fallback();
|
||||
}
|
||||
|
||||
const preview = await fetchPreviewPage<T>(expectedContentType, token, fields);
|
||||
return preview ?? fallback();
|
||||
}
|
||||
Reference in New Issue
Block a user