183 lines
7.9 KiB
TypeScript
183 lines
7.9 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useEffect, useState } from 'react';
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { Navigation } from '../../../components/Navigation';
|
||
|
|
import { Footer } from '../../../components/Footer';
|
||
|
|
import { Head } from '../../../components/Head';
|
||
|
|
import { Calendar, User, Clock, ArrowLeft } from 'lucide-react';
|
||
|
|
import ReactMarkdown from 'react-markdown';
|
||
|
|
import remarkGfm from 'remark-gfm';
|
||
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||
|
|
import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||
|
|
|
||
|
|
type Lang = 'zh' | 'zh-tw' | 'en';
|
||
|
|
|
||
|
|
interface ArticleData {
|
||
|
|
content: string;
|
||
|
|
meta: {
|
||
|
|
title: string;
|
||
|
|
description: string;
|
||
|
|
author: string;
|
||
|
|
date: string;
|
||
|
|
readTime: number;
|
||
|
|
category: string;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ArticleClientProps {
|
||
|
|
slug: string;
|
||
|
|
initialData: ArticleData;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ArticleClient({ slug, initialData }: ArticleClientProps) {
|
||
|
|
const [currentLang, setCurrentLang] = useState<Lang>('zh');
|
||
|
|
const [content, setContent] = useState(initialData.content);
|
||
|
|
const [meta, setMeta] = useState(initialData.meta);
|
||
|
|
|
||
|
|
const translations = {
|
||
|
|
zh: {
|
||
|
|
nav: { home: '首页', products: '产品', solutions: '解决方案', support: '支持', news: '资讯', contact: '联系我们' },
|
||
|
|
backToNews: '返回资讯列表',
|
||
|
|
minutes: '分钟',
|
||
|
|
articleNotFound: '文章未找到',
|
||
|
|
backToList: '返回资讯列表',
|
||
|
|
},
|
||
|
|
'zh-tw': {
|
||
|
|
nav: { home: '首頁', products: '產品', solutions: '解決方案', support: '支援', news: '資訊', contact: '聯絡我們' },
|
||
|
|
backToNews: '返回資訊列表',
|
||
|
|
minutes: '分鐘',
|
||
|
|
articleNotFound: '文章未找到',
|
||
|
|
backToList: '返回資訊列表',
|
||
|
|
},
|
||
|
|
en: {
|
||
|
|
nav: { home: 'Home', products: 'Products', solutions: 'Solutions', support: 'Support', news: 'News', contact: 'Contact' },
|
||
|
|
backToNews: 'Back to News',
|
||
|
|
minutes: 'min',
|
||
|
|
articleNotFound: 'Article Not Found',
|
||
|
|
backToList: 'Back to News List',
|
||
|
|
},
|
||
|
|
} as const;
|
||
|
|
|
||
|
|
const t = translations[currentLang];
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const browserLang = navigator.language.toLowerCase();
|
||
|
|
if (browserLang.includes('en')) {
|
||
|
|
setCurrentLang('en');
|
||
|
|
} else if (browserLang.includes('zh-tw') || browserLang.includes('zh-hk')) {
|
||
|
|
setCurrentLang('zh-tw');
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// 移除动态加载内容的逻辑,直接使用传入的数据
|
||
|
|
// useEffect(() => { ... }, [slug, currentLang]);
|
||
|
|
|
||
|
|
const getCategoryColor = (category: string) => {
|
||
|
|
const colors = {
|
||
|
|
tech: 'bg-blue-100 text-blue-800',
|
||
|
|
product: 'bg-green-100 text-green-800',
|
||
|
|
industry: 'bg-purple-100 text-purple-800',
|
||
|
|
tutorial: 'bg-orange-100 text-orange-800',
|
||
|
|
} as const;
|
||
|
|
return (colors as any)[category] || 'bg-gray-100 text-gray-800';
|
||
|
|
};
|
||
|
|
|
||
|
|
const getCategoryName = (category: string) => {
|
||
|
|
const names = {
|
||
|
|
zh: { tech: '技术文章', product: '产品更新', industry: '行业动态', tutorial: '教程指南' },
|
||
|
|
'zh-tw': { tech: '技術文章', product: '產品更新', industry: '行業動態', tutorial: '教學指南' },
|
||
|
|
en: { tech: 'Technical Articles', product: 'Product Updates', industry: 'Industry News', tutorial: 'Tutorials' },
|
||
|
|
} as const;
|
||
|
|
return (names as any)[currentLang]?.[category] || category;
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!meta || !content) {
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-gradient-to-br from-amber-50 to-yellow-50 flex items-center justify-center">
|
||
|
|
<div className="text-center">
|
||
|
|
<h1 className="text-2xl font-bold text-amber-900 mb-4">{t.articleNotFound}</h1>
|
||
|
|
<Link href="/news" className="text-amber-600 hover:text-amber-700">
|
||
|
|
{t.backToList}
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-gradient-to-br from-amber-50 to-yellow-50">
|
||
|
|
<Head
|
||
|
|
title={`${meta.title} - CloudPro`}
|
||
|
|
description={meta.description}
|
||
|
|
keywords="云计算,技术文章,CloudPro"
|
||
|
|
currentLang={currentLang}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<Navigation currentLang={currentLang} onLanguageChange={setCurrentLang} translations={translations} />
|
||
|
|
|
||
|
|
<section className="bg-gradient-to-r from-amber-900 to-yellow-800 text-white py-12">
|
||
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
|
|
<Link href="/news" className="inline-flex items-center text-yellow-200 hover:text-yellow-100 mb-6 transition-colors">
|
||
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||
|
|
{t.backToNews}
|
||
|
|
</Link>
|
||
|
|
|
||
|
|
<div className="mb-6">
|
||
|
|
<span className={`inline-block px-3 py-1 rounded-full text-xs font-medium ${getCategoryColor(meta.category)} bg-opacity-20 text-yellow-200 border border-yellow-300`}>
|
||
|
|
{getCategoryName(meta.category)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h1 className="text-3xl md:text-4xl font-bold mb-6 leading-tight">{meta.title}</h1>
|
||
|
|
|
||
|
|
<div className="flex flex-wrap items-center gap-6 text-yellow-200">
|
||
|
|
<div className="flex items-center">
|
||
|
|
<User className="w-4 h-4 mr-2" />
|
||
|
|
{meta.author}
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center">
|
||
|
|
<Calendar className="w-4 h-4 mr-2" />
|
||
|
|
{meta.date}
|
||
|
|
</div>
|
||
|
|
<div className="flex items-center">
|
||
|
|
<Clock className="w-4 h-4 mr-2" />
|
||
|
|
{meta.readTime} {t.minutes}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section className="py-12">
|
||
|
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
|
|
<article className="bg-white rounded-xl shadow-lg p-8 md:p-12 border border-amber-200">
|
||
|
|
<div className="prose prose-lg max-w-none prose-headings:text-amber-900 prose-a:text-amber-600 prose-strong:text-amber-800 prose-code:text-amber-700 prose-code:bg-amber-50 prose-pre:bg-amber-50">
|
||
|
|
<ReactMarkdown
|
||
|
|
remarkPlugins={[remarkGfm]}
|
||
|
|
components={{
|
||
|
|
code(codeProps) {
|
||
|
|
const { children, className, ...rest } = codeProps as any;
|
||
|
|
const match = /language-(\w+)/.exec((className as string) || '');
|
||
|
|
return match ? (
|
||
|
|
<SyntaxHighlighter style={tomorrow as any} language={match[1]} PreTag="div" {...rest}>
|
||
|
|
{String(children).replace(/\n$/, '')}
|
||
|
|
</SyntaxHighlighter>
|
||
|
|
) : (
|
||
|
|
<code className={className as string} {...rest}>
|
||
|
|
{children}
|
||
|
|
</code>
|
||
|
|
);
|
||
|
|
},
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{content}
|
||
|
|
</ReactMarkdown>
|
||
|
|
</div>
|
||
|
|
</article>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<Footer currentLang={currentLang} translations={translations} />
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|