app里面的路由实现分组管理
This commit is contained in:
parent
9345e8fc3c
commit
6b147778e5
225
app/(presentation)/presentation/[...slug]/page.jsx
Normal file
225
app/(presentation)/presentation/[...slug]/page.jsx
Normal file
@ -0,0 +1,225 @@
|
||||
import { getSiteSettings } from "@/utils/data";
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Suspense } from 'react';
|
||||
import { getPageData, getAllSlugs } from "@/utils/data";
|
||||
|
||||
const LoadingSpinner = () => (
|
||||
<div className="flex justify-center items-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-b-4 border-blue-500"></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Using a static value to comply with Next.js 15 build requirements.
|
||||
// On-demand revalidation will be handled via the API route.
|
||||
export const revalidate = 3600;
|
||||
|
||||
export async function generateStaticParams() {
|
||||
try {
|
||||
const slugs = await getAllSlugs();
|
||||
return slugs.map(slug => ({
|
||||
slug: slug,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Failed to generate static params:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const resolvedParams = await params;
|
||||
const slugArr = resolvedParams.slug;
|
||||
const { data, error, page_info } = await getPageData({
|
||||
slug_list: slugArr,
|
||||
downloadFiles: false // Do not download files for metadata
|
||||
});
|
||||
const siteSettings = await getSiteSettings();
|
||||
const siteTitle = siteSettings.site_title || '';
|
||||
const siteName = siteSettings.site_name || '';
|
||||
const siteNameInPageTitles = siteSettings.site_name_in_page_titles || 'None';
|
||||
|
||||
let title = '';
|
||||
if (error) {
|
||||
title = error.title || 'Page Error';
|
||||
return {
|
||||
title,
|
||||
description: error.message || '',
|
||||
};
|
||||
}
|
||||
if (Array.isArray(data) && page_info) {
|
||||
title = page_info.meta_title || page_info.title || '';
|
||||
if (siteName && title) {
|
||||
if (siteNameInPageTitles === 'After') {
|
||||
title = `${title} - ${siteName}`;
|
||||
} else if (siteNameInPageTitles === 'Before') {
|
||||
title = `${siteName} - ${title}`;
|
||||
}
|
||||
}
|
||||
return {
|
||||
title,
|
||||
description: page_info.meta_description || '',
|
||||
};
|
||||
}
|
||||
title = data?.meta_title || data?.title || '';
|
||||
if (siteName && title) {
|
||||
if (siteNameInPageTitles === 'After') {
|
||||
title = `${title} - ${siteName}`;
|
||||
} else if (siteNameInPageTitles === 'Before') {
|
||||
title = `${siteName} - ${title}`;
|
||||
}
|
||||
}
|
||||
return {
|
||||
title,
|
||||
description: data?.meta_description || '',
|
||||
};
|
||||
}
|
||||
|
||||
export default async function DynamicPage({ params, searchParams }) {
|
||||
const resolvedParams = await params;
|
||||
const slugArr = resolvedParams.slug;
|
||||
const siteSettings = await getSiteSettings();
|
||||
const pageSize = Number(siteSettings.page_size) || 12;
|
||||
|
||||
// 始终获取第一页的数据用于静态生成
|
||||
const { data, error, total } = await getPageData({
|
||||
slug_list: slugArr,
|
||||
page: 1,
|
||||
page_size: pageSize,
|
||||
downloadFiles: true // Download files for page rendering
|
||||
});
|
||||
|
||||
if (error) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// 列表页面 - 全屏展示
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{data.map((item, index) => (
|
||||
<div key={index} className="bg-white rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-shadow duration-300">
|
||||
{item.image && (
|
||||
<div className="aspect-video overflow-hidden">
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.title || 'Presentation item'}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-semibold text-gray-800 mb-2">
|
||||
{item.title}
|
||||
</h3>
|
||||
{item.subtitle && (
|
||||
<p className="text-gray-600 text-sm mb-4 line-clamp-3">
|
||||
{item.subtitle}
|
||||
</p>
|
||||
)}
|
||||
{item.description && (
|
||||
<div className="text-gray-700 text-sm mb-4 line-clamp-4">
|
||||
{item.description}
|
||||
</div>
|
||||
)}
|
||||
{item.button_text && item.button_link && (
|
||||
<a
|
||||
href={item.button_link}
|
||||
className="inline-block bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors duration-200"
|
||||
>
|
||||
{item.button_text}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
} else if (data) {
|
||||
// 单页内容 - 全屏展示
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
|
||||
{/* 主图片区域 */}
|
||||
{data.image && (
|
||||
<div className="w-full h-96 md:h-[500px] overflow-hidden">
|
||||
<img
|
||||
src={data.image}
|
||||
alt={data.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="p-8">
|
||||
{/* 标题 */}
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-gray-800 mb-6">
|
||||
{data.title}
|
||||
</h1>
|
||||
|
||||
{/* 副标题 */}
|
||||
{data.subtitle && (
|
||||
<div className="bg-blue-50 border-l-4 border-blue-500 p-4 mb-6 rounded-r-lg">
|
||||
<p className="text-gray-700 whitespace-pre-line">
|
||||
{data.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 主内容 */}
|
||||
<div className="prose max-w-none text-gray-700 mb-8 prose-img:max-w-full prose-img:h-auto prose-table:border prose-table:border-gray-300 prose-th:border prose-th:border-gray-300 prose-td:border prose-td:border-gray-300 prose-th:bg-gray-50 prose-table:rounded-lg prose-table:overflow-hidden"
|
||||
dangerouslySetInnerHTML={{ __html: data.content || '' }} />
|
||||
|
||||
{/* 附加内容 */}
|
||||
{data.additional_content && (
|
||||
<div className="border-t pt-8">
|
||||
<h2 className="text-2xl font-semibold text-gray-800 mb-4">附加信息</h2>
|
||||
<div className="prose max-w-none text-gray-700 prose-img:max-w-full prose-img:h-auto prose-table:border prose-table:border-gray-300 prose-th:border prose-th:border-gray-300 prose-td:border prose-td:border-gray-300 prose-th:bg-gray-50 prose-table:rounded-lg prose-table:overflow-hidden"
|
||||
dangerouslySetInnerHTML={{ __html: data.additional_content }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 按钮区域 */}
|
||||
{(data.button_text || data.button_1_text || data.button_2_text) && (
|
||||
<div className="flex flex-wrap gap-4 mt-8 pt-6 border-t">
|
||||
{data.button_text && data.button_link && (
|
||||
<a
|
||||
href={data.button_link}
|
||||
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors duration-200 font-medium"
|
||||
>
|
||||
{data.button_text}
|
||||
</a>
|
||||
)}
|
||||
{data.button_1_text && data.button_1_link && (
|
||||
<a
|
||||
href={data.button_1_link}
|
||||
className="bg-gray-600 text-white px-6 py-3 rounded-lg hover:bg-gray-700 transition-colors duration-200 font-medium"
|
||||
>
|
||||
{data.button_1_text}
|
||||
</a>
|
||||
)}
|
||||
{data.button_2_text && data.button_2_link && (
|
||||
<a
|
||||
href={data.button_2_link}
|
||||
className="border border-gray-300 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-50 transition-colors duration-200 font-medium"
|
||||
>
|
||||
{data.button_2_text}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
24
app/(presentation)/presentation/layout.jsx
Normal file
24
app/(presentation)/presentation/layout.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import Context from "@/context/Context";
|
||||
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&family=Manrope:wght@400;500;700"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@1,300;1,400;1,500;1,600;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<Context>
|
||||
{children}
|
||||
</Context>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1,4 +1,4 @@
|
||||
import "../public/assets/style.css";
|
||||
import "@/public/assets/style.css";
|
||||
import "photoswipe/dist/photoswipe.css";
|
||||
import Context from "@/context/Context";
|
||||
import ProgressWrap from "@/components/common/ProgressWrap";
|
||||
@ -1,7 +1,7 @@
|
||||
@import url("./fonts/unicons/unicons.css");
|
||||
@import url("./css/plugins.css");
|
||||
@import url("./css/icon.css");
|
||||
@import url("../../app/globals.css");
|
||||
@import url("../../app/(website)/globals.css");
|
||||
@import url("./css/colors/aqua.css");
|
||||
@import url("./css/colors/fuchsia.css");
|
||||
@import url("./css/colors/grape.css");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user