diff --git a/app/[locale]/about/arch/page.tsx b/app/[locale]/about/arch/page.tsx
index e69de29..53bc7fb 100644
--- a/app/[locale]/about/arch/page.tsx
+++ b/app/[locale]/about/arch/page.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { useEffect } from "react";
+import { useSectionStore } from "@/stores/sectionStore";
+
+export default function Page() {
+ const { setSections } = useSectionStore();
+ useEffect(() => {
+ setSections([
+ { id: "arch-vision", name: "友发愿景" },
+ { id: "arch-products", name: "产品体系" },
+ { id: "arch-application", name: "应用场景" },
+ { id: "arch-contact", name: "联系方式" },
+ ]);
+ }, [setSections]);
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/about/base/page.tsx b/app/[locale]/about/base/page.tsx
index e69de29..19a9c39 100644
--- a/app/[locale]/about/base/page.tsx
+++ b/app/[locale]/about/base/page.tsx
@@ -0,0 +1,5 @@
+export default function Page() {
+ return (
+ 基础介绍
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/about/course/page.tsx b/app/[locale]/about/course/page.tsx
index e69de29..6ff4440 100644
--- a/app/[locale]/about/course/page.tsx
+++ b/app/[locale]/about/course/page.tsx
@@ -0,0 +1,5 @@
+export default function Page() {
+ return (
+ 发展历程
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/about/culture/page.tsx b/app/[locale]/about/culture/page.tsx
index e69de29..9217f56 100644
--- a/app/[locale]/about/culture/page.tsx
+++ b/app/[locale]/about/culture/page.tsx
@@ -0,0 +1,5 @@
+export default function Page() {
+ return (
+ 企业文化
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/about/green/page.tsx b/app/[locale]/about/green/page.tsx
index e69de29..eeaa1e2 100644
--- a/app/[locale]/about/green/page.tsx
+++ b/app/[locale]/about/green/page.tsx
@@ -0,0 +1,5 @@
+export default function Page() {
+ return (
+ 绿色发展
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/about/honor/page.tsx b/app/[locale]/about/honor/page.tsx
index e69de29..b0dc8ef 100644
--- a/app/[locale]/about/honor/page.tsx
+++ b/app/[locale]/about/honor/page.tsx
@@ -0,0 +1,5 @@
+export default function Page() {
+ return (
+ 荣誉资质
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/about/introduction/page.tsx b/app/[locale]/about/introduction/page.tsx
index e69de29..c1e9a6c 100644
--- a/app/[locale]/about/introduction/page.tsx
+++ b/app/[locale]/about/introduction/page.tsx
@@ -0,0 +1,5 @@
+export default function Page() {
+ return (
+ 公司介绍
+ );
+}
\ No newline at end of file
diff --git a/app/[locale]/blog/page.tsx b/app/[locale]/blog/page.tsx
index f8d2e14..2fcf49d 100644
--- a/app/[locale]/blog/page.tsx
+++ b/app/[locale]/blog/page.tsx
@@ -1,16 +1,15 @@
-import { Locale, LOCALES, Link as I18nLink } from "@/i18n/routing";
+import ParallaxHero from "@/app/[locale]/blog/ParallaxHero";
+import TablerEyeFilled from "@/components/icons/eye";
+import { Link as I18nLink, Locale, LOCALES } from "@/i18n/routing";
import { getPosts } from "@/lib/getBlogs";
import { constructMetadata } from "@/lib/metadata";
+import dayjs from "dayjs";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
-import Image from "next/image";
-import dayjs from "dayjs";
-import TablerEyeFilled from "@/components/icons/eye";
-import ParallaxHero from "@/app/[locale]/blog/ParallaxHero";
type Params = Promise<{ locale: string }>;
-type SearchParams = { page?: string };
+type SearchParams = { page?: string; type?: string };
type MetadataProps = {
params: Params;
@@ -40,10 +39,29 @@ export default async function Page({
searchParams?: SearchParams;
}) {
const { locale } = await params;
- const { posts } = await getPosts(locale);
+ let { posts } = await getPosts(locale);
const t = await getTranslations("Blog");
+ // 分类筛选(公司新闻/专家访谈/行业动态/公告)
+ const type = (searchParams?.type || "").toLowerCase();
+ const typeLabelMap: Record = {
+ company: "公司新闻",
+ experts: "专家访谈",
+ industry: "行业动态",
+ notice: "公告",
+ };
+ const typeLabel = typeLabelMap[type];
+ if (type) {
+ posts = posts.filter((p) => {
+ const metaType = String((p.metadata as any)?.type || "").toLowerCase();
+ const tags: string[] = (p.tags as any) || [];
+ const hitMeta = metaType === type;
+ const hitTag = Array.isArray(tags) && tags.some((t) => String(t).toLowerCase() === type);
+ return hitMeta || hitTag;
+ });
+ }
+
const pageRaw = searchParams?.page;
const page = Math.max(1, parseInt(pageRaw || "1", 10));
const pageSize = 10;
@@ -75,6 +93,12 @@ export default async function Page({
新闻中心
+ {typeLabel && (
+ <>
+ /
+ {typeLabel}
+ >
+ )}
diff --git a/components/header/HeaderLinks.tsx b/components/header/HeaderLinks.tsx
index 0e9cf88..5c86438 100644
--- a/components/header/HeaderLinks.tsx
+++ b/components/header/HeaderLinks.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Link as I18nLink, usePathname } from "@/i18n/routing";
+import { Link as I18nLink, usePathname, useRouter } from "@/i18n/routing";
import { cn } from "@/lib/utils";
import { HeaderLink } from "@/types/common";
import { ExternalLink } from "lucide-react";
@@ -9,32 +9,112 @@ import { useTranslations } from "next-intl";
const HeaderLinks = () => {
const tHeader = useTranslations("Header");
const pathname = usePathname();
+ const router = useRouter();
const headerLinks: HeaderLink[] = tHeader.raw("links");
+ const localePrefix = `/${(pathname || "/zh").split("/")[1] || "zh"}`;
return (
- {headerLinks.map((link) => (
-
- {link.name}
- {link.target && link.target === "_blank" && (
-
-
-
- )}
-
- ))}
+ {headerLinks.map((link) => {
+ const isAbout = link.href === "/about";
+ const isNews = link.href === "/blog";
+ if (isAbout) {
+ return (
+
+
+ {link.name}
+
+ {/* 移除触发器与菜单间的间隙,使用 pt-2 保持视觉间距但不中断 hover 区域 */}
+
+
+ 友发愿景
+ 产品体系
+ 应用场景
+ 联系方式
+
+
+
+ );
+ }
+ if (isNews) {
+ return (
+
+
+ {link.name}
+
+
+
+ {[
+ { key: "company", label: "公司新闻" },
+ { key: "experts", label: "专家访谈" },
+ { key: "industry", label: "行业动态" },
+ { key: "notice", label: "公告" },
+ ].map((it) => (
+
+ ))}
+
+
+
+ );
+ }
+ return (
+
+ {link.name}
+ {link.target && link.target === "_blank" && (
+
+
+
+ )}
+
+ );
+ })}
);
};
diff --git a/components/header/SectionDropdown.tsx b/components/header/SectionDropdown.tsx
new file mode 100644
index 0000000..06b2a9d
--- /dev/null
+++ b/components/header/SectionDropdown.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import { useSectionStore } from "@/stores/sectionStore";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+export default function SectionDropdown() {
+ const { sections } = useSectionStore();
+ if (!sections || sections.length === 0) return null;
+
+ return (
+
+
+ 页面目录
+
+
+
+ {sections.map((s) => (
+ {
+ const el = document.getElementById(s.id);
+ if (!el) return;
+ const rect = el.getBoundingClientRect();
+ const top = rect.top + window.scrollY - 80;
+ window.scrollTo({ top, behavior: "smooth" });
+ history.replaceState(null, "", `#${s.id}`);
+ }}
+ >
+ {s.name}
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/i18n/messages/zh.json b/i18n/messages/zh.json
index e3d2459..994ed1c 100644
--- a/i18n/messages/zh.json
+++ b/i18n/messages/zh.json
@@ -130,7 +130,9 @@
"Home": {
"title": "Next Forge",
"tagLine": "Next.js 多语言启动模板",
- "description": "内置多语言支持的 Next.js 16 启动模板,助您快速构建面向全球的出海网站。简洁高效,开箱即用,完全优化的SEO基础架构。"
+ "description": "内置多语言支持的 Next.js 16 启动模板,助您快速构建面向全球的出海网站。简洁高效,开箱即用,完全优化的SEO基础架构。",
+ "carousel": "轮播图",
+ "company_video": "企业视频"
},
"Blog": {
"title": "博客列表",
diff --git a/next-env.d.ts b/next-env.d.ts
index 9edff1c..c4b7818 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/stores/sectionStore.ts b/stores/sectionStore.ts
new file mode 100644
index 0000000..023bec3
--- /dev/null
+++ b/stores/sectionStore.ts
@@ -0,0 +1,15 @@
+import { create } from "zustand";
+
+export type SectionItem = { id: string; name: string };
+
+type SectionStore = {
+ sections: SectionItem[];
+ setSections: (items: SectionItem[]) => void;
+ clear: () => void;
+};
+
+export const useSectionStore = create((set) => ({
+ sections: [],
+ setSections: (items) => set({ sections: items }),
+ clear: () => set({ sections: [] }),
+}));
\ No newline at end of file