如何在Next.js+Strapi项目首页按分类展示产品并控量
问题:在Next.js+Strapi首页按分类展示产品并控制数量
我正在用Next.js和Strapi开发产品展示页面,想要在首页按产品分类(比如相机、手机、耳机等)展示产品,并且能控制每个分类展示的产品数量。目前已经实现了产品分类功能,但不知道怎么在首页展示各分类的产品。
效果参考:
- 首页分类展示:按分类分组展示产品,每个分类下有指定数量的产品卡片
- 分类详情页:单个分类的产品列表及分页(已实现)
相关代码文件
productCard.js
import React from "react"; import Link from "next/link"; import Image from "next/image"; const ProductCard = ({ data: { attributes: p, id } }) => { return ( <div className="group rounded-md shadow-xl"> <Link href={`/product/${p.slug}`}> <div className="transform overflow-hidden cursor-pointer"> <Image className="w-full h-full object-cover group-hover:scale-110 duration-500" src={p.thumbnail?.data?.attributes.url || "/p1.png"} width={640} height={640} alt={p.name} /> </div> <div className="p-4 text-black/[0.9]"> <h2 className="text-sm md:text-md lg:text-lg font-medium"> {p.name.length > 10 ? `${p.name.slice(10, 60)}` : p.name} </h2> </div> </Link> </div> ); }; export default ProductCard;
Home.js(当前版本)
import HeroBanner from "@/components/HeroBanner"; import ProductCard from "@/components/ProductCard"; import Wrapper from "@/components/Wrapper"; import { fetchDataFromApi } from "@/utils/api"; export default function Home({ products }) { return ( <> <main> <Wrapper> <HeroBanner /> {/* products grid start */} <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 md:gap-3 lg:gap-4 my-14 md:px-0"> {products?.data?.map((product) => ( <ProductCard key={product?.id} data={product} /> ))} </div> {/* products grid end */} </Wrapper> <div className="min-h-screen hidden md:flex justify-center items-center bg-sea1 bg-fixed bg-cover bg-no-repeat w-[1920px] h-[260px] md:h-[480px] "></div> <div className="container mx-auto flex flex-col justify-center bg-white p-10"> <span className="py-4"> Mihaqi Textile adopts a "5-s management" approach, which not only continuously strengthens the quality of its excellent team, but also continuously improves the quality of the company's on-site environment to maintain the ability to deliver the highest quality products and services to customers at all times. Our professional team is dedicated to producing and providing the highest quality environmentally friendly textiles to renowned curtain brands around the world. </span> <hr className="mt-8" /> <span className="flex justify-center"> <img src="/icon/certifications.png" alt="" /> </span> </div> <div className="min-h-screen hidden md:flex justify-center items-center bg-sea2 bg-fixed bg-cover bg-no-repeat w-[1920px] h-[260px] md:h-[480px] "></div> </main> </> ); } export async function getStaticProps() { const products = await fetchDataFromApi("/api/products?populate=*"); return { props: { products }, }; }
category/slug.js
import ProductCard from "@/components/ProductCard"; import Wrapper from "@/components/Wrapper"; import { fetchDataFromApi } from "@/utils/api"; import React, { useState, useEffect } from "react"; import useSWR from "swr"; import { useRouter } from "next/router"; const maxResult = 8; const Category = ({ category, products, slug }) => { const [pageIndex, setPageIndex] = useState(1); const { query } = useRouter(); useEffect(() => { setPageIndex(1); }, [query]); const { data, error, isLoading } = useSWR( `/api/products?populate=*&[filters][category][slug][$eq]=${slug}&pagination[page]=${pageIndex}&pagination[pageSize]=${maxResult}`, fetchDataFromApi, { fallbackData: products, }, ); return ( <div className="w-full md:py-20"> <Wrapper> <div className="text-center max-w-[800px] mx-auto mt-8 md:mt-0"> <div className="text-[28px] md:text-[34px] mb-5"> {category?.data?.[0]?.attributes?.name} </div> </div> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5 my-14 px-5 md:px-0"> {data?.data?.map((product) => ( <ProductCard key={product?.id} data={product} /> ))} </div> {/* PAGINATION BUTTONS START */} {data?.meta?.pagination?.total > maxResult && ( <div className="flex gap-3 items-center justify-center my-16 md:my-0"> <button className={`rounded py-2 px-4 bg-black text-white disabled:bg-gray-200 disabled:text-gray-500`} disabled={pageIndex === 1} onClick={() => setPageIndex(pageIndex - 1)} > Previous </button> <span className="font-bold">{`${pageIndex} of ${ data && data.meta.pagination.pageCount }`}</span> <button className={`rounded py-2 px-4 bg-black text-white disabled:bg-gray-200 disabled:text-gray-500`} disabled={pageIndex === (data && data.meta.pagination.pageCount)} onClick={() => setPageIndex(pageIndex + 1)} > Next </button> </div> )} {/* PAGINATION BUTTONS END */} {isLoading && ( <div className="absolute top-0 left-0 w-full bg-white/[0.5] flex flex-col gap-5 justify-center items-center"> <span className="text-2xl">Loading...</span> </div> )} </Wrapper> </div> ); }; export default Category; export async function getStaticPaths() { const category = await fetchDataFromApi("/api/categories?populate=*"); const paths = category.data.map((c) => ({ params: { slug: c.attributes.slug, }, })); return { paths, fallback: false, }; } export async function getStaticProps({ params: { slug } }) { const category = await fetchDataFromApi( `/api/categories?filters[slug][$eq]=${slug}`, ); const products = await fetchDataFromApi( `/api/products?populate=*&[filters][category][slug][$eq]=${slug}&pagination[page]=2&pagination[pageSize]=${maxResult}`, ); return { props: { category, products, slug, }, }; }
解决方案
1. 修改Home.js的getStaticProps,获取分类及对应产品
先定义每个分类要展示的产品数量,再批量获取所有分类及对应产品数据:
export async function getStaticProps() { const PER_CATEGORY_LIMIT = 4; // 自定义每个分类展示的产品数量 // 获取所有分类数据 const categoriesRes = await fetchDataFromApi("/api/categories?populate=*"); // 为每个分类请求指定数量的产品 const categoriesWithProducts = await Promise.all( categoriesRes.data.map(async (category) => { const productsRes = await fetchDataFromApi( `/api/products?populate=*&[filters][category][slug][$eq]=${category.attributes.slug}&pagination[pageSize]=${PER_CATEGORY_LIMIT}` ); return { ...category, products: productsRes.data }; }) ); return { props: { categories: categoriesWithProducts }, }; }
2. 修改Home组件渲染逻辑,按分类展示产品
遍历分类数据,渲染分类标题、产品卡片及查看更多链接:
export default function Home({ categories }) { return ( <> <main> <Wrapper> <HeroBanner /> {/* 按分类分组展示产品 */} {categories.map((category) => { // 跳过没有产品的分类 if (category.products.length === 0) return null; return ( <section key={category.id} className="my-16"> {/* 分类标题 */} <h2 className="text-xl md:text-2xl font-bold mb-6"> {category.attributes.name} </h2> {/* 产品网格 */} <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 md:gap-3 lg:gap-4"> {category.products.map((product) => ( <ProductCard key={product.id} data={product} /> ))} </div> {/* 查看更多链接 */} <div className="mt-4 text-right"> <Link href={`/category/${category.attributes.slug}`} className="text-blue-600 hover:underline"> 查看更多 → </Link> </div> </section> ) })} </Wrapper> {/* 保留原有其他页面内容 */} <div className="min-h-screen hidden md:flex justify-center items-center bg-sea1 bg-fixed bg-cover bg-no-repeat w-[1920px] h-[260px] md:h-[480px] "></div> <div className="container mx-auto flex flex-col justify-center bg-white p-10"> <span className="py-4"> Mihaqi Textile adopts a "5-s management" approach, which not only continuously strengthens the quality of its excellent team, but also continuously improves the quality of the company's on-site environment to maintain the ability to deliver the highest quality products and services to customers at all times. Our professional team is dedicated to producing and providing the highest quality environmentally friendly textiles to renowned curtain brands around the world. </span> <hr className="mt-8" /> <span className="flex justify-center"> <img src="/icon/certifications.png" alt="认证图标" /> </span> </div> <div className="min-h-screen hidden md:flex justify-center items-center bg-sea2 bg-fixed bg-cover bg-no-repeat w-[1920px] h-[260px] md:h-[480px] "></div> </main> </> ); }
内容的提问来源于stack exchange,提问作者fumei mai
相关产品推荐
相关产品推荐

