瀏覽代碼

首页和顶部横幅调整

zhangzf 2 天之前
父節點
當前提交
6d0c7edb49

+ 26 - 9
src/app/(public)/_components/HomeContent.tsx

@@ -10,6 +10,8 @@ import MiddleImageSwiper from "./MiddleImageSwiper";
 import Trending from "./Trending";
 import RecommendedProducts from "./RecommendedProducts";
 import CategoryTabSwiper  from "./CategoryTabSwiper ";
+import Image from "next/image";
+import Link from "next/link";
 import "swiper/css";
 import "swiper/css/navigation";
 import type {HomeApiData,HomeProduct } from "@/types/home/type"
@@ -100,31 +102,34 @@ export default function HomeContent({ homeData }: HomeContentProps) {
         // categories 
       } = homeData;
       // 过滤出 banner 板块
-  const bannerSection = sections.find(sec => sec.name === "banner");
+  const bannerSection = sections.find(sec => sec.showstyle === '2');
   const bannerImages = bannerSection?.images ?? [];
        // 过滤出 banner 板块
-  const jinGangSection = sections.find(sec => sec.id === 13);
+  const jinGangSection = sections.find(sec => sec.showstyle === '3');
   const jinGang = jinGangSection?.images ?? [];
     // 2. 筛选热销板块,假设后端name为 best_sellers,自行修改匹配后端标识
-  const bestSellerSection = sections.find(sec => sec.name === "BestSellers");
+  const bestSellerSection = sections.find(sec => sec.showstyle === '4');
   const bestSellers: HomeProduct[] = bestSellerSection?.products ?? [];
         // 过滤出 banner 板块
-  const middleBannerSection = sections.find(sec => sec.id === 15);
+  const middleBannerSection = sections.find(sec => sec.showstyle === '5');
   const middleBanner = middleBannerSection?.images ?? [];
    // 3. 筛选new Arrival板块
-  const newArrivalSection = sections.find(sec => sec.name === "New_Arrival");
+  const newArrivalSection = sections.find(sec => sec.showstyle === '6');
   const newArrival: HomeProduct[] = newArrivalSection?.products ?? [];
    // 4. 筛选7板块
-  const zeroWearImageSection = sections.find(sec => sec.id === 18);
+  const zeroWearImageSection = sections.find(sec => sec.showstyle === '7');
   const zeroWearImage= zeroWearImageSection?.images ?? [];
    // 4. 筛选7板块
-  const zeroWearSection = sections.find(sec => sec.name === "7");
+  const zeroWearSection = sections.find(sec => sec.showstyle === '8');
   const zeroWear: HomeProduct[] = zeroWearSection?.products ?? [];
    // 4. 筛选trendingSection板块
-  const trendingSection = sections.find(sec => sec.name === "Trending_Now");
+  const trendingSection = sections.find(sec => sec.showstyle === '9');
   const trending: HomeProduct[] = trendingSection?.products ?? [];
+    // 4. 筛选7板块
+  const bottomBannerSection = sections.find(sec => sec.showstyle === '10');
+  const bottomBanner= bottomBannerSection?.images ?? [];
    // 4. Recommended
-  const recommendedSection = sections.find(sec => sec.name === "Recommended_For_You");
+  const recommendedSection = sections.find(sec => sec.showstyle === '11');
   const recommended: HomeProduct[] = recommendedSection?.products ?? [];
   return (<>
     <div className="min-h-screen bg-white font-sans">
@@ -150,6 +155,18 @@ export default function HomeContent({ homeData }: HomeContentProps) {
       <HomeNewArrival newArrival={newArrival} />
       <MiddleImageSwiper zeroWear={zeroWear}  zeroWearImage={zeroWearImage} />
       <Trending trending={trending}/>
+      {
+        (bottomBanner && bottomBanner.length !== 0) ? (
+          <Link href={bottomBanner[0].link} className="relative w-full aspect-[3/4] mt-8">
+            <Image
+              src={bottomBanner[0].image}
+              alt={bottomBanner[0].title}
+              fill
+              className="object-cover rounded-md"
+            />
+          </Link>
+        ) : null
+      }
       <RecommendedProducts recommended={recommended} />
       <CategoryTabSwiper />
     </div>

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

@@ -142,12 +142,13 @@ const Trending = ({ trending }: trendingProps) => {
                 {/* 单个商品卡片 */}
                 <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"
-                    />
+                  <div className="relative  w-full aspect-[4/5] overflow-hidden rounded-xl">
+                   <Image
+                       src={item.image_url}
+                       alt={item.name}
+                       fill
+                       className="object-cover"
+                     />
                     {/* 居中圆形播放按钮 */}
                     <button
                       onClick={() => handlePlayClick(item.videoUrl||"/video/hair-demo-1.mp4" )}
@@ -308,16 +309,7 @@ const Trending = ({ trending }: trendingProps) => {
           </div>
         )}
       </div>
-      <div className="relative w-full aspect-[3/4] mt-8">
-        <Image
-          src={
-            "https://cdn.asteriahair.com/uploads/202607/01/20260701165110_3fff435adf276b89.jpg"
-          }
-          alt={"cscscs"}
-          fill
-          className="object-cover rounded-md"
-        />
-      </div>
+    
     </div>
   );
 };

+ 39 - 0
src/app/api/shop/theme-customizations/route.ts

@@ -0,0 +1,39 @@
+import {  type NextRequest,NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+
+
+export async function GET(req: NextRequest) {
+  try {
+    //  const { id } = await params; // 解包拿到订单ID
+  
+     const searchParams = req.nextUrl.searchParams; 
+   const apiUrl = `/shop/theme-customizations`;
+
+    console.log("最终请求API地址:", apiUrl);
+
+    const response = await restApiFetch<any>({
+      api: apiUrl,
+      method: "GET",
+      cache: "no-store",
+      variables:{
+        name:searchParams.get("name") ?? "Top_Bar",
+      }
+    });
+
+    return NextResponse.json({
+      status: response.status,
+      data: response.body,
+    });
+
+  } catch (error) {
+
+
+    return NextResponse.json(
+      {
+        message: "Network error",
+        error: error instanceof Error ? error.message : error,
+      },
+      { status: 500 }
+    );
+  }
+}

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

@@ -0,0 +1,117 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import Link from "next/link";
+
+const ITEM_HEIGHT = 40;
+const INTERVAL = 3000;
+const TRANSITION_DURATION = 500;
+
+export interface AnnouncementItem {
+  link: string;
+  image?: string;
+  title: string;
+}
+
+ interface AnnouncementBarProps {
+  items?: AnnouncementItem[];
+}
+
+export default function AnnouncementBar({ items = [] }: AnnouncementBarProps) {
+  const [index, setIndex] = useState(0);
+  const [enableTransition, setEnableTransition] = useState(true);
+
+  // 没有数据不显示
+  if (!items.length) {
+    return null;
+  }
+
+  const loopItems = [...items, items[0]];
+  useEffect(() => {
+    if (items.length <= 1) {
+      return;
+    }
+
+    const timer = window.setInterval(() => {
+      setIndex((prev) => prev + 1);
+    }, INTERVAL);
+
+    return () => {
+      window.clearInterval(timer);
+    };
+  }, [items.length]);
+
+  useEffect(() => {
+    if (index !== items.length) {
+      return;
+    }
+
+    const timer = window.setTimeout(() => {
+      setEnableTransition(false);
+
+      setIndex(0);
+
+      requestAnimationFrame(() => {
+        requestAnimationFrame(() => {
+          setEnableTransition(true);
+        });
+      });
+    }, TRANSITION_DURATION);
+
+    return () => {
+      window.clearTimeout(timer);
+    };
+  }, [index, items.length]);
+
+  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)`,
+        }}
+      >
+        {loopItems.map((item, itemIndex) => (
+          <div
+            key={itemIndex}
+            className="
+                  flex
+                  h-10
+                  items-center
+                  justify-center
+                  px-4
+                  text-center
+                  text-[14px]
+                  font-medium
+                  text-white
+                "
+          >
+            {item.link ? (
+              <Link
+                href={item.link}
+                className="
+                        hover:underline
+                      "
+              >
+                {item.title}
+              </Link>
+            ) : (
+              <span>{item.title}</span>
+            )}
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+}

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

@@ -13,8 +13,33 @@ 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"
+import { restApiFetch } from "@utils/bagisto/index";
+import type { AnnouncementItem } from "./TopSwiperBanner";
 export default async function Navbar() {
-
+  // 2. 服务端 fetch 请求我们自己的 Next.js API
+  const res = await restApiFetch({
+    api: `/shop/theme-customizations?name=Top_Bar`,
+    method: "GET",
+    isRoute: false,
+    takeAuthorization: false,
+  
+  });
+  let topBarData: AnnouncementItem[] = [];
+  try {
+    // 安全取值链
+    const themeItem = res?.body?.[0];
+    const optionsStr = themeItem?.translation?.options;
+    if (optionsStr) {
+      const parsed = JSON.parse(optionsStr);
+      topBarData = parsed?.images ?? [];
+    }
+  } catch (err) {
+    console.error("解析 translation.options 失败", err);
+    topBarData = [];
+  }
+  console.log('topbar-res----------------------------:',res);
+  
     const session = await getServerSession(authOptions);// 游客是null
     const isGuest = session === null;
 
@@ -29,6 +54,7 @@ export default async function Navbar() {
   return (
     <NavbarErrorBoundary>
       <header className="sticky top-0 z-10">
+         <TopSwiperBanner  items={topBarData} /> 
         <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) */}

+ 1 - 0
src/types/home/type.ts

@@ -26,6 +26,7 @@ type HomeSection = {
   type: "image_carousel" | "product_carousel";
   name: string;
   sort_order: number;
+  showstyle?: string;
   title?: string;
   images?: HomeSectionImage[];
   products?: HomeProduct[];