新增内容引擎文章发布接口与详情页

This commit is contained in:
amb
2026-09-11 17:19:09 +08:00
parent ba6aab722e
commit 726485dccb
16 changed files with 958 additions and 102 deletions
@@ -0,0 +1,75 @@
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { articleUrl, getPublishedArticle } from '@/lib/articles';
const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || process.env.PUBLIC_SITE_URL || 'https://www.aiameiba.com').replace(/\/$/, '');
const formatDate = (value: string) => {
const date = new Date(value);
return Number.isNaN(date.getTime()) ? value : date.toLocaleDateString('zh-CN');
};
const paragraphs = (body: string) => body.split(/\n\s*\n/).map(item => item.trim()).filter(Boolean);
export async function generateMetadata({ params }: { params: { identifier: string } }): Promise<Metadata> {
const article = await getPublishedArticle(params.identifier);
if (!article) return { title: '文章不存在' };
const localUrl = `${siteUrl}${articleUrl(article)}`;
const canonical = article.canonical_url || localUrl;
return {
title: article.title,
description: article.summary,
alternates: { canonical },
openGraph: {
type: 'article',
title: article.title,
description: article.summary,
url: canonical,
images: article.cover_image_url ? [{ url: article.cover_image_url }] : undefined,
publishedTime: article.published_at,
modifiedTime: article.updated_at,
authors: [article.author_name],
},
};
}
export default async function KnowledgeArticlePage({ params }: { params: { identifier: string } }) {
const article = await getPublishedArticle(params.identifier);
if (!article) notFound();
const localUrl = `${siteUrl}${articleUrl(article)}`;
const canonical = article.canonical_url || localUrl;
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: article.title,
description: article.summary,
url: canonical,
mainEntityOfPage: localUrl,
author: { '@type': 'Organization', name: article.author_name },
publisher: { '@type': 'Organization', name: '阿美巴AI', url: siteUrl },
datePublished: article.published_at,
dateModified: article.updated_at,
image: article.cover_image_url || undefined,
articleSection: article.category,
};
return (
<div className="container-narrow">
<nav className="breadcrumb" aria-label="面包屑导航">
<ol><li><a href="/">首页</a></li><li><a href="/knowledge">知识中心</a></li><li><span aria-current="page">{article.title}</span></li></ol>
</nav>
<article className="knowledge-article">
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<header className="knowledge-article-header">
<p className="article-meta"><span>{article.category}</span><span>{article.author_name}</span><time dateTime={article.published_at}>发布于 {formatDate(article.published_at)}</time><time dateTime={article.updated_at}>更新于 {formatDate(article.updated_at)}</time></p>
<h1>{article.title}</h1>
{article.summary && <p className="knowledge-article-summary">{article.summary}</p>}
</header>
<div className="knowledge-article-body">
{paragraphs(article.body_plain).map((paragraph, index) => <p key={index}>{paragraph}</p>)}
</div>
<footer className="knowledge-article-footer"><a href="/knowledge">← 返回知识中心</a></footer>
</article>
</div>
);
}