feat: add solutions list/detail pages
- types: SolutionPageSummary/Detail - services/solution.service.ts - app/solutions: list + detail pages - Header nav: add 解决方案 link
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { getSolutionBySlug } from "@/services/solution.service";
|
||||||
|
import { BlockRenderer } from "@/blocks/BlockRenderer";
|
||||||
|
|
||||||
|
interface SolutionDetailPageProps {
|
||||||
|
params: Promise<{ slug: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: SolutionDetailPageProps): Promise<Metadata> {
|
||||||
|
const { slug } = await params;
|
||||||
|
const solution = await getSolutionBySlug(slug).catch(() => null);
|
||||||
|
return { title: solution?.title ?? "解决方案未找到" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function SolutionDetailPage({
|
||||||
|
params,
|
||||||
|
}: SolutionDetailPageProps) {
|
||||||
|
const { slug } = await params;
|
||||||
|
const solution = await getSolutionBySlug(slug).catch(() => null);
|
||||||
|
|
||||||
|
if (!solution) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="mx-auto max-w-3xl px-6 py-24">
|
||||||
|
<h1 className="mb-2 text-4xl font-bold text-slate-900">
|
||||||
|
{solution.title}
|
||||||
|
</h1>
|
||||||
|
{solution.industry && (
|
||||||
|
<p className="mb-12 text-sm text-slate-500">{solution.industry}</p>
|
||||||
|
)}
|
||||||
|
<BlockRenderer blocks={solution.body} />
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { getSolutionList } from "@/services/solution.service";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "解决方案",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function SolutionListPage() {
|
||||||
|
const solutions = await getSolutionList().catch(() => []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-4xl px-6 py-24">
|
||||||
|
<h1 className="mb-12 text-4xl font-bold text-slate-900">解决方案</h1>
|
||||||
|
<ul className="space-y-6">
|
||||||
|
{solutions.map((solution) => (
|
||||||
|
<li key={solution.id} className="border-b border-slate-100 pb-6">
|
||||||
|
<Link
|
||||||
|
href={`/solutions/${solution.meta.slug}`}
|
||||||
|
className="text-2xl font-semibold text-slate-900 hover:text-primary"
|
||||||
|
>
|
||||||
|
{solution.title}
|
||||||
|
</Link>
|
||||||
|
{solution.industry && (
|
||||||
|
<p className="mt-1 text-sm text-slate-500">{solution.industry}</p>
|
||||||
|
)}
|
||||||
|
{solution.summary && (
|
||||||
|
<p className="mt-2 text-slate-600">{solution.summary}</p>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{solutions.length === 0 && (
|
||||||
|
<p className="text-slate-500">暂无解决方案,或后端服务未连接。</p>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import Link from "next/link";
|
|||||||
const navItems = [
|
const navItems = [
|
||||||
{ title: "首页", href: "/" },
|
{ title: "首页", href: "/" },
|
||||||
{ title: "产品", href: "/products" },
|
{ title: "产品", href: "/products" },
|
||||||
|
{ title: "解决方案", href: "/solutions" },
|
||||||
{ title: "案例", href: "/cases" },
|
{ title: "案例", href: "/cases" },
|
||||||
{ title: "博客", href: "/blog" },
|
{ title: "博客", href: "/blog" },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { fetchAPI } from "./api-client";
|
||||||
|
import type {
|
||||||
|
SolutionPageDetail,
|
||||||
|
SolutionPageSummary,
|
||||||
|
WagtailListResponse,
|
||||||
|
} from "@/types/wagtail";
|
||||||
|
|
||||||
|
/** 获取解决方案列表。 */
|
||||||
|
export async function getSolutionList(): Promise<SolutionPageSummary[]> {
|
||||||
|
const res = await fetchAPI<WagtailListResponse<SolutionPageSummary>>(
|
||||||
|
"/api/v2/pages/?type=solutions.SolutionPage&fields=industry,summary",
|
||||||
|
{ tags: ["solution-list"] },
|
||||||
|
);
|
||||||
|
return res.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据 slug 获取解决方案详情。 */
|
||||||
|
export async function getSolutionBySlug(
|
||||||
|
slug: string,
|
||||||
|
): Promise<SolutionPageDetail | null> {
|
||||||
|
const res = await fetchAPI<WagtailListResponse<SolutionPageDetail>>(
|
||||||
|
`/api/v2/pages/?type=solutions.SolutionPage&slug=${encodeURIComponent(slug)}&fields=body,industry,summary&limit=1`,
|
||||||
|
{ tags: [`solution:${slug}`] },
|
||||||
|
);
|
||||||
|
return res.items[0] ?? null;
|
||||||
|
}
|
||||||
@@ -147,3 +147,12 @@ export interface CaseStudyPageSummary extends WagtailPageSummary {
|
|||||||
export interface CaseStudyPageDetail extends CaseStudyPageSummary {
|
export interface CaseStudyPageDetail extends CaseStudyPageSummary {
|
||||||
body: StreamFieldBlock[];
|
body: StreamFieldBlock[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SolutionPageSummary extends WagtailPageSummary {
|
||||||
|
industry?: string;
|
||||||
|
summary?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SolutionPageDetail extends SolutionPageSummary {
|
||||||
|
body: StreamFieldBlock[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user