Files
fad-trade-next/app/[locale]/about/page.tsx
砂糖 effdee935a feat(blog): 添加博客目录功能并更新logo格式
为博客和关于页面添加可交互的目录组件,方便用户导航
将网站logo从svg格式统一改为png格式
移除generateStaticParams函数以简化构建流程
新增getBlogDetail.ts用于获取博客详情数据
2025-11-27 13:10:26 +08:00

156 lines
3.8 KiB
TypeScript
Raw 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 MDXComponents from "@/components/mdx/MDXComponents";
import TableOfContents from "@/components/mdx/TableOfContents.client";
import { Locale, LOCALES } from "@/i18n/routing";
import { constructMetadata } from "@/lib/metadata";
import fs from "fs/promises";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { MDXRemote } from "next-mdx-remote-client/rsc";
import path from "path";
import remarkGfm from "remark-gfm";
const options = {
parseFrontmatter: true,
mdxOptions: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
};
interface TableOfContentsItem {
id: string;
text: string;
level: number;
}
// 解析MDX内容并提取标题
async function parseMDXContent(content: string): Promise<TableOfContentsItem[]> {
if (!content) {
return [];
}
try {
const headingRegex = /^#{2,4}\s+(.+)$/gm;
const headings: TableOfContentsItem[] = [];
let match;
while ((match = headingRegex.exec(content)) !== null) {
const fullMatch = match[0];
const text = match[1]?.trim();
if (!text) continue;
// 确定标题级别
let level = 2;
if (fullMatch.startsWith("###")) {
level = fullMatch.startsWith("####") ? 4 : 3;
}
// 生成ID将文本转换为URL友好的格式
const id = text
.toLowerCase()
.replace(/[^a-z0-9\u4e00-\u9fa5\s-]/g, "")
.replace(/\s+/g, "-");
headings.push({ id, text, level });
}
return headings;
} catch (error) {
console.error("Error parsing MDX content for TOC:", error);
return [];
}
}
async function getMDXContent(locale: string, section: string): Promise<string> {
const filePath = path.join(
process.cwd(),
"content",
"about",
section,
`${locale}.mdx`
);
try {
const content = await fs.readFile(filePath, "utf-8");
return content;
} catch (error) {
console.error(`Error reading MDX file: ${error}`);
return "";
}
}
type Params = Promise<{
locale: string;
}>;
type MetadataProps = {
params: Params;
};
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "About" });
return constructMetadata({
page: "About",
title: t("title"),
description: t("description"),
locale: locale as Locale,
path: `/about`,
canonicalUrl: `/about`,
});
}
export default async function AboutPage({
params,
searchParams
}: {
params: Params;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const { locale } = await params;
const resolvedSearchParams = await searchParams;
const section = (resolvedSearchParams.section as string) || "company";
const content = await getMDXContent(locale, section);
const tocItems = await parseMDXContent(content);
// 获取多语言目录标题
const t = await getTranslations({ locale, namespace: "Common" });
const tocTitle = t("tableOfContents") || "目录";
return (
<div className="flex flex-col md:flex-row w-full gap-8">
{/* 侧边目录 - 在移动端显示在内容上方 */}
<div className="w-full md:w-1/4 lg:w-1/5 order-2 md:order-1">
<TableOfContents
items={tocItems || []}
title={tocTitle}
/>
</div>
{/* 主要内容 */}
<article className="w-full md:w-3/4 lg:w-4/5 px-2 md:px-8 ml-0 md:ml-64 order-1 md:order-2">
{content ? (
<MDXRemote
source={content}
components={MDXComponents}
options={options}
/>
) : (
<div className="text-center py-8 text-gray-500">
<p>...</p>
</div>
)}
</article>
</div>
);
}
export async function generateStaticParams() {
return LOCALES.map((locale) => ({
locale,
}));
}