From 450337a019ab6a0de14c85f616a5d8c77ef1245d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=82=E7=B3=96?= Date: Thu, 11 Dec 2025 09:10:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor(about):=20=E9=87=8D=E6=9E=84=E5=85=B3?= =?UTF-8?q?=E4=BA=8E=E9=A1=B5=E9=9D=A2=E8=B7=AF=E7=94=B1=E4=B8=BA=E9=9D=99?= =?UTF-8?q?=E6=80=81=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将关于页面的查询参数路由改为静态路径结构,如/about/company 更新i18n消息中的链接路径 添加新的[section]页面处理逻辑 优化静态生成参数和错误处理 --- app/[locale]/about/[section]/page.tsx | 120 ++++++++++++++++++++++++++ app/[locale]/about/page.tsx | 102 +++++----------------- app/[locale]/blog/[slug]/page.tsx | 45 ---------- app/[locale]/blog/page.tsx | 14 ++- app/[locale]/page.tsx | 18 ++-- app/[locale]/product/[slug]/page.tsx | 4 +- app/robots.ts | 2 +- app/sitemap.ts | 2 +- components/ProductTabsClient.tsx | 2 +- components/header/Header.tsx | 3 +- content/product/en/1.mdx | 6 +- content/product/en/2.mdx | 6 +- content/product/en/3.mdx | 6 +- content/product/vi/1.mdx | 6 +- content/product/vi/2.mdx | 6 +- content/product/vi/3.mdx | 6 +- content/product/zh/1.mdx | 6 +- content/product/zh/2.mdx | 6 +- content/product/zh/3.mdx | 4 +- i18n/messages/en.json | 24 +++--- i18n/messages/vi.json | 24 +++--- i18n/messages/zh.json | 24 +++--- next.config.mjs | 3 +- package-lock.json | 7 +- package.json | 3 +- 25 files changed, 234 insertions(+), 215 deletions(-) create mode 100644 app/[locale]/about/[section]/page.tsx diff --git a/app/[locale]/about/[section]/page.tsx b/app/[locale]/about/[section]/page.tsx new file mode 100644 index 0000000..18e389a --- /dev/null +++ b/app/[locale]/about/[section]/page.tsx @@ -0,0 +1,120 @@ +import MDXComponents from "@/components/mdx/MDXComponents"; +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"; + +// 强制静态渲染 +export const dynamic = "force-static"; + +const options = { + parseFrontmatter: true, + mdxOptions: { + remarkPlugins: [remarkGfm], + rehypePlugins: [], + }, +}; + +// 增强:添加参数校验,防止undefined +async function getMDXContent(locale: string, section: string) { + // 兜底校验:确保参数是字符串 + const validLocale = locale?.trim() || "en"; // 兜底为默认语言 + const validSection = section?.trim() || "company"; // 兜底为默认section + + // 校验LOCALES是否包含当前locale + if (!LOCALES.includes(validLocale)) { + console.error(`Locale "${validLocale}" not found in LOCALES!`); + return ""; + } + + const filePath = path.join( + process.cwd(), + "content", + "about", + validSection, // 使用校验后的section + `${validLocale}.mdx` // 使用校验后的locale + ); + + try { + // 先检查文件是否存在,避免读取不存在的文件 + await fs.access(filePath); + const content = await fs.readFile(filePath, "utf-8"); + return content; + } catch (error) { + console.error(`Error accessing/reading MDX file (${filePath}):`, error); + return ""; + } +} + +// 修正:路由参数类型(locale + section) +type Params = { + locale: string; + section: string; +}; + +type MetadataProps = { + params: Params; +}; + +export async function generateMetadata({ + params, +}: MetadataProps): Promise { + const { locale, section } = params; + const t = await getTranslations({ locale, namespace: "About" }); + + return constructMetadata({ + page: `About - ${section}`, + title: t(`title`) || t("title"), // 适配不同section的标题 + description: t(`description`) || t("description"), + locale: locale as Locale, + path: `/about/${section}`, + canonicalUrl: `/about/${section}`, + }); +} + +// 页面组件:从params获取locale和section(路由参数) +export default async function AboutPage({ + params, +}: { + params: Promise; +}) { + const { locale, section } = await params; + + console.log(`Rendering AboutPage for locale: ${locale}, section: ${section}`); + // 调用带校验的getContent函数 + const content = await getMDXContent(locale, section); + + return ( +
+ +
+ ); +} + +// 关键:生成locale + section的所有静态组合(覆盖vi/organization等) +export async function generateStaticParams() { + const sections = ['company', 'awards', 'base', 'culture', 'history', 'organization']; + + // 确保LOCALES包含"vi"(越南语),否则会生成undefined! + if (!LOCALES.includes("vi")) { + console.warn("⚠️ LOCALES does not include 'vi'! Add it to fix /vi/about/* paths."); + // 临时兜底:如果没有vi,手动添加(或检查你的i18n/routing.ts) + // LOCALES.push("vi"); + } + + // 生成所有locale × section的组合 + return LOCALES.flatMap((locale) => + sections.map((section) => ({ + locale, + section, + })) + ); +} \ No newline at end of file diff --git a/app/[locale]/about/page.tsx b/app/[locale]/about/page.tsx index 83441af..0770838 100644 --- a/app/[locale]/about/page.tsx +++ b/app/[locale]/about/page.tsx @@ -1,88 +1,32 @@ -import MDXComponents from "@/components/mdx/MDXComponents"; -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"; +import { LOCALES } from "@/i18n/routing"; +import { redirect } from "next/navigation"; -const options = { - parseFrontmatter: true, - mdxOptions: { - remarkPlugins: [remarkGfm], - rehypePlugins: [], - }, -}; - -async function getMDXContent(locale: string, section: 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({ +// 重定向到 /about/company +export default async function Page({ params, -}: MetadataProps): Promise { - 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 }>; + params: Promise<{ locale: string }>; }) { const { locale } = await params; - const resolvedSearchParams = await searchParams; - const section = (resolvedSearchParams.section as string) || "company"; - - const content = await getMDXContent(locale, section); - - return ( -
- -
- ); + return redirect(`/${locale}/about/company`); } +// 关键:生成locale + section的所有静态组合(覆盖vi/organization等) export async function generateStaticParams() { - return LOCALES.map((locale) => ({ - locale, - })); + const sections = ['company', 'awards', 'base', 'culture', 'history', 'organization']; + + // 确保LOCALES包含"vi"(越南语),否则会生成undefined! + if (!LOCALES.includes("vi")) { + console.warn("⚠️ LOCALES does not include 'vi'! Add it to fix /vi/about/* paths."); + // 临时兜底:如果没有vi,手动添加(或检查你的i18n/routing.ts) + // LOCALES.push("vi"); + } + + // 生成所有locale × section的组合 + return LOCALES.flatMap((locale) => + sections.map((section) => ({ + locale, + section, + })) + ); } \ No newline at end of file diff --git a/app/[locale]/blog/[slug]/page.tsx b/app/[locale]/blog/[slug]/page.tsx index 62393c5..2e9ff21 100644 --- a/app/[locale]/blog/[slug]/page.tsx +++ b/app/[locale]/blog/[slug]/page.tsx @@ -72,51 +72,6 @@ async function getMDXContent(locale: string, section: string): Promise } } -/** - * 解析MDX内容,提取frontmatter和正文 - */ -function parseMDXContent(content: string): { - frontmatter: Record - content: string -} { - // 匹配frontmatter的正则表达式(---开头和结尾) - const frontmatterRegex = /^---\s*[\r\n]([\s\S]*?)[\r\n]---\s*[\r\n]/; - const match = frontmatterRegex.exec(content); - - let frontmatter: Record = {}; - let postContent = content; - - if (match && match[1]) { - // 提取frontmatter部分并解析 - const frontmatterStr = match[1]; - postContent = content.slice(match[0].length); - - // 解析frontmatter的每一行 - const lines = frontmatterStr.split(/[\r\n]+/); - lines.forEach(line => { - // 跳过空行和注释 - if (!line.trim() || line.trim().startsWith('#')) return; - - // 分割键值对(支持值中有冒号的情况) - const colonIndex = line.indexOf(':'); - if (colonIndex === -1) return; - - const key = line.substring(0, colonIndex).trim(); - let value = line.substring(colonIndex + 1).trim(); - - // 移除值的引号(如果有) - if ((value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'"))) { - value = value.substring(1, value.length - 1); - } - - frontmatter[key] = value; - }); - } - - return { frontmatter, content: postContent }; -} - export async function generateMetadata({ params, diff --git a/app/[locale]/blog/page.tsx b/app/[locale]/blog/page.tsx index 12a566d..7c6258d 100644 --- a/app/[locale]/blog/page.tsx +++ b/app/[locale]/blog/page.tsx @@ -9,8 +9,6 @@ import { getTranslations } from "next-intl/server"; type Params = Promise<{ locale: string }>; -type SearchParams = { page?: string; category?: string }; - type MetadataProps = { params: Params; }; @@ -33,21 +31,21 @@ export async function generateMetadata({ export default async function Page({ params, - searchParams, + // searchParams, }: { params: Params; - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const { locale } = await params; - const resolvedSearchParams = await searchParams; - const category = resolvedSearchParams.category as string || ""; + // const resolvedSearchParams = await searchParams; + // const category = resolvedSearchParams.category as string || ""; let { posts } = await getPosts(locale); const t = await getTranslations("Blog"); - const pageRaw = resolvedSearchParams.page as string || "1"; - const page = Math.max(1, parseInt(pageRaw, 10)); + // const pageRaw = resolvedSearchParams.page as string || "1"; + // const page = Math.max(1, parseInt(10, 10)); + const page = 1; const pageSize = 10; const totalPages = Math.max(1, Math.ceil(posts.length / pageSize)); const start = (page - 1) * pageSize; diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index eb99d87..789ccd6 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -1,16 +1,16 @@ import HomeComponent from "@/components/home"; -// export const dynamic = "force-static"; +export const dynamic = "force-static"; export default function Home() { return ; } -// export async function generateStaticParams() { -// return [ -// { locale: 'en' }, -// { locale: 'zh' }, -// { locale: 'vi' }, -// // { locale: 'ja' }, -// ] -// } +export async function generateStaticParams() { + return [ + { locale: 'en' }, + { locale: 'zh' }, + { locale: 'vi' }, + // { locale: 'ja' }, + ] +} diff --git a/app/[locale]/product/[slug]/page.tsx b/app/[locale]/product/[slug]/page.tsx index 7682746..c9aff38 100644 --- a/app/[locale]/product/[slug]/page.tsx +++ b/app/[locale]/product/[slug]/page.tsx @@ -169,12 +169,12 @@ export async function generateStaticParams() { try { const defaultLocale = LOCALES[0]; const { products }: { products: Product[] } = await getProducts(defaultLocale); - const validProducts = products.filter((product) => product.slug && product.title); + const validProducts = products.filter((product) => product.slug.toString() && product.title); return LOCALES.flatMap((locale) => validProducts.map((product) => ({ locale, - slug: product.slug, + slug: product.slug.toString(), })) ); } catch (error) { diff --git a/app/robots.ts b/app/robots.ts index e11b0b3..ca64e59 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -3,7 +3,7 @@ import type { MetadataRoute } from 'next' const siteUrl = siteConfig.url -// export const dynamic = "force-static" +export const dynamic = "force-static" export default function robots(): MetadataRoute.Robots { return { diff --git a/app/sitemap.ts b/app/sitemap.ts index e53915e..bc0c798 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -7,7 +7,7 @@ const siteUrl = siteConfig.url type ChangeFrequency = 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' | undefined -// export const dynamic = "force-static" +export const dynamic = "force-static" export default async function sitemap(): Promise { // Static pages diff --git a/components/ProductTabsClient.tsx b/components/ProductTabsClient.tsx index 93c740d..33620e1 100644 --- a/components/ProductTabsClient.tsx +++ b/components/ProductTabsClient.tsx @@ -35,7 +35,7 @@ export default function ProductTabsClient({ detail, spec, packaging, locale }: P className={`px-4 py-2 transition-colors ${activeTab === 'packaging' ? 'bg-orange-50 text-orange-600 font-medium' : 'hover:bg-gray-50'}`} onClick={() => setActiveTab('packaging')} > - {t('productPackaging')} + {t('productPacking')} {/* 内容区 */} diff --git a/components/header/Header.tsx b/components/header/Header.tsx index a9817ef..5a4a172 100644 --- a/components/header/Header.tsx +++ b/components/header/Header.tsx @@ -4,7 +4,6 @@ import LocaleSwitcher from "@/components/LocaleSwitcher"; import { siteConfig } from "@/config/site"; import { Link as I18nLink } from "@/i18n/routing"; import { useTranslations } from "next-intl"; -import Image from "next/image"; const Header = () => { const t = useTranslations("Home"); @@ -18,7 +17,7 @@ const Header = () => { prefetch={false} className="flex items-center space-x-1 font-bold" > -