Просмотр исходного кода

网站头部和menu修改,删除底部导航栏,引入swiper

fogwind 1 неделя назад
Родитель
Сommit
c23482c33e

BIN
public/image/logo.png


Разница между файлами не показана из-за своего большого размера
+ 11 - 37
src/components/common/icons/LogoIcon.tsx


+ 3 - 3
src/components/common/slider/HeroCarouselShimmer.tsx

@@ -1,14 +1,14 @@
 "use client";
 
 import { Shimmer } from "@/components/common/Shimmer";
-
-export function HeroCarouselShimmer() {
+// 占位组件
+export function HeroCarouselShimmer({ratio = "3/4"} : {ratio?: string}) {
   return (
     <div className="group relative overflow-hidden">
       <div
         className="group relative h-full max-h-[738px] w-full overflow-hidden rounded-2xl"
         style={{
-          aspectRatio: "380/316",
+          aspectRatio: ratio,
         }}
       >
         <div className="relative h-full w-full">

+ 0 - 55
src/components/layout/navbar/CategoriesMenu.tsx

@@ -1,55 +0,0 @@
-import Link from "next/link";
-import { GET_TREE_CATEGORIES } from "@/graphql";
-import MobileMenu from "./MobileMenu";
-import { cachedGraphQLRequest } from "@utils/hooks/useCache";
-import { TreeCategoriesResponse } from "@/types/theme/category-tree";
-
-export async function CategoriesMenu() {
-   const data = await cachedGraphQLRequest<TreeCategoriesResponse>(
-    "category",
-    GET_TREE_CATEGORIES,
-    { parentId: 1 }
-  );
-
-
-  const categories = data?.treeCategories || [];
-
-  const filteredCategories = categories
-    .filter((cat: any) => cat.id !== "1")
-    .map((cat: any) => {
-      const translation = cat.translation;
-      return {
-        id: cat.id,
-        name: translation?.name || "",
-        slug: translation?.slug || "",
-      };
-    })
-    .filter((item: any) => item.name && item.slug);
-
-  const menuData = [
-    { id: "all", name: "All", slug: "" },
-    ...filteredCategories.slice(0, 3),
-  ];
-
-  return (
-    <>
-      <MobileMenu menu={menuData} />
-      <ul className="hidden gap-4 text-sm md:items-center lg:flex xl:gap-6">
-        {menuData.map(
-          (item: { id: string; name: string; slug: string }) => (
-            <li key={item?.id + item?.name}>
-              <Link
-                className="text-nowrap relative text-neutral-500 before:absolute before:bottom-0 before:left-0 before:h-px before:w-0 before:bg-current before:transition-all before:duration-300 before:content-[''] hover:text-black hover:before:w-full dark:text-neutral-400 dark:hover:text-neutral-300"
-                href={item.slug ? `/search/${item.slug}` : "/search"}
-                prefetch={true}
-                aria-label={`Browse ${item.name} products`}
-              >
-                {item.name}
-              </Link>
-            </li>
-          )
-        )}
-      </ul>
-    </>
-  );
-}

+ 9 - 17
src/components/layout/navbar/MobileMenu.tsx

@@ -7,27 +7,19 @@ import { MobileSearchBar } from "./MobileSearch";
 import { useState } from "react";
 import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
 
-export default function MobileMenu({ menu }: { menu: any }) {
-  const [activeTab, setActiveTab] = useState<
-    "home" | "category" | "cart" | "account" | null
-  >("home");
+interface MobileMenuProps {
+  menu: any[];
+  isOpen: boolean;
+  onClose: () => void; // 接收关闭函数
+}
+export default function MobileMenu({ menu, isOpen, onClose }: MobileMenuProps) {
 
-  const isOpen = activeTab === "category";
 
   useBodyScrollLock(isOpen);
 
-  const handleClose = () => {
-    setActiveTab(null);
-  };
 
   return (
     <>
-      <BottomNavbar
-        onMenuOpen={() => setActiveTab("category")}
-        setActiveTab={setActiveTab}
-        activeTab={activeTab}
-      />
-
       <AnimatePresence>
         {isOpen && (
           <>
@@ -35,7 +27,7 @@ export default function MobileMenu({ menu }: { menu: any }) {
               initial={{ opacity: 0 }}
               animate={{ opacity: 1 }}
               exit={{ opacity: 0 }}
-              onClick={handleClose}
+              onClick={onClose}
               className="fixed inset-0 z-40 bg-transparent lg:hidden"
               style={{ top: "68px", bottom: "64px" }}
             />
@@ -55,7 +47,7 @@ export default function MobileMenu({ menu }: { menu: any }) {
               }}
             >
               <div className="h-full overflow-y-auto px-4 py-4 drawer-scrollbar-hidden">
-                <MobileSearchBar onClose={handleClose} />
+                <MobileSearchBar onClose={onClose} />
 
                 <h1 className="mt-4 px-2 text-2xl font-semibold text-black dark:text-white">
                   Category
@@ -70,7 +62,7 @@ export default function MobileMenu({ menu }: { menu: any }) {
                       <Link
                         href={item.slug ? `/search/${item.slug}` : "/search"}
                         aria-label={`${item?.name}`}
-                        onClick={handleClose}
+                        onClick={onClose}
                       >
                         {item.name}
                       </Link>

+ 27 - 0
src/components/layout/navbar/MobileMenuTrigger.tsx

@@ -0,0 +1,27 @@
+'use client';
+
+import { useState } from "react";
+import MobileMenu from "./MobileMenu";
+
+export default function MobileMenuTrigger({ menuData }: { menuData: any[] }) {
+  const [isOpen, setIsOpen] = useState(false);
+  const toggleMenu = () => setIsOpen((prev) => !prev);
+  const closeMenu = () => setIsOpen(false);
+
+  return (
+    <>
+      {/* 汉堡按钮 */}
+      <button className="flex-initial w-6 h-6" onClick={toggleMenu}>
+        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
+          <rect x="0" y="0" width="24" height="24" fill="#FFFFFF" fillOpacity="0" />
+          <path stroke="currentColor" strokeWidth="1.5" d="M4 5L20 5" />
+          <path stroke="currentColor" strokeWidth="1.5" d="M4 12L20 12" />
+          <path stroke="currentColor" strokeWidth="1.5" d="M4 19L20 19" />
+        </svg>
+      </button>
+
+      {/* 抽屉菜单 */}
+      <MobileMenu menu={menuData} isOpen={isOpen} onClose={closeMenu} />
+    </>
+  );
+}

+ 41 - 7
src/components/layout/navbar/index.tsx

@@ -3,13 +3,45 @@ import { Suspense } from "react";
 import Search from "./Search";
 import { SearchSkeleton } from "@/components/common/skeleton/SearchSkeleton";
 import LogoIcon from "@components/common/icons/LogoIcon";
-import { CategoriesMenu } from "./CategoriesMenu";
+import MobileMenuTrigger from "./MobileMenuTrigger";
 import { CartAndUserActions } from "./CartAndUserActions";
 import { NavigationSkeleton } from "./NavigationSkeleton";
 import { ActionsSkeleton } from "./ActionsSkeleton";
 import { NavbarErrorBoundary } from "@/components/error/ErrorBoundary";
 
-export default function Navbar() {
+import { GET_TREE_CATEGORIES } from "@/graphql";
+import { cachedGraphQLRequest } from "@utils/hooks/useCache";
+import { TreeCategoriesResponse } from "@/types/theme/category-tree";
+
+export default async function Navbar() {
+
+  const data = await cachedGraphQLRequest<TreeCategoriesResponse>(
+      "category",
+      GET_TREE_CATEGORIES,
+      { parentId: 1 }
+    );
+  
+  
+    const categories = data?.treeCategories || [];
+  
+    const filteredCategories = categories
+      .filter((cat: any) => cat.id !== "1")
+      .map((cat: any) => {
+        const translation = cat.translation;
+        return {
+          id: cat.id,
+          name: translation?.name || "",
+          slug: translation?.slug || "",
+        };
+      })
+      .filter((item: any) => item.name && item.slug);
+  
+    const menuData = [
+      { id: "all", name: "All", slug: "" },
+      ...filteredCategories.slice(0, 3),
+    ];
+  
+
   return (
     <NavbarErrorBoundary>
       <header className="sticky top-0 z-10">
@@ -17,18 +49,20 @@ export default function Navbar() {
           <div className="flex w-full items-center justify-between gap-0 sm:gap-4">
             {/* 1. THE STATIC SHELL (Visible Instantly) */}
             <div className="flex max-w-fit gap-2 xl:gap-6">
+
+              {/* 2. STATIC HOLE: Categories (Suspended) */}
+              <Suspense fallback={<NavigationSkeleton />}>
+                <MobileMenuTrigger menuData={menuData} />
+              </Suspense>
               <Link
-                className="flex h-9 w-full scale-95 items-center md:h-9 md:w-auto lg:h-10"
+                className="flex-initial h-9 w-36.5"
                 href="/"
                 aria-label="Go to homepage"
               >
                 <LogoIcon />
               </Link>
               
-              {/* 2. STATIC HOLE: Categories (Suspended) */}
-              <Suspense fallback={<NavigationSkeleton />}>
-                <CategoriesMenu />
-              </Suspense>
+              
             </div>
 
             <div className="hidden flex-1 justify-center md:flex">

+ 0 - 57
src/utils/LRUCache.ts

@@ -1,57 +0,0 @@
-interface CacheEntry<T> {
-    value: T;
-    timestamp: number;
-}
-export class LRUCache<T> {
-    private cache: Map<string, CacheEntry<T>>;
-    private maxSize: number;
-    private ttl: number;
-
-    constructor(maxSize: number = 100, ttlMinutes: number = 10) {
-        this.cache = new Map();
-        this.maxSize = maxSize;
-        this.ttl = ttlMinutes * 60 * 1000;
-    }
-
-    get(key: string): T | null {
-        const entry = this.cache.get(key);
-
-        if (!entry) {
-            return null;
-        }
-
-        if (Date.now() - entry.timestamp > this.ttl) {
-            this.cache.delete(key);
-            return null;
-        }
-
-        this.cache.delete(key);
-        this.cache.set(key, entry);
-
-        return entry.value;
-    }
-
-    set(key: string, value: T): void {
-        if (this.cache.has(key)) {
-            this.cache.delete(key);
-        }
-        if (this.cache.size >= this.maxSize) {
-            const firstKey = this.cache.keys().next().value;
-            if (firstKey) {
-                this.cache.delete(firstKey);
-            }
-        }
-        this.cache.set(key, {
-            value,
-            timestamp: Date.now(),
-        });
-    }
-
-    clear(): void {
-        this.cache.clear();
-    }
-
-    size(): number {
-        return this.cache.size;
-    }
-}