Procházet zdrojové kódy

首页样式调整

zhangzf před 1 týdnem
rodič
revize
7e62498d9b

+ 72 - 86
src/app/(public)/_components/HomeBestSellers.tsx

@@ -1,16 +1,20 @@
-
-import { Swiper, SwiperSlide } from "swiper/react";
-import { Navigation, Pagination } from "swiper/modules";
+// import { Swiper, SwiperSlide } from "swiper/react";
+// import { Navigation, Pagination } from "swiper/modules";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
 // 导入Swiper基础样式
 import "swiper/css";
 import "swiper/css/navigation";
-import type { HomeProduct } from "@/types/home/type";
+import type { HomeProduct,HomeSection } from "@/types/home/type";
 import Link from "next/link";
+import Image from "next/image";
+interface jinGangSectionProps{
+  bestSellerSection:HomeSection |undefined;
+}
 interface HomeBestSellersProps {
   bestSellers: HomeProduct[];
 }
-const HomeBestSellers = ({ bestSellers }: HomeBestSellersProps) => {
+const HomeBestSellers = ({ bestSellerSection }: jinGangSectionProps) => {
+  const bestSellers: HomeProduct[] = bestSellerSection?.products ?? [];
   if (!bestSellers || bestSellers.length === 0) return null;
   // 4. 真实数据按每6个拆分一组,替换原来的模拟拆分逻辑
   const chunkSize = 6;
@@ -19,78 +23,63 @@ const HomeBestSellers = ({ bestSellers }: HomeBestSellersProps) => {
     productBlocks.push(bestSellers.slice(i, i + chunkSize));
   }
   return (
-    <div className="w-full max-w-[1200px] mx-auto px-4 py-8">
+    <div className="w-full  mx-auto px-12 py-12">
       {/* 标题 */}
-      <h2 className="text-2xl font-bold mb-6">Best Sellers</h2>
+      <h2 className="text-4xl text-center font-bold leading-ly-46 mb-6">{bestSellerSection?.title}</h2>
 
-      {/* Swiper容器 */}
-      <Swiper
-        slidesPerView={1}
-        spaceBetween={20}
-        modules={[Navigation, Pagination]}
-        pagination={{
-          el: ".custom-pagination",
-          clickable: true,
-          renderBullet: (index, className) => {
-            // 所有分页默认底色为 #E5E5E5
-            return `<span class="${className} w-6 h-[5px] rounded-full mx-1 bg-[#E5E5E5]"></span>`;
-          },
-          // 激活时强制覆盖底色,加 !important 提高权重
-          bulletActiveClass: "!bg-[#333]",
-        }}
-        className="relative"
-      >
-        {/* 渲染2个滑动块 */}
-        {productBlocks.map((block, blockIndex) => (
-          <SwiperSlide key={blockIndex} className="!px-0">
-            {/* 每个块:6个产品,2列布局 */}
-            <div className="grid grid-cols-2 gap-4">
-              {block.map((product) => (
-                <div key={product.id} className="flex flex-col gap-2">
-                  {/* 产品图片 */}
-                  <img
-                    src={product.image_url}
-                    alt={product.name}
-                    className="w-full h-auto object-cover rounded-sm"
-                  />
-                  {/* 产品标题 */}
-                  <Link href={"/wiggins-ready-go-250-density-popping-curly-hd-lace-front-wigs-medium-length-human-hair-wigs.html"} className="text-sm text-gray-800 truncate">
-                    {product.name}
-                  </Link>
-                  {/* 评分 + 评论数 */}
-                  <div className="flex items-center gap-1">
-                    <div className="flex">
-                      {/* 星级图标(简化版,可替换为真实星星图标) */}
-                      {[1, 2, 3, 4, 5].map((star) => (
-                        <svg
-                          key={star}
-                          className="w-4 h-4 text-yellow-400"
-                          fill="currentColor"
-                          viewBox="0 0 20 20"
-                        >
-                          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
-                        </svg>
-                      ))}
-                    </div>
-                    <span className="text-xs text-gray-600">
-                      {product.review_count}
-                    </span>
-                  </div>
-                  {/* 价格 + 购物车图标 */}
-                  <div className="flex items-center justify-between">
-                    <div className="flex items-center gap-1">
-                      <span className="font-semibold">
-                        ${product.min_price}
-                      </span>
-                      <span className="text-xs text-gray-500 line-through">
-                        ${product.price}
-                      </span>
-                    </div>
-                    {/* 购物车图标 */}
-                    <OpenAddToCartModalButton
-                      productId={product.id}
-                      icon={
-                        <svg
+      <div className="grid grid-cols-4 gap-8">
+        {bestSellers.map((product) => (
+          <div key={product.id} className="flex min-w-0 flex-col gap-2">
+            <div className="relative aspect-[3/4] w-full overflow-hidden rounded-sm">
+              <Image
+                src={product.image_url}
+                alt={product.name}
+                fill
+                sizes="25vw"
+                className="object-cover"
+              />
+            </div>
+
+            <Link
+              href="/wiggins-ready-go-250-density-popping-curly-hd-lace-front-wigs-medium-length-human-hair-wigs.html"
+              className="truncate text-sm leading-ly-22 font-normal text-gray-800"
+            >
+              {product.name}
+            </Link>
+
+            <div className="flex items-center gap-1">
+              <div className="flex">
+                {[1, 2, 3, 4, 5].map((star) => (
+                  <svg
+                    key={star}
+                    className="h-4 w-4 text-green-500"
+                    fill="currentColor"
+                    viewBox="0 0 20 20"
+                  >
+                    <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
+                  </svg>
+                  
+                ))}
+              </div>
+
+              <span className="text-xs text-gray-600">
+                {product.review_count}
+              </span>
+            </div>
+
+            <div className="flex items-center justify-between">
+              <div className="flex items-center gap-1">
+                <span className="font-semibold">${product.min_price}</span>
+
+                <span className="text-xs text-gray-500 line-through">
+                  ${product.price}
+                </span>
+              </div>
+
+              <OpenAddToCartModalButton
+                productId={product.id}
+                icon={
+                  <svg
                           xmlns="http://www.w3.org/2000/svg"
                           width="32"
                           height="32"
@@ -146,19 +135,16 @@ const HomeBestSellers = ({ bestSellers }: HomeBestSellersProps) => {
                             stroke-linecap="round"
                             d="M17.1348 17.1332L17.1348 13.3999"
                           ></path>
-                        </svg>
-                      }
-                    />
-                  </div>
-                </div>
-              ))}
+                    </svg>
+                }
+              />
             </div>
-          </SwiperSlide>
+          </div>
         ))}
-
-        {/* 自定义分页器容器(底部小按钮) */}
-        <div className="custom-pagination flex justify-center mt-4"></div>
-      </Swiper>
+      </div>
+      <div className="w-[186px] h-[32px] m-auto text-center text-sm leading-ly-32 mt-12 opacity-100 rounded-[29px] bg-white border border-solid">
+        View More
+      </div>
     </div>
   );
 };

+ 13 - 15
src/app/(public)/_components/HomeContent.tsx

@@ -9,7 +9,7 @@ import HomeNewArrival from "./HomeNewArrival";
 import MiddleImageSwiper from "./MiddleImageSwiper";
 import Trending from "./Trending";
 import RecommendedProducts from "./RecommendedProducts";
-import CategoryTabSwiper  from "./CategoryTabSwiper ";
+// import CategoryTabSwiper  from "./CategoryTabSwiper ";
 import "swiper/css";
 import "swiper/css/navigation";
 import type {HomeApiData,HomeProduct } from "@/types/home/type"
@@ -104,16 +104,14 @@ export default function HomeContent({ homeData }: HomeContentProps) {
   const bannerImages = bannerSection?.images ?? [];
        // 过滤出 banner 板块
   const jinGangSection = sections.find(sec => sec.id === 13);
-  const jinGang = jinGangSection?.images ?? [];
+  
     // 2. 筛选热销板块,假设后端name为 best_sellers,自行修改匹配后端标识
   const bestSellerSection = sections.find(sec => sec.name === "BestSellers");
-  const bestSellers: HomeProduct[] = bestSellerSection?.products ?? [];
         // 过滤出 banner 板块
   const middleBannerSection = sections.find(sec => sec.id === 15);
   const middleBanner = middleBannerSection?.images ?? [];
    // 3. 筛选new Arrival板块
   const newArrivalSection = sections.find(sec => sec.name === "New_Arrival");
-  const newArrival: HomeProduct[] = newArrivalSection?.products ?? [];
    // 4. 筛选7板块
   const zeroWearImageSection = sections.find(sec => sec.id === 18);
   const zeroWearImage= zeroWearImageSection?.images ?? [];
@@ -122,36 +120,36 @@ export default function HomeContent({ homeData }: HomeContentProps) {
   const zeroWear: HomeProduct[] = zeroWearSection?.products ?? [];
    // 4. 筛选trendingSection板块
   const trendingSection = sections.find(sec => sec.name === "Trending_Now");
-  const trending: HomeProduct[] = trendingSection?.products ?? [];
+ 
    // 4. Recommended
   const recommendedSection = sections.find(sec => sec.name === "Recommended_For_You");
-  const recommended: HomeProduct[] = recommendedSection?.products ?? [];
+ 
   return (<>
-    <div className="min-h-screen bg-white font-sans">
+    <div className="min-h-screen pt-[162px] bg-white font-sans">
       {/* 顶部Banner */}
       <div className="w-full relative">
         <TopImageSwiper  bannerImages={bannerImages}  />
       </div>
 
       {/* 服务标签栏 */}
-      <div className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 bg-[#EAE9E1]">
+      <div className="flex justify-center items-center gap-[200px]  p-4 bg-[#EAE9E1]">
         {MOCK_DATA.services.map((item, idx) => (
           <div key={idx} className="flex items-center gap-2">
             <span className="text-xl">{item.icon}</span>
-            <span className="text-sm font-medium">{item.text}</span>
+            <span className="text-sm font-medium leading-5">{item.text}</span>
           </div>
         ))}
       </div>
       {/* 分类快捷入口 */}
-      <JinGangSwiper jinGang={jinGang} />
+      <JinGangSwiper jinGangSection={jinGangSection} />
       {/* 热销商品区 */}
-      <HomeBestSellers bestSellers={bestSellers} />
+      <HomeBestSellers bestSellerSection={bestSellerSection} />
       <HomeMiddleBanner  middleBanner={middleBanner} />
-      <HomeNewArrival newArrival={newArrival} />
+      <HomeNewArrival newArrivalSection={newArrivalSection} />
       <MiddleImageSwiper zeroWear={zeroWear}  zeroWearImage={zeroWearImage} />
-      <Trending trending={trending}/>
-      <RecommendedProducts recommended={recommended} />
-      <CategoryTabSwiper />
+      <Trending trendingSection={trendingSection}/>
+      <RecommendedProducts recommendedSection={recommendedSection} />
+      {/* <CategoryTabSwiper /> */}
     </div>
     <CustomerService />
   </>);

+ 8 - 8
src/app/(public)/_components/HomeMiddleBanner.tsx

@@ -27,16 +27,16 @@ export default function HomeMiddleBanner({ middleBanner }: middleBannerProps) {
   };
 
   return (
-    <div className="max-w-screen-md mx-auto p-4">
-      <Swiper
+    <div className="w-full  mx-auto px-12 flex items-center justify-center gap-11 ">
+      {/* <Swiper
         modules={[Navigation]}
         spaceBetween={10}
         slidesPerView={1}
         onSlideChange={handleSlideChange}
         className="mb-6"
-      >
+      > */}
         {middleBannerImages.map((item, idx) => (
-          <SwiperSlide key={idx}>
+          // <SwiperSlide key={idx}>
             <div className="relative w-full h-42">
               <Image
                 src={item.image}
@@ -45,10 +45,10 @@ export default function HomeMiddleBanner({ middleBanner }: middleBannerProps) {
                 className="w-full h-auto object-cover rounded-md"
               />
             </div>
-          </SwiperSlide>
+             //  {/* </SwiperSlide> */}
         ))}
-      </Swiper>
-      <div className="flex justify-center gap-2">
+      {/* </Swiper> */}
+      {/* <div className="flex justify-center gap-2">
         <button
           onClick={() => handleIndicatorClick(0)}
           className={`w-6 h-[5px] rounded-full  opacity-100 ${
@@ -63,7 +63,7 @@ export default function HomeMiddleBanner({ middleBanner }: middleBannerProps) {
           }`}
           aria-label="切换到第二块"
         />
-      </div>
+      </div> */}
     </div>
   );
 }

+ 72 - 87
src/app/(public)/_components/HomeNewArrival.tsx

@@ -1,15 +1,16 @@
 
-import { Swiper, SwiperSlide } from "swiper/react";
-import { Navigation, Pagination } from "swiper/modules";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
 // 导入Swiper基础样式
-import "swiper/css";
-import "swiper/css/navigation";
-import type {HomeProduct} from "@/types/home/type"
-interface NewArrivalProps {
-  newArrival: HomeProduct[];
+// import "swiper/css";
+// import "swiper/css/navigation";
+import Link from "next/link";
+import Image from "next/image";
+import type {HomeProduct,HomeSection} from "@/types/home/type"
+interface newArrivalSectionProps{
+  newArrivalSection:HomeSection |undefined;
 }
-const HomeNewArrival = ({ newArrival }: NewArrivalProps) => {
+const HomeNewArrival = ({ newArrivalSection }: newArrivalSectionProps) => {
+   const newArrival: HomeProduct[] = newArrivalSection?.products ?? [];
      if (!newArrival || newArrival.length === 0) return null;
         // 4. 真实数据按每6个拆分一组,替换原来的模拟拆分逻辑
     const chunkSize = 6;
@@ -18,76 +19,63 @@ const HomeNewArrival = ({ newArrival }: NewArrivalProps) => {
       productBlocks.push(newArrival.slice(i, i + chunkSize));
     }
   return (
-    <div className="w-full max-w-[1200px] mx-auto px-4 py-8">
+    <div className="w-full  mx-auto px-12 py-12">
       {/* 标题 */}
-      <h2 className="text-2xl font-bold mb-6">New Arrival</h2>
+      <h2 className="text-4xl text-center font-bold leading-ly-46 mb-6">{newArrivalSection?.title}</h2>
 
-      {/* Swiper容器 */}
-      <Swiper
-        slidesPerView={1}
-        spaceBetween={20}
-        modules={[Navigation, Pagination]}
-        pagination={{
-          el: ".custom-pagination",
-          clickable: true,
-          renderBullet: (index, className) => {
-            // 所有分页默认底色为 #E5E5E5
-            return `<span class="${className} w-6 h-[5px] rounded-full mx-1 bg-[#E5E5E5]"></span>`;
-          },
-          // 激活时强制覆盖底色,加 !important 提高权重
-          bulletActiveClass: "!bg-[#333]",
-        }}
-        className="relative"
-      >
-        {/* 渲染2个滑动块 */}
-        {productBlocks.map((block, blockIndex) => (
-          <SwiperSlide key={blockIndex} className="!px-0">
-            {/* 每个块:6个产品,2列布局 */}
-            <div className="grid grid-cols-2 gap-4">
-              {block.map((product) => (
-                <div key={product.id} className="flex flex-col gap-2">
-                  {/* 产品图片 */}
-                  <img
-                    src={product.image_url}
-                    alt={product.name}
-                    className="w-full h-auto object-cover rounded-sm"
-                  />
-                  {/* 产品标题 */}
-                  <p className="text-sm text-gray-800 truncate">
-                    {product.name}
-                  </p>
-                  {/* 评分 + 评论数 */}
-                  <div className="flex items-center gap-1">
-                    <div className="flex">
-                      {/* 星级图标(简化版,可替换为真实星星图标) */}
-                      {[1, 2, 3, 4, 5].map((star) => (
-                        <svg
-                          key={star}
-                          className="w-4 h-4 text-yellow-400"
-                          fill="currentColor"
-                          viewBox="0 0 20 20"
-                        >
-                          <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
-                        </svg>
-                      ))}
-                    </div>
-                    <span className="text-xs text-gray-600">
-                      {product.review_count}
-                    </span>
-                  </div>
-                  {/* 价格 + 购物车图标 */}
-                  <div className="flex items-center justify-between">
-                    <div className="flex items-center gap-1">
-                      <span className="font-semibold">${product.min_price}</span>
-                      <span className="text-xs text-gray-500 line-through">
-                        ${product.price}
-                      </span>
-                    </div>
-                    {/* 购物车图标 */}
-                    <OpenAddToCartModalButton
-                        productId={product.id}
-                      icon={
-                        <svg
+      <div className="grid grid-cols-4 gap-8">
+        {newArrival.map((product) => (
+          <div key={product.id} className="flex min-w-0 flex-col gap-2">
+            <div className="relative aspect-[3/4] w-full overflow-hidden rounded-sm">
+              <Image
+                src={product.image_url}
+                alt={product.name}
+                fill
+                sizes="25vw"
+                className="object-cover"
+              />
+            </div>
+
+            <Link
+              href="/wiggins-ready-go-250-density-popping-curly-hd-lace-front-wigs-medium-length-human-hair-wigs.html"
+              className="truncate text-sm leading-ly-22 font-normal text-gray-800"
+            >
+              {product.name}
+            </Link>
+
+            <div className="flex items-center gap-1">
+              <div className="flex">
+                {[1, 2, 3, 4, 5].map((star) => (
+                  <svg
+                    key={star}
+                    className="h-4 w-4 text-green-500"
+                    fill="currentColor"
+                    viewBox="0 0 20 20"
+                  >
+                    <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
+                  </svg>
+                  
+                ))}
+              </div>
+
+              <span className="text-xs text-gray-600">
+                {product.review_count}
+              </span>
+            </div>
+
+            <div className="flex items-center justify-between">
+              <div className="flex items-center gap-1">
+                <span className="font-semibold">${product.min_price}</span>
+
+                <span className="text-xs text-gray-500 line-through">
+                  ${product.price}
+                </span>
+              </div>
+
+              <OpenAddToCartModalButton
+                productId={product.id}
+                icon={
+                  <svg
                           xmlns="http://www.w3.org/2000/svg"
                           width="32"
                           height="32"
@@ -143,19 +131,16 @@ const HomeNewArrival = ({ newArrival }: NewArrivalProps) => {
                             stroke-linecap="round"
                             d="M17.1348 17.1332L17.1348 13.3999"
                           ></path>
-                        </svg>
-                      }
-                    />
-                  </div>
-                </div>
-              ))}
+                    </svg>
+                }
+              />
             </div>
-          </SwiperSlide>
+          </div>
         ))}
-
-        {/* 自定义分页器容器(底部小按钮) */}
-        <div className="custom-pagination flex justify-center mt-4"></div>
-      </Swiper>
+      </div>
+      <div className="w-[186px] h-[32px] m-auto text-center text-sm leading-ly-32 mt-12 opacity-100 rounded-[29px] bg-white border border-solid">
+        View More
+      </div>
     </div>
   );
 };

+ 188 - 60
src/app/(public)/_components/JinGangSwiper.tsx

@@ -1,15 +1,24 @@
 "use client";
-import { useState } from "react";
+import { useState, useRef } from "react";
 import { Swiper, SwiperSlide } from "swiper/react";
 import type { Swiper as SwiperType } from "swiper/types";
 import { Navigation } from "swiper/modules";
 import "swiper/css";
 import "swiper/css/navigation";
+import "swiper/css";
 import Image from "next/image";
-import type { HomeSectionImage } from "@/types/home/type";
-interface jinGangProps {
-  jinGang: HomeSectionImage[];
+import Link from "next/link";
+import type { HomeSectionImage,HomeSection } from "@/types/home/type";
+interface jinGangSectionProps{
+  jinGangSection:HomeSection |undefined;
 }
+// interface jinGangSection{
+//   id: number;
+//   type: string;
+//   name: string;
+//   sort_order:  number;
+//   images:HomeSectionImage[];
+// }
 // const MOCK_DATA = [
 //   [
 //     {
@@ -65,47 +74,68 @@ interface jinGangProps {
 //     },
 //   ],
 // ];
+const SLIDES_PER_VIEW = 6.5;
+const SLIDES_PER_GROUP = 6;
+const SPACE_BETWEEN = 24;
+export default function JinGangSwiper({jinGangSection} :jinGangSectionProps ) {
+  const jinGang: HomeSectionImage[] = jinGangSection?.images ?? [];
+  const swiperRef = useRef<SwiperType | null>(null);
+
+  const [activePage, setActivePage] = useState(0);
+  const [isBeginning, setIsBeginning] = useState(true);
+  const [isEnd, setIsEnd] = useState(jinGang.length <= SLIDES_PER_VIEW);
+  const pageCount = Math.ceil(jinGang.length / SLIDES_PER_VIEW);
 
-export default function JinGangSwiper({ jinGang }: jinGangProps) {
-  const [activeIndex, setActiveIndex] = useState(0);
+  const syncSwiperState = (swiper: SwiperType) => {
+    const pageIndex = Math.min(swiper.snapIndex, Math.max(pageCount - 1, 0));
 
-  const [swiperRef, setSwiperRef] = useState<SwiperType | null>(null);
-  if (!jinGang || jinGang.length === 0) return null;
-  // 4. 真实数据按每6个拆分一组,替换原来的模拟拆分逻辑
-  const chunkSize = 6;
-  const productBlocks: HomeSectionImage[][] = [];
-  for (let i = 0; i < jinGang.length; i += chunkSize) {
-    productBlocks.push(jinGang.slice(i, i + chunkSize));
-  }
+    setActivePage(pageIndex);
+    setIsBeginning(swiper.isBeginning);
+    setIsEnd(swiper.isEnd);
+  };
 
-  const handleSlideChange = (swiper: any) => {
-    setActiveIndex(swiper.activeIndex);
+  const handlePrev = () => {
+    swiperRef.current?.slidePrev();
   };
 
-  const handleIndicatorClick = (index: any) => {
-    if (swiperRef) {
-      swiperRef.slideTo(index);
-      setActiveIndex(index);
-    }
+  const handleNext = () => {
+    swiperRef.current?.slideNext();
   };
 
+  const handlePaginationClick = (pageIndex: number) => {
+    const swiper = swiperRef.current;
+
+    if (!swiper) return;
+
+    swiper.slideTo(pageIndex * SLIDES_PER_GROUP);
+  };
   return (
-    <div className="max-w-screen-md mx-auto p-4">
-      <Swiper
-        modules={[Navigation]}
-        spaceBetween={10}
-        slidesPerView={1}
-        onSwiper={(swiper) => setSwiperRef(swiper)}
-        onSlideChange={handleSlideChange}
-        className="mb-6"
-      >
-        {productBlocks.map((block, blockIndex) => (
-          <SwiperSlide key={blockIndex}>
-            <div className="grid grid-cols-3 gap-2">
-              {block.map((item, idx) => (
-                <div
-                  key={`block1-${idx}`}
-                  className="relative w-full aspect-[3/4]"
+    <div className="relative w-full mt-6">
+      {/* Swiper */}
+      <div className="pl-12">
+        <Swiper
+          slidesPerView={SLIDES_PER_VIEW}
+          slidesPerGroup={SLIDES_PER_GROUP}
+          spaceBetween={SPACE_BETWEEN}
+          speed={300}
+          watchOverflow
+          onSwiper={(swiper) => {
+            swiperRef.current = swiper;
+            syncSwiperState(swiper);
+          }}
+          onSlideChange={syncSwiperState}
+          onReachBeginning={syncSwiperState}
+          onReachEnd={syncSwiperState}
+          onFromEdge={syncSwiperState}
+          className="w-full"
+        >
+          {jinGang.map((item, index) => (
+            <SwiperSlide key={`${item.title}-${index}`}>
+              {item.link ? (
+                <Link
+                  href={item.link}
+                  className="relative aspect-[3/4] w-full overflow-hidden rounded-md w-full"
+                  aria-label={item.title}
                 >
                   <Image
                     src={item.image}
@@ -113,33 +143,131 @@ export default function JinGangSwiper({ jinGang }: jinGangProps) {
                     fill
                     className="object-cover rounded-md"
                   />
-                  {/* <div className="absolute bottom-2 left-2 text-white text-sm font-medium bg-black/50 px-2 py-1 rounded">
-                  {item.name}
-                </div> */}
+                </Link>
+              ) : (
+                <div className="relative aspect-[3/4] w-full overflow-hidden rounded-md w-full">
+                 <Image
+                    src={item.image}
+                    alt={item.title}
+                    fill
+                    className="object-cover rounded-md"
+                  />
                 </div>
-              ))}
-            </div>
-          </SwiperSlide>
-        ))}
-      </Swiper>
+              )}
+            </SwiperSlide>
+          ))}
+        </Swiper>
+      </div>
 
-      {/* 底部指示器按钮:2个,激活态黑色,未激活灰色 */}
-      <div className="flex justify-center gap-2">
+      {!isBeginning && (
         <button
-          onClick={() => handleIndicatorClick(0)}
-          className={`w-6 h-[5px] rounded-full  opacity-100 ${
-            activeIndex === 0 ? "bg-[#333]" : "bg-[#E5E5E5]"
-          }`}
-          aria-label="切换到第一块"
-        />
+          type="button"
+          aria-label="Previous"
+          onClick={handlePrev}
+          className="
+      absolute
+      left-18
+      top-1/2
+      z-10
+      flex
+      h-10
+      w-10
+      -translate-y-1/2
+      items-center
+      justify-center
+    "
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+            className="rotate-180"
+          >
+            <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
+
+            <path
+              d="M17 26L23 20L17 14"
+              stroke="rgba(255, 255, 255, 1)"
+              strokeWidth="1.5"
+              strokeLinejoin="round"
+              strokeLinecap="square"
+            />
+          </svg>
+        </button>
+      )}
+
+      {/* Next - 最后一页不显示 */}
+      {!isEnd && (
         <button
-          onClick={() => handleIndicatorClick(1)}
-          className={`w-6 h-[5px] rounded-full  opacity-100 ${
-            activeIndex === 1 ? "bg-[#333]" : "bg-[#E5E5E5]"
-          }`}
-          aria-label="切换到第二块"
-        />
-      </div>
+          type="button"
+          aria-label="Next"
+          onClick={handleNext}
+          className="
+      absolute
+      right-6
+      top-1/2
+      z-10
+      flex
+      h-10
+      w-10
+      -translate-y-1/2
+      items-center
+      justify-center
+    "
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+          >
+            <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
+
+            <path
+              d="M17 26L23 20L17 14"
+              stroke="rgba(255, 255, 255, 1)"
+              strokeWidth="1.5"
+              strokeLinejoin="round"
+              strokeLinecap="square"
+            />
+          </svg>
+        </button>
+      )}
+
+      {/* Pagination */}
+      {pageCount > 1 && (
+        <div
+          className="
+            mt-4
+            flex
+            items-center
+            justify-center
+            gap-2
+          "
+        >
+          {Array.from({
+            length: pageCount,
+          }).map((_, index) => (
+            <button
+              key={index}
+              type="button"
+              aria-label={`Go to page ${index + 1}`}
+              onClick={() => handlePaginationClick(index)}
+              className={`
+                h-2
+                w-10
+                rounded-full
+                transition-all
+                duration-300
+                ${activePage === index ? "bg-black" : "bg-[#DBDBDB]!"}
+              `}
+            />
+          ))}
+        </div>
+      )}
     </div>
   );
 }

+ 111 - 13
src/app/(public)/_components/MiddleImageSwiper.tsx

@@ -1,6 +1,6 @@
 
 import { Swiper, SwiperSlide } from "swiper/react";
-import {  Pagination  } from "swiper/modules";
+// import {  Pagination  } from "swiper/modules";
 import "swiper/css";
 import "swiper/css/pagination";
 import "swiper/css/navigation";
@@ -39,28 +39,126 @@ interface zeroWearProps {
 // ];
 
 const MiddleImageSwiper = ({ zeroWear,zeroWearImage }: zeroWearProps) => {
+  // zeroWear=[
+  //                   {
+  //                       "id": 55766,
+  //                       "sku": "STW--4*7BobWig--RTG-10, #4--STW--5*5BobWig--12",
+  //                       "name": "[Pack Deal] 10in Natural Black 7*4 Bob Wig and 12in Chocolate Brown 5*5 Bob Wig",
+  //                       "slug": null,
+  //                       "type": "flexible_variant",
+  //                       "price": '368',
+  //                       "min_price":'368' ,
+  //                       "special_price": null,
+  //                       "image_url": "https://img.asteriahair.com/media/catalog/product/2/_/2_wigs_package_deal_natural_black_bob_wig_and_natural_black_bob_wig_1.jpg",
+  //                       "is_new": false,
+  //                       "is_featured": false,
+  //                       "short_description": "Asteria Hair [Pack Deal] 10in Natural Black Ready To Go 7*4 Lace Bob Wig and 12in Chocolate Brown 5*5 Lace Closure Bob Wig.",
+  //                       "rating": 5,
+  //                       "review_count": 2
+  //                   },
+  //                   {
+  //                       "id": 55811,
+  //                       "sku": "LD--13*4Wig--24,LB--5*7Wig--RTG--20",
+  //                       "name": "[Pack Deal] 2 Long Length Wig 24Inch Loose Deep 13*4 Lace Wig and 20Inch Body Wave Ready To Go Closure Wig",
+  //                       "slug": null,
+  //                       "type": "flexible_variant",
+  //                       "price": "830",
+  //                       "min_price": "830",
+  //                       "special_price": null,
+  //                       "image_url": "https://img.asteriahair.com/media/catalog/product/l/d/ld_body.png",
+  //                       "is_new": false,
+  //                       "is_featured": false,
+  //                       "short_description": "Asteria Hair [Pack Deal] 2 Long Length Wig 24Inch Loose Deep 13*4 Lace Wig and 20Inch Body Wave Ready To Go 5*7 Closure Wig.",
+  //                       "rating": 5,
+  //                       "review_count": 13
+  //                   },
+  //                   {
+  //                       "id": 55651,
+  //                       "sku": "STW--4*7Wig--RTG--16,A025--STW--13*4Bob--RTG--12",
+  //                       "name": "[Pack Deal] 2 Ready To Go Wig 100% Glueless Straight Texture",
+  //                       "slug": null,
+  //                       "type": "flexible_variant",
+  //                       "price": "400",
+  //                       "min_price": "400",
+  //                       "special_price": null,
+  //                       "image_url": "https://img.asteriahair.com/media/catalog/product/b/o/bob_.png",
+  //                       "is_new": false,
+  //                       "is_featured": false,
+  //                       "short_description": "Asteria Hair [Pack Deal] 2 Ready To Go Wig 100% Glueless Straight Texture, straight 7*5 ready to go lace wig 16inch and zigzag style 13*4 ready to go lace bob wig 12inch.",
+  //                       "rating": 5,
+  //                       "review_count": 11
+  //                   },
+  //                   {
+  //                       "id": 55681,
+  //                       "sku": "Curly--HDLaceFrontWig--20--250%,LB--4*7BobWig--RTG-10",
+  //                       "name": "[Pack Deal] 20in 250% Density Curly 13*4 HD Lace Wig and 10Inch 7*4 Ready To Go Body Wave Bob Human Hair Wig",
+  //                       "slug": null,
+  //                       "type": "flexible_variant",
+  //                       "price": "650",
+  //                       "min_price": "650",
+  //                       "special_price": null,
+  //                       "image_url": "https://img.asteriahair.com/media/catalog/product/j/c/jc_rtg_lb_.png",
+  //                       "is_new": false,
+  //                       "is_featured": false,
+  //                       "short_description": "Asteria Hair [Pack Deal] 20in 250% Density Curly 13*4 HD Lace Wig and 10in 7*4 Ready To Go Body Wave Bob Human Hair Lace Wig.",
+  //                       "rating": 5,
+  //                       "review_count": 4
+  //                   },
+  //                   {
+  //                       "id": 55982,
+  //                       "sku": "JC-5*7Wig-24-180%-RTG,NHC-RTG-LB-4*7BOB-12",
+  //                       "name": "[Pack Deal]2 Ready To Go Wigs Curly 5*7 Lace Wig 24 Inch And BOB Body Wave Wig 4*7 Lace Wigs 12 Inch",
+  //                       "slug": null,
+  //                       "type": "flexible_variant",
+  //                       "price": '738',
+  //                       "min_price": '738',
+  //                       "special_price": null,
+  //                       "image_url": "https://img.asteriahair.com/media/catalog/product/1/_/1_6_16.png",
+  //                       "is_new": false,
+  //                       "is_featured": false,
+  //                       "short_description": "This value pack includes two ready-to-wear wigs: a 24-inch curly style with 5*7 lace front and a 12-inch body wave bob with 4*7 lace, both made from human hair for instant perfection and versatile styling.",
+  //                       "rating": 0,
+  //                       "review_count": 0
+  //                   },
+  //                   {
+  //                       "id": 55984,
+  //                       "sku": "NHC-RTG-LB-4*7BOB-12,NHC-RTG-STW-4*7BOB-10",
+  //                       "name": "[Pack Deal]Ready To Go Wigs Bob Straight Wig 4*7 Lace Wigs 10 Inch And BOB Body Wave Wig 4*7 Lace Wigs 12 Inch",
+  //                       "slug": null,
+  //                       "type": "flexible_variant",
+  //                       "price": '398',
+  //                       "min_price": '398',
+  //                       "special_price": null,
+  //                       "image_url": "https://img.asteriahair.com/media/catalog/product/1/_/1_7.png",
+  //                       "is_new": false,
+  //                       "is_featured": false,
+  //                       "short_description": "This value pack includes two ready-to-wear bob wigs: a 10-inch straight style and a 12-inch body wave style, both featuring a 4*7 lace construction and made from human hair, allowing you to effortlessly switch between two fashionable looks.",
+  //                       "rating": 0,
+  //                       "review_count": 0
+  //                   }
+  //               ]
   if (!zeroWear || zeroWear.length === 0) return null;
   const  productList = zeroWear
   const WearImage = zeroWearImage;
   return (
-    <div className="w-full pr-4 pl-4 mx-auto  rounded-lg  ">
-      <div className="w-full">
-        <img
+    <div className="w-full pr-4 pl-4 mx-auto flex  rounded-lg  ">
+      <div className="w-[777px] h-full">
+        {/* <img
           src={WearImage[0].image || 'https://cdn.asteriahair.com/uploads/202602/11/20260211095330_7ec9031a4ab36e44.gif'}
           alt={WearImage[0].title || 'ZEROWEAR hair banner'}
           className="w-full h-auto object-cover block"
-        />
+        /> */}
       </div>
-      <div className="py-4 px-4 pb-6 relative min-h-83 bg-[#f0f5f3] pb-10">
+      <div className="py-4 px-4 pl-0 relative max-w-[960px] ">
         <div className="w-full relative">
           <Swiper
-            modules={[Pagination]}
+            // modules={[Pagination]}
             spaceBetween={16}
-            slidesPerView={2}
-            pagination={{
-              type: "progressbar",
-              el: ".my-progress-bar",
-            }}
+            slidesPerView={3.2}
+            // pagination={{
+            //   type: "progressbar",
+            //   el: ".my-progress-bar",
+            // }}
             className="mySwiper"
             breakpoints={{
               480: { slidesPerView: 1 },
@@ -158,7 +256,7 @@ const MiddleImageSwiper = ({ zeroWear,zeroWearImage }: zeroWearProps) => {
               </SwiperSlide>
             ))}
           </Swiper>
-          <div className="my-progress-bar mt-5 w-full h-1 bg-gray-200 rounded-full bottom-0 [top:unset]"></div>
+          {/* <div className="my-progress-bar mt-5 w-full h-1 bg-gray-200 rounded-full bottom-0 [top:unset]"></div> */}
         </div>
       </div>
     </div>

+ 10 - 7
src/app/(public)/_components/RecommendedProducts.tsx

@@ -2,10 +2,13 @@
 import { useState, useEffect, useCallback } from "react";
 import Image from "next/image";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
-import type {HomeProduct} from "@/types/home/type"
+import type {HomeProduct,HomeSection} from "@/types/home/type"
 interface recommendedProps {
   recommended: HomeProduct[];
 }
+interface newArrivalSectionProps{
+  recommendedSection:HomeSection |undefined;
+}
 // 商品类型定义
 interface ProductItem {
   id: number;
@@ -54,8 +57,8 @@ const mockGetProductApi = async (page: number, pageSize: number) => {
 
 const PAGE_SIZE = 6;
 
-export default function RecommendedProducts({ recommended }: recommendedProps) {
-  
+export default function RecommendedProducts({ recommendedSection }: newArrivalSectionProps) {
+   const recommended: HomeProduct[] = recommendedSection?.products ?? [];
   // 已渲染的全部商品
   const [productList, _setProductList] = useState<HomeProduct[]>(recommended);
   // 当前页码
@@ -117,13 +120,13 @@ export default function RecommendedProducts({ recommended }: recommendedProps) {
   };
 
   return (
-    <section className="py-6 px-4 pt-8">
-      <h2 className="text-[clamp(1.5rem,4vw,2.2rem)] font-semibold mb-6">
+    <section className="w-full px-12 mt-12">
+      <h2 className="text-4xl text-center font-bold leading-ly-46 mb-6">
         Recommended For You
       </h2>
 
       {/* 商品2列网格 */}
-      <div className="grid grid-cols-2 gap-4">
+      <div className="grid grid-cols-4 gap-8">
         {productList.map((item) => (
           <div key={item.id} className="flex flex-col gap-3">
             {/* Next.Image 商品图容器 */}
@@ -253,7 +256,7 @@ export default function RecommendedProducts({ recommended }: recommendedProps) {
 
       {/* View More 按钮:有下一页且非加载中显示 */}
       {hasMore && (
-        <div className="flex justify-center mt-8">
+        <div className="flex justify-center my-8">
           <button
             onClick={handleViewMore}
             disabled={loading}

+ 1 - 1
src/app/(public)/_components/TopImageSwiper.tsx

@@ -24,7 +24,7 @@ const TopImageSwiper = ({ bannerImages }: TopImageSwiperProps) => {
   // ];
 
   return (
-    <div className="w-full max-w-[1200px] mx-auto ">
+    <div className="w-full mx-auto ">
       <Swiper
         slidesPerView={1}
         spaceBetween={0}

+ 282 - 203
src/app/(public)/_components/Trending.tsx

@@ -1,14 +1,16 @@
 import { useState, useRef, useEffect } from "react";
 import { Swiper, SwiperSlide } from "swiper/react";
-import { Scrollbar, Navigation } from "swiper/modules";
+import type { Swiper as SwiperType } from "swiper/types";
+import { Navigation } from "swiper/modules";
 import "swiper/css";
-import "swiper/css/scrollbar";
 import "swiper/css/navigation";
+import "swiper/css";
 import Image from "next/image";
+import Link from "next/link";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
-import type {HomeProduct} from "@/types/home/type"
-interface trendingProps {
-  trending: HomeProduct[];
+import type { HomeProduct,HomeSection } from "@/types/home/type";
+interface newArrivalSectionProps{
+  trendingSection:HomeSection |undefined;
 }
 // // 商品数据,每个卡片携带videoUrl视频地址
 // type ProductItem = {
@@ -49,23 +51,24 @@ interface trendingProps {
 //     videoUrl: "/video/hair-demo-4.mp4",
 //   },
 // ];
-
-const Trending = ({ trending }: trendingProps) => {
-   // 视频弹窗状态
+const SLIDES_PER_VIEW = 6.5;
+const SLIDES_PER_GROUP = 6;
+const SPACE_BETWEEN = 24;
+const Trending = ({ trendingSection }: newArrivalSectionProps) => {
+   const trending: HomeProduct[] = trendingSection?.products ?? [];
+  // 视频弹窗状态
   const [openVideoModal, setOpenVideoModal] = useState(false);
   const [currentVideoSrc, setCurrentVideoSrc] = useState("");
-   const videoRef = useRef<HTMLVideoElement>(null);
-   // 弹窗打开自动播放
+  const videoRef = useRef<HTMLVideoElement>(null);
+  // 弹窗打开自动播放
   useEffect(() => {
     if (openVideoModal && videoRef.current) {
       videoRef.current.play().catch((err) => console.log("播放拦截", err));
     }
   }, [openVideoModal]);
-  
+
   if (!trending || trending.length === 0) return null;
-  const  productList = trending
- 
- 
+  const productList = trending;
 
   // 打开弹窗并赋值视频地址
   const handlePlayClick = (videoUrl: string) => {
@@ -81,204 +84,172 @@ const Trending = ({ trending }: trendingProps) => {
       videoRef.current.currentTime = 0;
     }
   };
+  const swiperRef = useRef<SwiperType | null>(null);
+  const [activePage, setActivePage] = useState(0);
+  const [isBeginning, setIsBeginning] = useState(true);
+  const [isEnd, setIsEnd] = useState(trending.length <= SLIDES_PER_VIEW);
+  const pageCount = Math.ceil(trending.length / SLIDES_PER_VIEW);
 
- 
+  const syncSwiperState = (swiper: SwiperType) => {
+    const pageIndex = Math.min(swiper.snapIndex, Math.max(pageCount - 1, 0));
 
-  return (
-    <div className="w-full max-w-[960px] mx-auto">
-      <div className="w-full max-w-[960px] mx-auto px-4 py-6">
-        {/* 顶部标题 */}
-        <h2 className="text-[clamp(1.5rem,4vw,2.2rem)] font-bold mb-6 text-black">
-          Real looks,Trending Now
-        </h2>
+    setActivePage(pageIndex);
+    setIsBeginning(swiper.isBeginning);
+    setIsEnd(swiper.isEnd);
+  };
 
-        {/* Swiper外层容器:进度条深层Tailwind样式 */}
-        <div
-          className="
-          [&_.custom-scrollbar]:h-[3px]
-          [&_.custom-scrollbar]:bg-gray-200
-          [&_.custom-scrollbar]:rounded-full
-          [&_.swiper-scrollbar-drag]:h-full
-          [&_.swiper-scrollbar-drag]:bg-black
-          [&_.swiper-scrollbar-drag]:rounded-full
-          [&_.swiper-scrollbar-drag]:shadow-none
-          [&_.swiper-scrollbar]:bg-transparent
-          /* 自定义左右箭头样式 匹配截图灰色圆形按钮 */
-          [&_.swiper-button-next]:w-11
-          [&_.swiper-button-next]:h-11
-          [&_.swiper-button-next]:rounded-full
-          [&_.swiper-button-next]:bg-black/40
-          [&_.swiper-button-next]:text-white
-          [&_.swiper-button-next]:after:text-base
-          [&_.swiper-button-next]:after:font-bold
-          [&_.swiper-button-prev]:w-11
-          [&_.swiper-button-prev]:h-11
-          [&_.swiper-button-prev]:rounded-full
-          [&_.swiper-button-prev]:bg-black/40
-          [&_.swiper-button-prev]:text-white
-          [&_.swiper-button-prev]:after:text-base
-          [&_.swiper-button-prev]:after:font-bold
-          [&_.swiper-button-next]:right-3
-          [&_.swiper-button-prev]:left-3
-        "
+  const handlePrev = () => {
+    swiperRef.current?.slidePrev();
+  };
+
+  const handleNext = () => {
+    swiperRef.current?.slideNext();
+  };
+
+  const handlePaginationClick = (pageIndex: number) => {
+    const swiper = swiperRef.current;
+
+    if (!swiper) return;
+
+    swiper.slideTo(pageIndex * SLIDES_PER_GROUP);
+  };
+
+  return (
+    <div className=" relative w-full mx-auto">
+      <h2 className="text-4xl text-center font-bold leading-ly-46 mt-12 mb-8">{trendingSection?.title}</h2>
+      <div className="pl-12">
+        <Swiper
+          slidesPerView={SLIDES_PER_VIEW}
+          slidesPerGroup={SLIDES_PER_GROUP}
+          spaceBetween={SPACE_BETWEEN}
+          speed={300}
+          watchOverflow
+          onSwiper={(swiper) => {
+            swiperRef.current = swiper;
+            syncSwiperState(swiper);
+          }}
+          onSlideChange={syncSwiperState}
+          onReachBeginning={syncSwiperState}
+          onReachEnd={syncSwiperState}
+          onFromEdge={syncSwiperState}
+          className="w-full"
         >
-          <Swiper
-            modules={[Scrollbar, Navigation]}
-            spaceBetween={16}
-            slidesPerView={1.2} // 露出右侧下一张,和截图一致
-            navigation={{
-              prevEl: ".btn-prev", // 左箭头class
-              nextEl: ".btn-next", // 右箭头class
-              disabledClass: "opacity-20", // 滑到首尾自动加这个类,Tailwind直接用
-            }}
-            breakpoints={{
-              640: { slidesPerView: 2.2 },
-              1024: { slidesPerView: 2.5 },
-            }}
-          >
-            {productList.map((item) => (
-              <SwiperSlide key={item.id} className="!h-auto">
-                {/* 单个商品卡片 */}
-                <div className=" overflow-hidden  flex flex-col">
-                  {/* 图片区域 + 中间播放按钮 */}
-                  <div className="relative rounded-xl w-full aspect-[4/5] overflow-hidden rounded-xl">
-                    <img
-                      src={item.image_url}
-                      alt={item.name}
-                      className="w-full h-full object-cover"
-                    />
-                    {/* 居中圆形播放按钮 */}
-                    <button
-                      onClick={() => handlePlayClick(item.videoUrl||"/video/hair-demo-1.mp4" )}
-                      className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
+          {trending.map((item, index) => (
+            <SwiperSlide key={`${item.id}-${index}`}>
+              <div className=" overflow-hidden  flex flex-col">
+                {/* 图片区域 + 中间播放按钮 */}
+                <div className="relative rounded-xl w-full aspect-[4/5] overflow-hidden rounded-xl">
+                  <img
+                    src={item.image_url}
+                    alt={item.name}
+                    className="w-full h-full object-cover"
+                  />
+                  {/* 居中圆形播放按钮 */}
+                  <button
+                    onClick={() =>
+                      handlePlayClick(item.videoUrl || "/video/hair-demo-1.mp4")
+                    }
+                    className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
                       w-24 h-24 rounded-full bg-white/60 backdrop-blur-sm
                       flex items-center justify-center transition hover:bg-white/80"
+                  >
+                    <svg
+                      width="36"
+                      height="36"
+                      viewBox="0 0 24 24"
+                      fill="none"
+                      stroke="#222"
+                      strokeWidth="2"
                     >
-                      <svg
-                        width="36"
-                        height="36"
-                        viewBox="0 0 24 24"
-                        fill="none"
-                        stroke="#222"
-                        strokeWidth="2"
-                      >
-                        <polygon points="5 3 19 12 5 21 5 3" />
-                      </svg>
-                    </button>
-                  </div>
+                      <polygon points="5 3 19 12 5 21 5 3" />
+                    </svg>
+                  </button>
+                </div>
 
-                  {/* 底部文字价格区域 */}
-                  <div className="p-3 flex flex-col rounded-xl mt-3 bg-[#f0f8f5] gap-3">
-                    <p className="text-base text-gray-900 truncate">
-                      {item.name}
-                    </p>
-                    {/* 价格 + 购物车图标 */}
-                    <div className="flex items-center justify-between">
-                      <div className="flex items-center gap-1">
-                        <span className="font-semibold">${item.min_price}</span>
-                        <span className="text-xs text-gray-500 line-through">
-                          ${item.price}
-                        </span>
-                      </div>
-                      {/* 购物车图标 */}
-                      <OpenAddToCartModalButton
-                         productId={item.id}
-                        icon={
-                          <svg
-                            xmlns="http://www.w3.org/2000/svg"
+                {/* 底部文字价格区域 */}
+                <div className="p-3 flex flex-col rounded-xl mt-3 bg-[#f0f8f5] gap-3">
+                  <p className="text-base text-gray-900 truncate">
+                    {item.name}
+                  </p>
+                  {/* 价格 + 购物车图标 */}
+                  <div className="flex items-center justify-between">
+                    <div className="flex items-center gap-1">
+                      <span className="font-semibold">${item.min_price}</span>
+                      <span className="text-xs text-gray-500 line-through">
+                        ${item.price}
+                      </span>
+                    </div>
+                    {/* 购物车图标 */}
+                    <OpenAddToCartModalButton
+                      productId={item.id}
+                      icon={
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="32"
+                          height="32"
+                          viewBox="0 0 32 32"
+                          fill="none"
+                        >
+                          <rect
+                            x="0"
+                            y="0"
                             width="32"
                             height="32"
-                            viewBox="0 0 32 32"
-                            fill="none"
-                          >
-                            <rect
-                              x="0"
-                              y="0"
-                              width="32"
-                              height="32"
-                              rx="8"
-                              fill="#036141"
-                            ></rect>
-                            <path
-                              stroke="rgba(255, 255, 255, 1)"
-                              stroke-width="1.3333333333333333"
-                              stroke-linejoin="round"
-                              stroke-linecap="round"
-                              d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
-                            ></path>
-                            <ellipse
-                              cx="11.066040515899658"
-                              cy="23.199928283691406"
-                              rx="1.4000248908996582"
-                              ry="1.4000015258789062"
-                              stroke="rgba(255, 255, 255, 1)"
-                              stroke-width="1.3333333333333333"
-                              stroke-linejoin="round"
-                              stroke-linecap="round"
-                            ></ellipse>
-                            <ellipse
-                              cx="23.1998291015625"
-                              cy="23.199928283691406"
-                              rx="1.4000244140625"
-                              ry="1.4000015258789062"
-                              stroke="rgba(255, 255, 255, 1)"
-                              stroke-width="1.3333333333333333"
-                              stroke-linejoin="round"
-                              stroke-linecap="round"
-                            ></ellipse>
-                            <path
-                              stroke="rgba(255, 255, 255, 1)"
-                              stroke-width="1.3333333333333333"
-                              stroke-linejoin="round"
-                              stroke-linecap="round"
-                              d="M15.2656 15.2665L18.999 15.2665"
-                            ></path>
-                            <path
-                              stroke="rgba(255, 255, 255, 1)"
-                              stroke-width="1.3333333333333333"
-                              stroke-linejoin="round"
-                              stroke-linecap="round"
-                              d="M17.1348 17.1332L17.1348 13.3999"
-                            ></path>
-                          </svg>
-                        }
-                      />
-                    </div>
+                            rx="8"
+                            fill="#036141"
+                          ></rect>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
+                          ></path>
+                          <ellipse
+                            cx="11.066040515899658"
+                            cy="23.199928283691406"
+                            rx="1.4000248908996582"
+                            ry="1.4000015258789062"
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                          ></ellipse>
+                          <ellipse
+                            cx="23.1998291015625"
+                            cy="23.199928283691406"
+                            rx="1.4000244140625"
+                            ry="1.4000015258789062"
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                          ></ellipse>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M15.2656 15.2665L18.999 15.2665"
+                          ></path>
+                          <path
+                            stroke="rgba(255, 255, 255, 1)"
+                            stroke-width="1.3333333333333333"
+                            stroke-linejoin="round"
+                            stroke-linecap="round"
+                            d="M17.1348 17.1332L17.1348 13.3999"
+                          ></path>
+                        </svg>
+                      }
+                    />
                   </div>
                 </div>
-              </SwiperSlide>
-            ))}
-            {/* 自定义左右按钮,放在Swiper内部,同级slide */}
-            <button className="btn-prev absolute left-2 top-1/2 -translate-y-1/2 z-10 w-9 h-9 rounded-full bg-black/50 shadow flex items-center justify-center">
-              {/* 自定义左箭头SVG */}
-              <svg
-                width="16"
-                height="16"
-                viewBox="0 0 24 24"
-                fill="none"
-                stroke="#fff"
-                stroke-width="2"
-              >
-                <path d="M15 18l-6-6 6-6" />
-              </svg>
-            </button>
-            <button className="btn-next absolute right-2 top-1/2 -translate-y-1/2 z-10 w-9 h-9 rounded-full bg-black/50 shadow flex items-center justify-center">
-              {/* 自定义右箭头SVG */}
-              <svg
-                width="16"
-                height="16"
-                viewBox="0 0 24 24"
-                fill="none"
-                stroke="#fff"
-                stroke-width="2"
-              >
-                <path d="M9 18l6-6-6-6" />
-              </svg>
-            </button>
-          </Swiper>
-        </div>
-
-        {/* ====================== 全屏视频遮罩弹窗 ====================== */}
+              </div>
+            </SwiperSlide>
+          ))}
+        </Swiper>
+      </div>
+          {/* ====================== 全屏视频遮罩弹窗 ====================== */}
         {openVideoModal && (
           <div
             className="fixed inset-0 z-[9999] bg-black/95 flex items-center justify-center p-4"
@@ -305,9 +276,117 @@ const Trending = ({ trending }: trendingProps) => {
               />
             </div>
           </div>
-        )}
-      </div>
-      <div className="relative w-full aspect-[3/4] mt-8">
+        )}   
+      {!isBeginning && (
+        <button
+          type="button"
+          aria-label="Previous"
+          onClick={handlePrev}
+          className="
+      absolute
+      left-18
+      top-1/2
+      z-10
+      flex
+      h-10
+      w-10
+      -translate-y-1/2
+      items-center
+      justify-center
+    "
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+            className="rotate-180"
+          >
+            <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
+
+            <path
+              d="M17 26L23 20L17 14"
+              stroke="rgba(255, 255, 255, 1)"
+              strokeWidth="1.5"
+              strokeLinejoin="round"
+              strokeLinecap="square"
+            />
+          </svg>
+        </button>
+      )}
+
+      {/* Next - 最后一页不显示 */}
+      {!isEnd && (
+        <button
+          type="button"
+          aria-label="Next"
+          onClick={handleNext}
+          className="
+      absolute
+      right-6
+      top-1/2
+      z-10
+      flex
+      h-10
+      w-10
+      -translate-y-1/2
+      items-center
+      justify-center
+    "
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="40"
+            height="40"
+            viewBox="0 0 40 40"
+            fill="none"
+          >
+            <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
+
+            <path
+              d="M17 26L23 20L17 14"
+              stroke="rgba(255, 255, 255, 1)"
+              strokeWidth="1.5"
+              strokeLinejoin="round"
+              strokeLinecap="square"
+            />
+          </svg>
+        </button>
+      )}
+
+      {/* Pagination */}
+      {pageCount > 1 && (
+        <div
+          className="
+            mt-4
+            flex
+            items-center
+            justify-center
+            gap-2
+          "
+        >
+          {Array.from({
+            length: pageCount,
+          }).map((_, index) => (
+            <button
+              key={index}
+              type="button"
+              aria-label={`Go to page ${index + 1}`}
+              onClick={() => handlePaginationClick(index)}
+              className={`
+                h-2
+                w-10
+                rounded-full
+                transition-all
+                duration-300
+                ${activePage === index ? "bg-black" : "bg-[#DBDBDB]!"}
+              `}
+            />
+          ))}
+        </div>
+      )}
+      <div className="relative w-full aspect-[4/3] mt-8">
         <Image
           src={
             "https://cdn.asteriahair.com/uploads/202607/01/20260701165110_3fff435adf276b89.jpg"

+ 2 - 1
src/components/layout/navbar/NavbarActions.tsx

@@ -2,7 +2,7 @@
 
 import { useAppSelector } from "@/store/hooks";
 import Link from "next/link";
-
+import {CurrencySwitch} from "@/components/common/CurrencySwitch/CurrencySwitch";
 export function NavbarActions({
     isGuest
 }: {
@@ -34,6 +34,7 @@ export function NavbarActions({
             >
                 <svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="0" y="0" width="24" height="24" fill="#CCCCCC" fillOpacity="0"></rect><circle cx="12" cy="7" r="4" stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5"></circle><path d="M20 21C20 16.5817 16.4183 13 12 13C7.58172 13 4 16.5817 4 21L20 21Z" stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5"></path></svg>
             </Link>
+             <CurrencySwitch/>
         </div>
     );
 }

+ 92 - 0
src/components/layout/navbar/TopSwiperBanner.tsx

@@ -0,0 +1,92 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+const ITEM_HEIGHT = 40; // h-10 = 40px
+const INTERVAL = 3000;
+const TRANSITION_DURATION = 500;
+
+const messages = [
+  "Tax Refund Season, Straight to The Lowest Price! As Low As $99!",
+  "Free Shipping On Orders Over $99",
+  "30 Days Easy Return & Exchange",
+];
+
+export default function AnnouncementBar() {
+  const [index, setIndex] = useState(0);
+  const [enableTransition, setEnableTransition] = useState(true);
+
+  // 最后复制第一条,实现无缝循环
+  const loopMessages = [...messages, messages[0]];
+
+  // 每隔 3 秒向上滚动一条
+  useEffect(() => {
+    if (messages.length <= 1) return;
+
+    const timer = window.setInterval(() => {
+      setIndex((prev) => prev + 1);
+    }, INTERVAL);
+
+    return () => {
+      window.clearInterval(timer);
+    };
+  }, []);
+
+  // 滚动到复制出来的第一条后,瞬间重置到真正第一条
+  useEffect(() => {
+    if (index !== messages.length) return;
+
+    const timer = window.setTimeout(() => {
+      // 先关闭 transition
+      setEnableTransition(false);
+
+      // 瞬间回到第一条
+      setIndex(0);
+
+      // 下一帧重新开启 transition
+      requestAnimationFrame(() => {
+        requestAnimationFrame(() => {
+          setEnableTransition(true);
+        });
+      });
+    }, TRANSITION_DURATION);
+
+    return () => {
+      window.clearTimeout(timer);
+    };
+  }, [index]);
+
+  return (
+    <div className="h-10 w-full overflow-hidden bg-[#036141]">
+      <div
+        className={
+          enableTransition
+            ? "transition-transform duration-500 ease-in-out"
+            : ""
+        }
+        style={{
+          transform: `translateY(-${index * ITEM_HEIGHT}px)`,
+        }}
+      >
+        {loopMessages.map((message, messageIndex) => (
+          <div
+            key={messageIndex}
+            className="
+              flex
+              h-10
+              items-center
+              justify-center
+              px-4
+              text-center
+              text-[14px]
+              font-medium
+              text-white
+            "
+          >
+            {message}
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}

+ 3 - 1
src/components/layout/navbar/index.tsx

@@ -13,6 +13,7 @@ import NavbarSearchIcon from "./NavbarSearchIcon"
 import type { GetTreefrontMenusResult } from "@/types/theme/menus";
 import { authOptions } from "@utils/auth";
 import { getServerSession } from "next-auth";
+import TopSwiperBanner from "./TopSwiperBanner"
 export default async function Navbar() {
 
     const session = await getServerSession(authOptions);// 游客是null
@@ -29,6 +30,7 @@ export default async function Navbar() {
   return (
     <NavbarErrorBoundary>
       <header className="sticky top-0 z-10">
+        <TopSwiperBanner /> 
         <nav className="relative flex flex-col items-center justify-between gap-4 bg-neutral-50 p-4 pt-3 pb-3 dark:bg-neutral-900 md:flex-row lg:px-6 lg:py-4">
           <div className="flex w-full items-center justify-between gap-0 sm:gap-4">
             {/* 1. THE STATIC SHELL (Visible Instantly) */}
@@ -54,7 +56,7 @@ export default async function Navbar() {
 
 
             <NavbarActions isGuest={isGuest} />
-
+                  
           </div>
         </nav>
       </header>

+ 2 - 2
src/types/home/type.ts

@@ -18,9 +18,9 @@ export type HomeProduct = {
   is_new: boolean;
   is_featured: boolean;
   short_description: string;
-  videoUrl: string;
+  videoUrl?: string;
 };
-type HomeSection = {
+export type HomeSection = {
   id: number;
   type: "image_carousel" | "product_carousel";
   name: string;