增加ISR静态生成功能

This commit is contained in:
jingrow 2025-06-21 02:41:04 +08:00
parent 4a4270daa5
commit a0d2f9529f
77 changed files with 179 additions and 67 deletions

View File

@ -1,11 +1,38 @@
import Banner from "@/components/banner/Banner"; import Banner from "@/components/banner/Banner";
import Category from "@/components/sidebar/Category"; import Category from "@/components/sidebar/Category";
import Pagination1 from "@/components/common/Pagination1";
import { getSiteSettings } from "@/utlis/siteSettings"; import { getSiteSettings } from "@/utlis/siteSettings";
import ListPageTemplate from "@/components/common/ListPageTemplate";
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import DynamicListPage from "@/components/common/DynamicListPage";
import { Suspense } from 'react';
const LoadingSpinner = () => (
<div className="flex justify-center items-center p-8">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
);
export const revalidate = 3600; export const revalidate = 3600;
export const dynamicParams = true;
export async function generateStaticParams() {
try {
const res = await fetch(`${process.env.SITE_URL}/api/get-all-slugs`);
if (!res.ok) {
throw new Error(`Failed to fetch slugs: ${res.status}`);
}
const slugs = await res.json();
if (!Array.isArray(slugs)) {
console.error("generateStaticParams: received non-array from /api/get-all-slugs", slugs);
return [];
}
return slugs.map(slug => ({
slug: slug,
}));
} catch (error) {
console.error("Could not generate static params:", error);
return [];
}
}
export async function generateMetadata({ params }) { export async function generateMetadata({ params }) {
const resolvedParams = await params; const resolvedParams = await params;
@ -35,20 +62,18 @@ export async function generateMetadata({ params }) {
}; };
} }
export default async function DynamicPage({ params, searchParams }) { export default async function DynamicPage({ params }) {
const resolvedParams = await params; const resolvedParams = await params;
const resolvedSearchParams = await searchParams;
const slugArr = resolvedParams.slug; const slugArr = resolvedParams.slug;
const currentPage = Number(resolvedSearchParams?.page) || 1;
// Get global site settings
const siteSettings = await getSiteSettings(process.env.SITE_URL); const siteSettings = await getSiteSettings(process.env.SITE_URL);
const pageSize = Number(siteSettings.page_size) || 12; const pageSize = Number(siteSettings.page_size) || 12;
//
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, { const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slug_list: slugArr, page: currentPage, page_size: pageSize }) body: JSON.stringify({ slug_list: slugArr, page: 1, page_size: pageSize })
}); });
const result = await res.json(); const result = await res.json();
const { data, error, total } = result; const { data, error, total } = result;
@ -60,26 +85,9 @@ export default async function DynamicPage({ params, searchParams }) {
const bannerComponentName = 'Banner-' + (slugArr[0] || 'home'); const bannerComponentName = 'Banner-' + (slugArr[0] || 'home');
const categoryComponentName = 'Category-' + (slugArr[0] || 'home'); const categoryComponentName = 'Category-' + (slugArr[0] || 'home');
// Infer pagetype and name from slugArr
const pagetype = slugArr?.[0] || '';
const name = slugArr?.[1] || '';
if (Array.isArray(data)) { if (Array.isArray(data)) {
const currentPath = '/' + (Array.isArray(slugArr) ? slugArr.join('/') : ''); const currentPath = '/' + (Array.isArray(slugArr) ? slugArr.join('/') : '');
const lastSlug = Array.isArray(slugArr) ? slugArr[slugArr.length - 1] : ''; const listColumns = 4;
const totalPages = Math.ceil((total || 0) / pageSize);
// Adapt template required fields
const listItems = data.map(item => ({
slug: item.slug,
title: item.title,
image: item.image || item.cover || item.img || '',
additional_title: item.additional_title || '',
subtitle: item.subtitle || item.content || '',
}));
// 4
const listColumns = 4; //
return ( return (
<> <>
@ -89,14 +97,16 @@ export default async function DynamicPage({ params, searchParams }) {
<div className="flex flex-col md:flex-row mx-[-15px] xl:mx-[-35px] lg:mx-[-20px]"> <div className="flex flex-col md:flex-row mx-[-15px] xl:mx-[-35px] lg:mx-[-20px]">
<Category componentName={categoryComponentName} /> <Category componentName={categoryComponentName} />
<div className="flex-1 min-w-0 !px-[15px] max-w-full md:!px-[20px] lg:!px-[20px] xl:!px-[35px]"> <div className="flex-1 min-w-0 !px-[15px] max-w-full md:!px-[20px] lg:!px-[20px] xl:!px-[35px]">
<ListPageTemplate items={listItems} basePath={currentPath} columns={listColumns} /> <Suspense fallback={<LoadingSpinner />}>
<div className="mt-8"> <DynamicListPage
<Pagination1 initialItems={data}
currentPage={currentPage} slugArr={slugArr}
totalPages={totalPages}
basePath={currentPath} basePath={currentPath}
columns={listColumns}
pageSize={pageSize}
totalItems={total}
/> />
</div> </Suspense>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,26 @@
import axios from 'axios';
const JINGROW_SERVER_URL = process.env.JINGROW_SERVER_URL;
export async function GET() {
try {
const response = await axios.get(
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_all_slugs`
);
const slugs = response.data.message?.data;
if (!Array.isArray(slugs)) {
console.error('API did not return an array of slugs:', response.data);
return Response.json({ error: '返回的slugs格式不正确' }, { status: 500 });
}
return Response.json(slugs);
} catch (error) {
console.error('Error fetching slugs:', error.message, error?.response?.data);
return Response.json(
{ error: '获取slugs失败', detail: error?.response?.data || error.message },
{ status: 500 }
);
}
}

View File

@ -1,16 +1,24 @@
import Banner from "@/components/banner/Banner"; import Banner from "@/components/banner/Banner";
import Category from "@/components/sidebar/Category"; import Category from "@/components/sidebar/Category";
import Pagination1 from "@/components/common/Pagination1";
import { getSiteSettings } from "@/utlis/siteSettings"; import { getSiteSettings } from "@/utlis/siteSettings";
import ListPageTemplate from "@/components/common/ListPageTemplate";
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import DynamicListPage from "@/components/common/DynamicListPage";
import { Suspense } from 'react';
const baseSlug = 'products'; const baseSlug = 'products';
const LoadingSpinner = () => (
<div className="flex justify-center items-center p-8">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
);
export const revalidate = 3600; export const revalidate = 3600;
export async function generateMetadata({ params }) { export async function generateMetadata({ params }) {
const slugArr = [baseSlug, ...(params.slug ? (Array.isArray(params.slug) ? params.slug : [params.slug]) : [])]; const resolvedParams = await params;
const slug = resolvedParams.slug || [];
const slugArr = [baseSlug, ...(Array.isArray(slug) ? slug : [slug])];
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, { const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -36,15 +44,19 @@ export async function generateMetadata({ params }) {
}; };
} }
export default async function Page({ params, searchParams }) { export default async function Page({ params }) {
const slugArr = [baseSlug, ...(params.slug ? (Array.isArray(params.slug) ? params.slug : [params.slug]) : [])]; const resolvedParams = await params;
const currentPage = Number(searchParams?.page) || 1; const slug = resolvedParams.slug || [];
const slugArr = [baseSlug, ...(Array.isArray(slug) ? slug : [slug])];
const siteSettings = await getSiteSettings(process.env.SITE_URL); const siteSettings = await getSiteSettings(process.env.SITE_URL);
const pageSize = Number(siteSettings.page_size) || 12; const pageSize = Number(siteSettings.page_size) || 12;
// Fetch only page 1 for initial static render
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, { const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slug_list: slugArr, page: currentPage, page_size: pageSize }) body: JSON.stringify({ slug_list: slugArr, page: 1, page_size: pageSize })
}); });
const result = await res.json(); const result = await res.json();
const { data, error, total } = result; const { data, error, total } = result;
@ -54,14 +66,8 @@ export default async function Page({ params, searchParams }) {
const bannerComponentName = 'Banner-' + baseSlug; const bannerComponentName = 'Banner-' + baseSlug;
const categoryComponentName = 'Category-' + baseSlug; const categoryComponentName = 'Category-' + baseSlug;
if (Array.isArray(data)) { if (Array.isArray(data)) {
const currentPath = '/' + [baseSlug, ...(params.slug ? (Array.isArray(params.slug) ? params.slug : [params.slug]) : [])].join('/'); const currentPath = '/' + slugArr.join('/');
const totalPages = Math.ceil((total || 0) / pageSize); const listColumns = 4;
const listItems = data.map(item => ({
slug: item.slug,
title: item.title,
image: item.image || item.cover || item.img || '',
content: item.content || item.description || '',
}));
return ( return (
<> <>
<Banner componentName={bannerComponentName} /> <Banner componentName={bannerComponentName} />
@ -70,14 +76,16 @@ export default async function Page({ params, searchParams }) {
<div className="flex flex-col md:flex-row mx-[-15px] xl:mx-[-35px] lg:mx-[-20px]"> <div className="flex flex-col md:flex-row mx-[-15px] xl:mx-[-35px] lg:mx-[-20px]">
<Category componentName={categoryComponentName} /> <Category componentName={categoryComponentName} />
<div className="flex-1 min-w-0 !px-[15px] max-w-full md:!px-[20px] lg:!px-[20px] xl:!px-[35px]"> <div className="flex-1 min-w-0 !px-[15px] max-w-full md:!px-[20px] lg:!px-[20px] xl:!px-[35px]">
<ListPageTemplate items={listItems} basePath={currentPath} /> <Suspense fallback={<LoadingSpinner />}>
<div className="mt-8"> <DynamicListPage
<Pagination1 initialItems={data}
currentPage={currentPage} slugArr={slugArr}
totalPages={totalPages}
basePath={currentPath} basePath={currentPath}
columns={listColumns}
pageSize={pageSize}
totalItems={total}
/> />
</div> </Suspense>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,77 @@
'use client';
import { useState, useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
import ListPageTemplate from '@/components/common/ListPageTemplate';
import Pagination1 from '@/components/common/Pagination1';
import axios from 'axios';
const LoadingSpinner = () => (
<div className="flex justify-center items-center p-8">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
);
export default function DynamicListPage({ initialItems, slugArr, basePath, columns, pageSize, totalItems }) {
const [items, setItems] = useState(initialItems);
const [total, setTotal] = useState(totalItems);
const [isLoading, setIsLoading] = useState(false);
const searchParams = useSearchParams();
const page = searchParams.get('page');
const currentPage = Number(page) || 1;
useEffect(() => {
const fetchPageData = async () => {
if (currentPage === 1) {
setItems(initialItems);
setTotal(totalItems);
return;
}
setIsLoading(true);
try {
const res = await axios.post('/api/get-page-data', {
slug_list: slugArr,
page: currentPage,
page_size: pageSize,
});
setItems(res.data.data);
setTotal(res.data.total);
} catch (error) {
console.error('Failed to fetch page data:', error);
// Optionally, handle the error in the UI
} finally {
setIsLoading(false);
}
};
fetchPageData();
}, [currentPage, slugArr, pageSize, initialItems, totalItems]);
const listItems = items.map(item => ({
...item,
slug: item.slug,
title: item.title,
image: item.image || item.cover || item.img || '',
additional_title: item.additional_title || '',
subtitle: item.subtitle || item.content || '',
}));
const totalPages = Math.ceil((total || 0) / pageSize);
return (
<>
{isLoading ? (
<LoadingSpinner />
) : (
<ListPageTemplate items={listItems} basePath={basePath} columns={columns} />
)}
<div className="mt-8">
<Pagination1
currentPage={currentPage}
totalPages={totalPages}
basePath={basePath}
/>
</div>
</>
);
}

View File

@ -1,5 +1,5 @@
"use client"; "use client";
import { useEffect, useState, useRef } from "react"; import { useEffect, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react"; import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination, Grid } from "swiper/modules"; import { Navigation, Pagination, Grid } from "swiper/modules";
import Link from "next/link"; import Link from "next/link";
@ -24,9 +24,6 @@ export default function SwiperItems(props) {
const rows = (data?.p1 && !isNaN(Number(data.p1))) ? Number(data.p1) : (props.rows || 1); const rows = (data?.p1 && !isNaN(Number(data.p1))) ? Number(data.p1) : (props.rows || 1);
const button_text = data?.button_text || props.button_text; const button_text = data?.button_text || props.button_text;
const prevRef = useRef(null);
const nextRef = useRef(null);
useEffect(() => { useEffect(() => {
async function fetchComponentData() { async function fetchComponentData() {
try { try {
@ -192,13 +189,9 @@ export default function SwiperItems(props) {
slidesPerGroup={columns * rows} slidesPerGroup={columns * rows}
grid={{ rows }} grid={{ rows }}
pagination={{ clickable: true, el: ".spdb15" }} pagination={{ clickable: true, el: ".spdb15" }}
navigation={{ prevEl: prevRef.current, nextEl: nextRef.current }} navigation={{
onInit={swiper => { prevEl: '.swiper-nav-prev-swiperitems',
// Swiper 8+ ref.current nextEl: '.swiper-nav-next-swiperitems'
swiper.params.navigation.prevEl = prevRef.current;
swiper.params.navigation.nextEl = nextRef.current;
swiper.navigation.init();
swiper.navigation.update();
}} }}
breakpoints={{ breakpoints={{
0: { slidesPerView: 1, slidesPerGroup: 1, grid: { rows: 1 } }, 0: { slidesPerView: 1, slidesPerGroup: 1, grid: { rows: 1 } },
@ -249,8 +242,7 @@ export default function SwiperItems(props) {
</Swiper> </Swiper>
{/* 美化后的导航按钮 */} {/* 美化后的导航按钮 */}
<button <button
ref={prevRef} className="swiper-nav-prev-swiperitems absolute left-[-32px] top-1/2 z-20 -translate-y-1/2 w-10 h-10 flex items-center justify-center !rounded-full border-2 border-[#e5e7eb] bg-transparent transition-all duration-200 hover:border-[#1fc76f] group"
className="absolute left-[-32px] top-1/2 z-20 -translate-y-1/2 w-10 h-10 flex items-center justify-center !rounded-full border-2 border-[#e5e7eb] bg-transparent transition-all duration-200 hover:border-[#1fc76f] group"
style={{ color: '#b0b7c3' }} style={{ color: '#b0b7c3' }}
aria-label="上一页" aria-label="上一页"
> >
@ -259,8 +251,7 @@ export default function SwiperItems(props) {
</svg> </svg>
</button> </button>
<button <button
ref={nextRef} className="swiper-nav-next-swiperitems absolute right-[-32px] top-1/2 z-20 -translate-y-1/2 w-10 h-10 flex items-center justify-center !rounded-full border-2 border-[#e5e7eb] bg-transparent transition-all duration-200 hover:border-[#1fc76f] group"
className="absolute right-[-32px] top-1/2 z-20 -translate-y-1/2 w-10 h-10 flex items-center justify-center !rounded-full border-2 border-[#e5e7eb] bg-transparent transition-all duration-200 hover:border-[#1fc76f] group"
style={{ color: '#b0b7c3' }} style={{ color: '#b0b7c3' }}
aria-label="下一页" aria-label="下一页"
> >

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
public/files/about4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
public/files/bg7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
public/files/bg8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
public/files/bg9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

BIN
public/files/bg95fddcf.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
public/files/test001.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB