You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 10:37:07