124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
|
|
import fs from 'fs';
|
||
|
|
import path from 'path';
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { ArticleClient } from './ArticleClient';
|
||
|
|
|
||
|
|
interface ArticleData {
|
||
|
|
content: string;
|
||
|
|
meta: {
|
||
|
|
title: string;
|
||
|
|
description: string;
|
||
|
|
author: string;
|
||
|
|
date: string;
|
||
|
|
readTime: number;
|
||
|
|
category: string;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function generateStaticParams() {
|
||
|
|
// 只基于 content 目录中实际存在的文章生成参数
|
||
|
|
const contentDir = path.join(process.cwd(), 'content');
|
||
|
|
const slugs: string[] = [];
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (fs.existsSync(contentDir)) {
|
||
|
|
const folders = fs
|
||
|
|
.readdirSync(contentDir, { withFileTypes: true })
|
||
|
|
.filter((dirent) => dirent.isDirectory())
|
||
|
|
.map((dirent) => dirent.name);
|
||
|
|
slugs.push(...folders);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.warn('generateStaticParams: unable to read content dir', error);
|
||
|
|
}
|
||
|
|
|
||
|
|
return slugs.map(slug => ({ slug }));
|
||
|
|
}
|
||
|
|
|
||
|
|
async function getArticleData(slug: string): Promise<ArticleData | null> {
|
||
|
|
try {
|
||
|
|
const contentDir = path.join(process.cwd(), 'content');
|
||
|
|
const articleDir = path.join(contentDir, slug);
|
||
|
|
|
||
|
|
if (!fs.existsSync(articleDir)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 优先读取中文内容
|
||
|
|
let contentFile = '';
|
||
|
|
let lang = 'zh';
|
||
|
|
|
||
|
|
if (fs.existsSync(path.join(articleDir, 'zh.md'))) {
|
||
|
|
contentFile = 'zh.md';
|
||
|
|
lang = 'zh';
|
||
|
|
} else if (fs.existsSync(path.join(articleDir, 'en.md'))) {
|
||
|
|
contentFile = 'en.md';
|
||
|
|
lang = 'en';
|
||
|
|
} else if (fs.existsSync(path.join(articleDir, 'zh-tw.md'))) {
|
||
|
|
contentFile = 'zh-tw.md';
|
||
|
|
lang = 'zh-tw';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!contentFile) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const filePath = path.join(articleDir, contentFile);
|
||
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||
|
|
|
||
|
|
// 从内容中提取元数据
|
||
|
|
const titleMatch = content.match(/^#\s+(.+)$/m);
|
||
|
|
const title = titleMatch ? titleMatch[1] : slug;
|
||
|
|
|
||
|
|
// 提取摘要(第一段非标题内容)
|
||
|
|
const lines = content.split('\n').filter((line: string) =>
|
||
|
|
line.trim() && !line.startsWith('#') && !line.startsWith('---')
|
||
|
|
);
|
||
|
|
const excerpt = lines[0] || title;
|
||
|
|
|
||
|
|
// 根据文件夹名称推断分类
|
||
|
|
let category = 'tech';
|
||
|
|
if (slug.includes('ecs') || slug.includes('rds') || slug.includes('oss')) {
|
||
|
|
category = 'product';
|
||
|
|
} else if (slug.includes('deployment') || slug.includes('guide') || slug.includes('tutorial')) {
|
||
|
|
category = 'tutorial';
|
||
|
|
} else if (slug.includes('market') || slug.includes('trend')) {
|
||
|
|
category = 'industry';
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
content,
|
||
|
|
meta: {
|
||
|
|
title,
|
||
|
|
description: excerpt.length > 200 ? excerpt.substring(0, 200) + '...' : excerpt,
|
||
|
|
author: 'CloudPro 技术团队',
|
||
|
|
date: new Date().toISOString().split('T')[0],
|
||
|
|
readTime: Math.ceil(content.split('\n').length / 20),
|
||
|
|
category,
|
||
|
|
}
|
||
|
|
};
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error reading article:', error);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default async function Page({ params }: { params: { slug: string } }) {
|
||
|
|
const articleData = await getArticleData(params.slug);
|
||
|
|
|
||
|
|
if (!articleData) {
|
||
|
|
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">文章未找到</h1>
|
||
|
|
<Link href="/news" className="text-amber-600 hover:text-amber-700">
|
||
|
|
返回资讯列表
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return <ArticleClient slug={params.slug} initialData={articleData} />;
|
||
|
|
}
|