/** * CMS 服务端数据访问层 * 从 Prisma 数据库直接读取已发布内容,供页面组件使用 */ import { prisma } from '@/lib/db'; import { cache } from 'react'; import type { ContentItem, ContentStatus } from './types'; // ============ 类型转换 ============ function toContentItem(item: Record): ContentItem { return { id: item.id as string, modelId: item.modelId as string, modelCode: item.modelCode as string, title: item.title as string, slug: (item.slug as string | null) ?? undefined, locale: (item.locale as string | null) ?? 'zh-CN', status: item.status as ContentStatus, data: JSON.parse(item.data as string), version: item.version as number, sortOrder: (item.sortOrder as number | null) ?? undefined, publishedAt: (item.publishedAt as Date | null)?.toISOString(), createdBy: item.createdBy as string, updatedBy: item.updatedBy as string, createdAt: (item.createdAt as Date).toISOString(), updatedAt: (item.updatedAt as Date).toISOString(), }; } // ============ 内容条目查询 ============ /** * 获取指定模型的已发布内容列表 */ export const getPublishedItems = cache(async (modelCode: string): Promise => { const items = await prisma.contentItem.findMany({ where: { modelCode, status: 'published' }, orderBy: { sortOrder: 'asc' }, }); return items.map((item) => toContentItem(item as unknown as Record)); }); /** * 根据 slug 获取单条已发布内容 */ export const getPublishedItemBySlug = cache(async (modelCode: string, slug: string): Promise => { const item = await prisma.contentItem.findFirst({ where: { modelCode, slug, status: 'published' }, }); if (!item) return null; return toContentItem(item as unknown as Record); }); /** * 根据 ID 获取单条内容(不限状态) */ export const getItemById = cache(async (id: string): Promise => { const item = await prisma.contentItem.findUnique({ where: { id } }); if (!item) return null; return toContentItem(item as unknown as Record); }); // ============ 内容区域查询 ============ /** * 获取指定页面的内容区域配置 */ export const getPageZones = cache(async (pageCode: string) => { const zones = await prisma.contentZone.findMany({ where: { pageCode }, }); return zones.map((z) => ({ ...z, allowedModels: JSON.parse(z.allowedModels), items: JSON.parse(z.items), settings: JSON.parse(z.settings), })); }); /** * 获取单个内容区域 */ export const getZone = cache(async (code: string) => { const zone = await prisma.contentZone.findUnique({ where: { code } }); if (!zone) return null; return { ...zone, allowedModels: JSON.parse(zone.allowedModels), items: JSON.parse(zone.items), settings: JSON.parse(zone.settings), }; }); // ============ 内容模型查询 ============ /** * 获取所有内容模型 */ export const getContentModels = cache(async () => { const models = await prisma.contentModel.findMany({ orderBy: { sortOrder: 'asc' }, }); return models.map((m) => ({ ...m, fields: JSON.parse(m.fields), })); }); // ============ 便捷函数:按业务类型获取 ============ export async function getCases() { return getPublishedItems('case-study'); } export async function getNews() { return getPublishedItems('news'); } export async function getServices() { return getPublishedItems('service'); } export async function getProducts() { return getPublishedItems('product'); } export async function getSolutions() { return getPublishedItems('solution'); } export async function getStats() { return getPublishedItems('stat-item'); } export async function getHeroBanners() { return getPublishedItems('hero-banner'); } export async function getCaseBySlug(slug: string) { return getPublishedItemBySlug('case-study', slug); } export async function getNewsBySlug(slug: string) { return getPublishedItemBySlug('news', slug); } export async function getServiceBySlug(slug: string) { return getPublishedItemBySlug('service', slug); } export async function getProductBySlug(slug: string) { return getPublishedItemBySlug('product', slug); } export async function getSolutionBySlug(slug: string) { return getPublishedItemBySlug('solution', slug); } /** * 获取指定页面的结构性文案配置(page-copy 模型,按页面 slug) */ export async function getPageCopy(pageCode: string) { return getPublishedItemBySlug('page-copy', pageCode); } /** * 获取首页结构性文案配置(章节标题/信任信号/叙事情节/CTA 的 CMS 覆盖) */ export async function getHomePageCopy() { return getPageCopy('home'); } export async function getHomePageZones() { return getPageZones('home'); } /** * 获取首页各 Zone 并解析引用的已发布内容条目 * 返回以 zoneKey 为键的内容条目数组映射 */ export async function getResolvedHomeZones(): Promise> { const zones = await getHomePageZones(); const result: Record = {}; if (zones.length === 0) { return result; } const itemIds = zones .flatMap((z) => z.items.map((i: { itemId?: string }) => i.itemId).filter(Boolean)); if (itemIds.length > 0) { const items = await prisma.contentItem.findMany({ where: { id: { in: itemIds as string[] }, status: 'published' }, }); const itemMap = new Map( items.map((item) => [item.id, toContentItem(item as unknown as Record)]), ); for (const zone of zones) { const key = zone.zoneKey || zone.code; result[key] = zone.items .map((zi: { itemId?: string }) => (zi.itemId ? itemMap.get(zi.itemId) : undefined)) .filter(Boolean) as ContentItem[]; } } return result; } // ============ 静态生成辅助 ============ /** * 获取所有已发布内容的 slug 列表(用于 generateStaticParams) */ export async function getAllPublishedSlugs(modelCode: string) { const items = await prisma.contentItem.findMany({ where: { modelCode, status: 'published' }, select: { slug: true }, }); return items.filter((i) => i.slug).map((i) => ({ slug: i.slug })); }