|
|
@@ -0,0 +1,485 @@
|
|
|
+"use client";
|
|
|
+
|
|
|
+import { useState } from "react";
|
|
|
+import Image from "next/image";
|
|
|
+import Link from "next/link";
|
|
|
+import type { TreefrontMenu } from "@/types/theme/menus";
|
|
|
+interface MenuEdge {
|
|
|
+ node: MenuItem;
|
|
|
+}
|
|
|
+
|
|
|
+interface MenuChildren {
|
|
|
+ edges: MenuEdge[];
|
|
|
+}
|
|
|
+
|
|
|
+export interface MenuItem {
|
|
|
+ _id: number;
|
|
|
+ name: string;
|
|
|
+ slug: string;
|
|
|
+ url: string;
|
|
|
+
|
|
|
+ showIcon?: boolean;
|
|
|
+
|
|
|
+ image?: string;
|
|
|
+ wapImage?: string;
|
|
|
+
|
|
|
+ banner?: string;
|
|
|
+ wapBanner?: string;
|
|
|
+
|
|
|
+ sortOrder: number;
|
|
|
+ status: boolean;
|
|
|
+
|
|
|
+ children?: MenuChildren;
|
|
|
+}
|
|
|
+
|
|
|
+interface DesktopNavigationProps {
|
|
|
+ menus:TreefrontMenu[];
|
|
|
+}
|
|
|
+
|
|
|
+const getMenuUrl = (url?: string) => {
|
|
|
+ if (!url) return "#";
|
|
|
+
|
|
|
+ if (
|
|
|
+ url.startsWith("/") ||
|
|
|
+ url.startsWith("http://") ||
|
|
|
+ url.startsWith("https://")
|
|
|
+ ) {
|
|
|
+ return url;
|
|
|
+ }
|
|
|
+
|
|
|
+ return `/${url}`;
|
|
|
+};
|
|
|
+
|
|
|
+const getChildren = (menu?: MenuItem) => {
|
|
|
+ if (!menu?.children?.edges) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ return menu.children.edges
|
|
|
+ .map((edge) => edge.node)
|
|
|
+ .filter((item) => item.status)
|
|
|
+ .sort((a, b) => a.sortOrder - b.sortOrder);
|
|
|
+};
|
|
|
+// 设置特殊分类ID
|
|
|
+const SPECIAL_IMAGE_MENU_IDS = new Set([999]);
|
|
|
+export default function DesktopNavigation({
|
|
|
+ menus,
|
|
|
+}: DesktopNavigationProps) {
|
|
|
+ const [activeMenu, setActiveMenu] =
|
|
|
+ useState<MenuItem | null>(null);
|
|
|
+
|
|
|
+ const topMenus = menus
|
|
|
+ .filter((item) => item.status)
|
|
|
+ .sort((a, b) => a.sortOrder - b.sortOrder);
|
|
|
+
|
|
|
+ const secondLevelMenus = getChildren(
|
|
|
+ activeMenu ?? undefined,
|
|
|
+ );
|
|
|
+ const banner =
|
|
|
+ activeMenu?.banner ||
|
|
|
+ activeMenu?.wapBanner ||
|
|
|
+ "";
|
|
|
+ // 是否是特殊图片菜单
|
|
|
+ const isSpecialImageMenu =
|
|
|
+ activeMenu !== null &&
|
|
|
+ SPECIAL_IMAGE_MENU_IDS.has(activeMenu._id);
|
|
|
+
|
|
|
+ // 特殊结构:
|
|
|
+ // 一级 -> 唯一二级 -> 多个三级
|
|
|
+ const specialSecondMenu =
|
|
|
+ isSpecialImageMenu
|
|
|
+ ? secondLevelMenus[0]
|
|
|
+ : undefined;
|
|
|
+
|
|
|
+ const specialThirdLevelMenus =
|
|
|
+ specialSecondMenu
|
|
|
+ ? getChildren(specialSecondMenu)
|
|
|
+ : [];
|
|
|
+
|
|
|
+
|
|
|
+ const hasMegaMenu =
|
|
|
+ activeMenu !== null &&
|
|
|
+ (
|
|
|
+ isSpecialImageMenu
|
|
|
+ ? specialThirdLevelMenus.length > 0
|
|
|
+ : Boolean(banner) ||
|
|
|
+ secondLevelMenus.length > 0
|
|
|
+ );
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ className="relative z-50 w-full bg-white"
|
|
|
+ onMouseLeave={() => {
|
|
|
+ setActiveMenu(null);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {/* =========================
|
|
|
+ 一级导航
|
|
|
+ ========================= */}
|
|
|
+ <div className="border-b border-[#EEEEEE]">
|
|
|
+ <nav
|
|
|
+ className="
|
|
|
+ mx-auto
|
|
|
+ flex
|
|
|
+ h-[50px]
|
|
|
+ max-w-[1440px]
|
|
|
+ items-stretch
|
|
|
+ justify-between
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {/* Home */}
|
|
|
+ <Link
|
|
|
+ href="/"
|
|
|
+ onMouseEnter={() => {
|
|
|
+ setActiveMenu(null);
|
|
|
+ }}
|
|
|
+ className={`
|
|
|
+ relative
|
|
|
+ flex
|
|
|
+ items-center
|
|
|
+ px-2
|
|
|
+ text-[16px]
|
|
|
+ font-medium
|
|
|
+ transition-colors
|
|
|
+ ${
|
|
|
+ activeMenu === null
|
|
|
+ ? "text-[#036141]"
|
|
|
+ : "text-[#111111] hover:text-[#036141]"
|
|
|
+ }
|
|
|
+ `}
|
|
|
+ >
|
|
|
+ Home
|
|
|
+
|
|
|
+ {activeMenu === null && (
|
|
|
+ <span
|
|
|
+ className="
|
|
|
+ absolute
|
|
|
+ bottom-0
|
|
|
+ left-0
|
|
|
+ h-[3px]
|
|
|
+ w-full
|
|
|
+ bg-[#036141]
|
|
|
+ "
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </Link>
|
|
|
+
|
|
|
+ {/* 接口一级菜单 */}
|
|
|
+ {topMenus.map((menu) => {
|
|
|
+ const isActive =
|
|
|
+ activeMenu?._id === menu._id;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Link
|
|
|
+ key={menu._id}
|
|
|
+ href={getMenuUrl(menu.url)}
|
|
|
+ onMouseEnter={() => {
|
|
|
+ setActiveMenu(menu);
|
|
|
+ }}
|
|
|
+ className={`
|
|
|
+ relative
|
|
|
+ flex
|
|
|
+ items-center
|
|
|
+ px-2
|
|
|
+ text-[16px]
|
|
|
+ font-medium
|
|
|
+ transition-colors
|
|
|
+ ${
|
|
|
+ isActive
|
|
|
+ ? "text-[#036141]"
|
|
|
+ : "text-[#111111] hover:text-[#036141]"
|
|
|
+ }
|
|
|
+ `}
|
|
|
+ >
|
|
|
+ {menu.name}
|
|
|
+
|
|
|
+ {isActive && (
|
|
|
+ <span
|
|
|
+ className="
|
|
|
+ absolute
|
|
|
+ bottom-0
|
|
|
+ left-0
|
|
|
+ h-[3px]
|
|
|
+ w-full
|
|
|
+ bg-[#036141]
|
|
|
+ "
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </Link>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </nav>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* =========================
|
|
|
+ Mega Menu
|
|
|
+ ========================= */}
|
|
|
+ {hasMegaMenu && activeMenu && (
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ absolute
|
|
|
+ left-0
|
|
|
+ top-full
|
|
|
+ w-full
|
|
|
+ border-b
|
|
|
+ border-[#EEEEEE]
|
|
|
+ bg-white
|
|
|
+ shadow-[0_8px_20px_rgba(0,0,0,0.04)]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {/* ======================================
|
|
|
+ 特殊一级分类:
|
|
|
+ 一级 -> 唯一二级 -> 多个三级图片
|
|
|
+ ======================================= */}
|
|
|
+ {isSpecialImageMenu ? (
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ mx-auto
|
|
|
+ flex
|
|
|
+ w-full
|
|
|
+ justify-center
|
|
|
+ px-10
|
|
|
+ py-5
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ flex
|
|
|
+ flex-wrap
|
|
|
+ justify-center
|
|
|
+ gap-x-10
|
|
|
+ gap-y-8
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {specialThirdLevelMenus.map(
|
|
|
+ (thirdMenu) => {
|
|
|
+ const content = (
|
|
|
+ <>
|
|
|
+ {/* 三级分类图片 */}
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ relative
|
|
|
+ aspect-[3/4]
|
|
|
+ w-full
|
|
|
+ overflow-hidden
|
|
|
+ "
|
|
|
+ >
|
|
|
+ { thirdMenu.image && (
|
|
|
+ <Image
|
|
|
+ src={thirdMenu.image}
|
|
|
+ alt={thirdMenu.name}
|
|
|
+ fill
|
|
|
+ sizes="192px"
|
|
|
+ className="object-cover"
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 三级分类名称 */}
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ mt-3
|
|
|
+ text-center
|
|
|
+ text-[16px]
|
|
|
+ font-medium
|
|
|
+ leading-[24px]
|
|
|
+ text-[#111111]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {thirdMenu.name}
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+
|
|
|
+ return thirdMenu.url ? (
|
|
|
+ <Link
|
|
|
+ key={thirdMenu._id}
|
|
|
+ href={getMenuUrl(
|
|
|
+ thirdMenu.url,
|
|
|
+ )}
|
|
|
+ className="
|
|
|
+ block
|
|
|
+ w-[192px]
|
|
|
+ shrink-0
|
|
|
+ transition-opacity
|
|
|
+ hover:opacity-80
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {content}
|
|
|
+ </Link>
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ key={thirdMenu._id}
|
|
|
+ className="
|
|
|
+ w-[192px]
|
|
|
+ shrink-0
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {content}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ /* ======================================
|
|
|
+ 普通 Mega Menu
|
|
|
+ ======================================= */
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ mx-auto
|
|
|
+ flex
|
|
|
+ max-w-[1220px]
|
|
|
+ gap-8
|
|
|
+ px-6
|
|
|
+ py-6
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {/* 左侧 Banner */}
|
|
|
+ {banner && (
|
|
|
+ <Link
|
|
|
+ href={getMenuUrl(
|
|
|
+ activeMenu.url,
|
|
|
+ )}
|
|
|
+ className="
|
|
|
+ relative
|
|
|
+ block
|
|
|
+ aspect-[3/4]
|
|
|
+ w-[330px]
|
|
|
+ shrink-0
|
|
|
+ overflow-hidden
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <Image
|
|
|
+ src={banner}
|
|
|
+ alt={activeMenu.name}
|
|
|
+ fill
|
|
|
+ sizes="330px"
|
|
|
+ className="object-cover"
|
|
|
+ />
|
|
|
+ </Link>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 二级 / 三级文字菜单 */}
|
|
|
+ {secondLevelMenus.length > 0 && (
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ grid
|
|
|
+ min-w-0
|
|
|
+ flex-1
|
|
|
+ grid-cols-3
|
|
|
+ items-start
|
|
|
+ gap-x-16
|
|
|
+ gap-y-8
|
|
|
+ pt-2
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {secondLevelMenus.map(
|
|
|
+ (secondMenu) => {
|
|
|
+ const thirdLevelMenus =
|
|
|
+ getChildren(
|
|
|
+ secondMenu,
|
|
|
+ );
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={secondMenu._id}
|
|
|
+ className="min-w-0"
|
|
|
+ >
|
|
|
+ {/* 二级标题 */}
|
|
|
+ {secondMenu.url ? (
|
|
|
+ <Link
|
|
|
+ href={getMenuUrl(
|
|
|
+ secondMenu.url,
|
|
|
+ )}
|
|
|
+ className="
|
|
|
+ inline-block
|
|
|
+ text-[15px]
|
|
|
+ font-semibold
|
|
|
+ text-[#111111]
|
|
|
+ transition-colors
|
|
|
+ hover:text-[#036141]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {secondMenu.name}
|
|
|
+ </Link>
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ text-[15px]
|
|
|
+ font-semibold
|
|
|
+ text-[#111111]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {secondMenu.name}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 三级菜单 */}
|
|
|
+ {thirdLevelMenus.length >
|
|
|
+ 0 && (
|
|
|
+ <div
|
|
|
+ className="
|
|
|
+ mt-5
|
|
|
+ flex
|
|
|
+ flex-col
|
|
|
+ gap-[18px]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {thirdLevelMenus.map(
|
|
|
+ (thirdMenu) =>
|
|
|
+ thirdMenu.url ? (
|
|
|
+ <Link
|
|
|
+ key={
|
|
|
+ thirdMenu._id
|
|
|
+ }
|
|
|
+ href={getMenuUrl(
|
|
|
+ thirdMenu.url,
|
|
|
+ )}
|
|
|
+ className="
|
|
|
+ block
|
|
|
+ text-[14px]
|
|
|
+ leading-[20px]
|
|
|
+ text-[#222222]
|
|
|
+ transition-colors
|
|
|
+ hover:text-[#036141]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {
|
|
|
+ thirdMenu.name
|
|
|
+ }
|
|
|
+ </Link>
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ key={
|
|
|
+ thirdMenu._id
|
|
|
+ }
|
|
|
+ className="
|
|
|
+ text-[14px]
|
|
|
+ leading-[20px]
|
|
|
+ text-[#222222]
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {
|
|
|
+ thirdMenu.name
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ },
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|