242 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
// !text-[var(--current-color)]
export default function Nav({ color = "#fab758" }) {
const [menu, setMenu] = useState([]);
const pathname = usePathname();
useEffect(() => {
// 获取菜单数据
fetch("/api/get-menu")
.then((res) => res.json())
.then((data) => setMenu(data.menu || []));
// 点击外部区域关闭下拉菜单
const handleClickOutside = (event) => {
if (!event.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown-menu').forEach(menu => {
menu.style.display = 'none';
});
}
};
// 为多级下拉菜单添加事件监听器
const handleDropdownEvents = () => {
document.querySelectorAll('.dropdown-submenu').forEach(submenu => {
submenu.addEventListener('mouseenter', function() {
const dropdownMenu = this.querySelector('.dropdown-menu');
if (dropdownMenu) {
dropdownMenu.style.display = 'block';
}
});
submenu.addEventListener('mouseleave', function() {
const dropdownMenu = this.querySelector('.dropdown-menu');
if (dropdownMenu) {
dropdownMenu.style.display = 'none';
}
});
});
};
// 等待 DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', handleDropdownEvents);
} else {
// 延迟执行,确保菜单已经渲染
setTimeout(handleDropdownEvents, 100);
}
// 添加全局点击事件监听器
document.addEventListener('click', handleClickOutside);
return () => {
// 清理事件监听器
document.removeEventListener('click', handleClickOutside);
document.querySelectorAll('.dropdown-submenu').forEach(submenu => {
submenu.removeEventListener('mouseenter', handleDropdownEvents);
submenu.removeEventListener('mouseleave', handleDropdownEvents);
});
};
}, []);
// 只在顶级菜单过滤 position
const filterHeaderMenu = (items) => {
return items.filter(item => item.position === 'Header');
};
// 递归渲染菜单
const renderMenu = (items, level = 0) => {
// 顶级菜单过滤 position
if (level === 0) items = filterHeaderMenu(items);
return items.map((item) => {
const hasChildren = item.children && item.children.length > 0;
const hasHref = !!item.href && item.href !== "#";
if (hasChildren) {
return (
<li key={item.id} className={`nav-item dropdown${level > 0 ? " dropdown-submenu dropend" : ""}`}>
<div style={{ display: 'flex', alignItems: 'center' }}>
{/* 如果有href左侧为可点击跳转始终用当前item的href */}
{hasHref && (
<Link
className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
href={item.href}
style={{ paddingRight: 0 }}
>
{item.label}
</Link>
)}
{/* 右侧小三角,负责展开下拉 */}
<a
className={`${hasHref ? "dropdown-toggle" : (level > 0 ? "dropdown-item" : "nav-link")} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${!hasHref && pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
href="#"
onClick={(e) => {
e.preventDefault();
const dropdownMenu = e.target.closest('.dropdown').querySelector('.dropdown-menu');
if (dropdownMenu) {
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
}
}}
style={hasHref ? { paddingLeft: 8, minWidth: 24 } : {}}
>
{hasHref ? <span className="sr-only">展开</span> : item.label}
</a>
</div>
<ul className={`dropdown-menu${level > 0 ? " submenu" : ""}`}>
{renderMenu(item.children, level + 1)}
</ul>
</li>
);
} else {
return (
<li key={item.id} className="nav-item">
<Link
className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
href={item.href || "#"}
>
{item.label}
</Link>
</li>
);
}
});
};
return (
<ul className="navbar-nav" style={{ "--current-color": color }}>
{renderMenu(menu)}
<style jsx>{`
.navbar-nav .dropdown-submenu {
position: relative;
}
.navbar-nav .dropdown-submenu .dropdown-menu {
top: 0;
left: 100%;
margin-top: -1px;
display: none;
position: absolute;
z-index: 1000;
}
.navbar-nav .dropdown-submenu:hover > .dropdown-menu {
display: block;
}
.dropdown-submenu.dropend .dropdown-menu {
left: 100%;
top: 0;
}
.dropdown-submenu > .dropdown-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid transparent;
border-right: 0;
border-bottom: 0.3em solid transparent;
border-left: 0.3em solid;
}
.dropdown-menu {
display: none;
position: absolute;
z-index: 1000;
min-width: 10rem;
padding: 0.5rem 0;
margin: 0;
font-size: 1rem;
color: #212529;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.375rem;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.dropdown-menu.show {
display: block;
}
.dropdown-item {
display: block;
width: 100%;
padding: 0.25rem 1rem;
clear: both;
font-weight: 400;
color: #212529;
text-align: inherit;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border: 0;
}
.dropdown-item:hover,
.dropdown-item:focus {
color: #1e2125;
background-color: #e9ecef;
}
.nav-link {
color: #6c757d;
text-decoration: none;
padding: 0.5rem 1rem;
}
.nav-link:hover {
color: var(--current-color);
}
.dropdown-submenu .dropdown-submenu .dropdown-menu {
z-index: 1001;
}
/* 确保顶级下拉菜单正确显示 */
.navbar-nav > .dropdown > .dropdown-menu {
top: 100%;
left: 0;
margin-top: 0;
}
/* 添加下拉箭头指示器 */
.dropdown-toggle::after {
display: inline-block;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}
`}</style>
</ul>
);
}