From 24a2311e20771bd7ae119e896dc2c7aeff0ae1b7 Mon Sep 17 00:00:00 2001 From: Zhengen TANG Date: Fri, 7 Aug 2026 13:06:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E5=85=A8=E5=B1=80Snippet=EF=BC=88=E5=AF=BC=E8=88=AA=E8=8F=9C?= =?UTF-8?q?=E5=8D=95/=E7=AB=99=E7=82=B9=E8=AE=BE=E7=BD=AE=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E8=A1=A5=E5=85=85=E5=9B=A2=E9=98=9F/=E8=AF=81?= =?UTF-8?q?=E8=A8=80/=E5=90=88=E4=BD=9C=E4=BC=99=E4=BC=B4=20service=20?= =?UTF-8?q?=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Header 改为异步 Server Component,接入 NavigationMenu(name=main), 接口不可用或未配置时回退静态导航项 - Footer 改为异步 Server Component,接入 SiteSettings, 公司名/ICP备案号/备案链接优先取后台配置,缺省回退硬编码值 - types/wagtail.ts 新增 TeamMember/Testimonial/Partner/NavigationMenu/SiteSettings 类型 - 新增 services: team/testimonial/partner/navigation/site-settings.service.ts - lint、build 均已验证通过 --- components/layout/Footer.tsx | 29 +++++++++++++----- components/layout/Header.tsx | 25 ++++++++++++++-- services/navigation.service.ts | 19 ++++++++++++ services/partner.service.ts | 11 +++++++ services/site-settings.service.ts | 14 +++++++++ services/team.service.ts | 11 +++++++ services/testimonial.service.ts | 11 +++++++ types/wagtail.ts | 50 +++++++++++++++++++++++++++++++ 8 files changed, 159 insertions(+), 11 deletions(-) create mode 100644 services/navigation.service.ts create mode 100644 services/partner.service.ts create mode 100644 services/site-settings.service.ts create mode 100644 services/team.service.ts create mode 100644 services/testimonial.service.ts diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx index 481971d..0f47b71 100644 --- a/components/layout/Footer.tsx +++ b/components/layout/Footer.tsx @@ -1,28 +1,41 @@ -export default function Footer() { +import { getSiteSettings } from "@/services/site-settings.service"; + +// 合规要求:中国大陆网站需展示 ICP 备案号并链接至工信部备案查询。 +// 详见 documents/设计方案分析与完善版.md §2.15。 +const FALLBACK_ICP_NUMBER = "冀ICP备2025130506号-1"; +const FALLBACK_ICP_URL = "https://beian.miit.gov.cn/"; + +export default async function Footer() { + // 优先使用后台配置的站点设置(apps.core.SiteSettings), + // 接口不可用时回退到硬编码的备案信息,保证构建/渲染始终可用。 + const siteSettings = await getSiteSettings(); + const icpNumber = siteSettings?.icp_number || FALLBACK_ICP_NUMBER; + const icpUrl = siteSettings?.icp_url || FALLBACK_ICP_URL; + return ( ); } + diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index 01abefd..12676c9 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; +import { getNavigationMenu } from "@/services/navigation.service"; -const navItems = [ +const fallbackNavItems = [ { title: "首页", href: "/" }, { title: "产品", href: "/products" }, { title: "解决方案", href: "/solutions" }, @@ -8,7 +9,18 @@ const navItems = [ { title: "博客", href: "/blog" }, ]; -export default function Header() { +export default async function Header() { + // 优先使用后台配置的主导航(apps.core.NavigationMenu,name="main"), + // 未配置或接口不可用时回退到静态导航项,保证构建/渲染始终可用。 + const menu = await getNavigationMenu("main"); + const navItems = menu?.items.length + ? menu.items.map((item) => ({ + title: item.label, + href: item.url, + newTab: item.open_in_new_tab, + })) + : fallbackNavItems.map((item) => ({ ...item, newTab: false })); + return (
@@ -17,7 +29,13 @@ export default function Header() {
); } + diff --git a/services/navigation.service.ts b/services/navigation.service.ts new file mode 100644 index 0000000..c69463e --- /dev/null +++ b/services/navigation.service.ts @@ -0,0 +1,19 @@ +import { fetchAPI } from "./api-client"; +import type { NavigationMenuValue } from "@/types/wagtail"; + +/** + * 根据 name(如 main / footer)获取导航菜单(apps.core.NavigationMenu Snippet)。 + * 未在后台配置对应菜单时返回 null,调用方应回退到静态导航项。 + */ +export async function getNavigationMenu( + name: string, +): Promise { + try { + return await fetchAPI( + `/api/v1/custom/core/navigation/?name=${encodeURIComponent(name)}`, + { tags: [`navigation:${name}`] }, + ); + } catch { + return null; + } +} diff --git a/services/partner.service.ts b/services/partner.service.ts new file mode 100644 index 0000000..f06b9b4 --- /dev/null +++ b/services/partner.service.ts @@ -0,0 +1,11 @@ +import { fetchAPI } from "./api-client"; +import type { PartnerValue } from "@/types/wagtail"; + +/** 获取合作伙伴/客户 Logo 列表(apps.core.Partner Snippet)。 */ +export async function getPartners(): Promise { + const res = await fetchAPI<{ items: PartnerValue[] }>( + "/api/v1/custom/core/partners/", + { tags: ["partners"] }, + ); + return res.items; +} diff --git a/services/site-settings.service.ts b/services/site-settings.service.ts new file mode 100644 index 0000000..7ace674 --- /dev/null +++ b/services/site-settings.service.ts @@ -0,0 +1,14 @@ +import { fetchAPI } from "./api-client"; +import type { SiteSettingsValue } from "@/types/wagtail"; + +/** 获取站点全局配置(apps.core.SiteSettings,wagtail.contrib.settings)。 */ +export async function getSiteSettings(): Promise { + try { + return await fetchAPI( + "/api/v1/custom/core/site-settings/", + { tags: ["site-settings"] }, + ); + } catch { + return null; + } +} diff --git a/services/team.service.ts b/services/team.service.ts new file mode 100644 index 0000000..60b0935 --- /dev/null +++ b/services/team.service.ts @@ -0,0 +1,11 @@ +import { fetchAPI } from "./api-client"; +import type { TeamMemberValue } from "@/types/wagtail"; + +/** 获取团队成员列表(apps.core.TeamMember Snippet)。 */ +export async function getTeamMembers(): Promise { + const res = await fetchAPI<{ items: TeamMemberValue[] }>( + "/api/v1/custom/core/team/", + { tags: ["team"] }, + ); + return res.items; +} diff --git a/services/testimonial.service.ts b/services/testimonial.service.ts new file mode 100644 index 0000000..30d7f60 --- /dev/null +++ b/services/testimonial.service.ts @@ -0,0 +1,11 @@ +import { fetchAPI } from "./api-client"; +import type { TestimonialValue } from "@/types/wagtail"; + +/** 获取客户证言列表(apps.core.Testimonial Snippet)。 */ +export async function getTestimonials(): Promise { + const res = await fetchAPI<{ items: TestimonialValue[] }>( + "/api/v1/custom/core/testimonials/", + { tags: ["testimonials"] }, + ); + return res.items; +} diff --git a/types/wagtail.ts b/types/wagtail.ts index 472420b..f3a0cb8 100644 --- a/types/wagtail.ts +++ b/types/wagtail.ts @@ -160,3 +160,53 @@ export interface SolutionPageDetail extends SolutionPageSummary { export interface SimpleContentPageDetail extends WagtailPageSummary { body: string; // richtext HTML } + +/** + * 全局 Snippet 类型(apps/core/models.py)。 + * 通过 /api/v1/custom/core/... 只读接口获取,非 Wagtail Page API。 + */ +export interface TeamMemberValue { + id: number; + name: string; + role: string; + bio: string; + photo: { url: string; title: string } | null; +} + +export interface TestimonialValue { + id: number; + quote: string; + author_name: string; + author_title: string; + author_photo: { url: string; title: string } | null; +} + +export interface PartnerValue { + id: number; + name: string; + website_url: string; + logo: { url: string; title: string } | null; +} + +export interface NavigationMenuItemValue { + label: string; + url: string; + open_in_new_tab: boolean; +} + +export interface NavigationMenuValue { + name: string; + items: NavigationMenuItemValue[]; +} + +export interface SiteSettingsValue { + company_name: string; + contact_phone: string; + contact_email: string; + address: string; + icp_number: string; + icp_url: string; + wechat_account: string; + weibo_url: string; + wechat_qrcode: { url: string; title: string } | null; +}