HaoAws/app/news/[id]/page.tsx
2025-09-16 16:37:48 +08:00

297 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { notFound } from 'next/navigation';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { getTranslations, Locale, getNavigationPaths, defaultLocale } from '@/lib/i18n';
import { generateMetadata as generateSEOMetadata } from '@/lib/seo';
import { getNewsArticle, getAvailableLocalesForArticle, checkArticleExists } from '@/lib/markdown';
import Layout from '@/components/Layout';
import LanguageSwitcher from '@/components/LanguageSwitcher';
import Link from 'next/link';
interface NewsDetailPageProps {
params: {
id: string;
};
}
export async function generateStaticParams() {
// 默认语言的文章ID
const articles = ['ai-transformation-2024', 'cloud-security-best-practices', 'company-expansion-2024'];
return articles.map((id) => ({ id }));
}
export async function generateMetadata({ params }: NewsDetailPageProps) {
const { id } = params;
const locale = defaultLocale; // 使用默认语言
// Check if article exists
if (!checkArticleExists(id, locale)) {
return {
title: 'Article Not Found',
description: 'The requested article could not be found.',
};
}
const article = await getNewsArticle(id, locale);
if (!article) {
return {
title: 'Article Not Found',
description: 'The requested article could not be found.',
};
}
return {
title: article.title,
description: article.excerpt,
openGraph: {
title: article.title,
description: article.excerpt,
type: 'article',
publishedTime: article.date,
authors: [article.author],
tags: article.tags,
},
};
}
export default async function NewsDetailPage({ params }: NewsDetailPageProps) {
const { id } = params;
const locale = defaultLocale; // 使用默认语言
// Check if article exists in the requested locale
if (!checkArticleExists(id, locale)) {
notFound();
}
const article = await getNewsArticle(id, locale);
if (!article) {
notFound();
}
const [common, availableLocales] = await Promise.all([
getTranslations(locale, 'common'),
getAvailableLocalesForArticle(id),
]);
const navigationPaths = getNavigationPaths(locale);
const navigation = [
{
name: common.navigation.home,
href: navigationPaths.find((p) => p.key === 'home')?.path || '/',
},
{
name: common.navigation.products,
href: navigationPaths.find((p) => p.key === 'products')?.path || '/products',
},
{
name: common.navigation.news,
href: navigationPaths.find((p) => p.key === 'news')?.path || '/news',
},
{
name: common.navigation.support,
href: navigationPaths.find((p) => p.key === 'support')?.path || '/support',
},
{
name: common.navigation.about,
href: navigationPaths.find((p) => p.key === 'about')?.path || '/about',
},
];
return (
<Layout locale={locale} navigation={navigation} common={common}>
{/* Breadcrumb */}
<section className="py-4 px-4 sm:px-6 lg:px-8 bg-gray-50 border-b">
<div className="max-w-4xl mx-auto">
<nav className="flex items-center space-x-2 text-sm text-gray-600">
<Link
href={navigationPaths.find((p) => p.key === 'home')?.path || '/'}
className="hover:text-blue-600 transition-colors"
>
{common.navigation.home}
</Link>
<span>/</span>
<Link
href={navigationPaths.find((p) => p.key === 'news')?.path || '/news'}
className="hover:text-blue-600 transition-colors"
>
{common.navigation.news}
</Link>
<span>/</span>
<span className="text-gray-900">{article.title}</span>
</nav>
</div>
</section>
{/* Language Switcher */}
<section className="py-4 px-4 sm:px-6 lg:px-8 bg-white border-b">
<div className="max-w-4xl mx-auto">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-sm text-gray-600"></span>
<LanguageSwitcher
currentLocale={locale}
availableLocales={availableLocales}
articleId={id}
/>
</div>
{/* Back to News */}
<Link
href={navigationPaths.find((p) => p.key === 'news')?.path || '/news'}
className="text-sm text-blue-600 hover:text-blue-800 flex items-center space-x-1"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
<span></span>
</Link>
</div>
</div>
</section>
{/* Article Header */}
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-white">
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<div className="flex items-center space-x-4 mb-6">
<span className="px-3 py-1 bg-blue-100 text-blue-800 text-sm rounded-full font-medium">
{article.category}
</span>
{article.featured && (
<span className="px-3 py-1 bg-red-100 text-red-800 text-sm rounded-full font-medium">
</span>
)}
</div>
<h1 className="text-4xl md:text-5xl font-light leading-tight mb-6 text-gray-900">
{article.title}
</h1>
<div className="flex flex-wrap items-center gap-6 text-gray-600 mb-6">
<div className="flex items-center space-x-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>{article.date}</span>
</div>
<div className="flex items-center space-x-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
<span>{article.author}</span>
</div>
<div className="flex items-center space-x-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{article.readTime}</span>
</div>
</div>
<p className="text-xl text-gray-600 font-light leading-relaxed">
{article.excerpt}
</p>
</div>
{/* Tags */}
{article.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-8">
{article.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 bg-gray-100 text-gray-700 text-sm rounded-full"
>
#{tag}
</span>
))}
</div>
)}
</div>
</section>
{/* Article Content */}
<section className="py-12 px-4 sm:px-6 lg:px-8 bg-white">
<div className="max-w-4xl mx-auto">
<div className="prose prose-lg prose-gray max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => (
<h1 className="text-3xl font-light mb-6 text-gray-900 border-b border-gray-200 pb-4">
{children}
</h1>
),
h2: ({ children }) => (
<h2 className="text-2xl font-light mb-4 text-gray-900 mt-8">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="text-xl font-light mb-3 text-gray-900 mt-6">
{children}
</h3>
),
p: ({ children }) => (
<p className="mb-4 text-gray-700 leading-relaxed">
{children}
</p>
),
ul: ({ children }) => (
<ul className="mb-4 pl-6 space-y-2">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="mb-4 pl-6 space-y-2 list-decimal">
{children}
</ol>
),
li: ({ children }) => (
<li className="text-gray-700 leading-relaxed">
{children}
</li>
),
blockquote: ({ children }) => (
<blockquote className="border-l-4 border-blue-600 pl-6 my-6 italic text-gray-600">
{children}
</blockquote>
),
code: ({ children }) => (
<code className="bg-gray-100 px-2 py-1 rounded text-sm font-mono">
{children}
</code>
),
pre: ({ children }) => (
<pre className="bg-gray-100 p-4 rounded-lg overflow-x-auto mb-4">
{children}
</pre>
),
}}
>
{article.content}
</ReactMarkdown>
</div>
</div>
</section>
{/* Related Articles */}
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-gray-50">
<div className="max-w-4xl mx-auto">
<h2 className="text-2xl font-light mb-8 text-gray-900"></h2>
<div className="grid md:grid-cols-2 gap-8">
{/* 这里可以添加相关文章的逻辑 */}
<div className="bg-white rounded-lg p-6 shadow-sm">
<h3 className="text-lg font-light mb-2 text-gray-900"></h3>
<p className="text-gray-600 text-sm">
</p>
</div>
</div>
</div>
</section>
</Layout>
);
}