128 lines
7.5 KiB
TypeScript
128 lines
7.5 KiB
TypeScript
import { Link, useLocation } from "react-router-dom";
|
|
import { Dropdown } from "antd";
|
|
import styles from "./Header.module.css";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { useStore } from "@/store";
|
|
import type { NavChild, LocaleKey, SupportLocale } from "@/type";
|
|
|
|
const DEFAULT_NAV_ITEMS: { path: string; label: string; children?: NavChild[] }[] = [];
|
|
|
|
export default function Header() {
|
|
const location = useLocation();
|
|
const appConfig = useStore((s) => s.appConfig);
|
|
const locale = useStore((s) => s.locale);
|
|
const setLocale = useStore((s) => s.setLocale);
|
|
const supportLocales = useStore((s) => s.supportLocales);
|
|
|
|
const navItems = appConfig?.header?.navItems?.filter((item) => !item.index) ?? DEFAULT_NAV_ITEMS;
|
|
const langMenuItems: SupportLocale[] = supportLocales || [];
|
|
const logo = appConfig?.logo ?? "/images/logo.png";
|
|
|
|
const [activeNav, setActiveNav] = useState("");
|
|
const [showDropPanel, setShowDropPanel] = useState(false);
|
|
const [hoverElLeft, setHoverElLeft] = useState(0);
|
|
const handleNavEnter = (e: any, path: string) => {
|
|
const left = e.target.offsetLeft;
|
|
// 计算元素宽度
|
|
const width = e.target.offsetWidth;
|
|
setHoverElLeft(left + width / 2);
|
|
setActiveNav(path);
|
|
setShowDropPanel(true);
|
|
}
|
|
|
|
const activePanelItem = useMemo(() => {
|
|
return navItems.find((item) => item.path === activeNav)?.children || [];
|
|
}, [activeNav]);
|
|
|
|
const [showWhiteMode, setShowWhiteMode] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const path = location.pathname;
|
|
if (path.includes("/detail/")) {
|
|
setShowWhiteMode(true);
|
|
} else {
|
|
setShowWhiteMode(false);
|
|
}
|
|
}, [location.pathname]);
|
|
|
|
// 监听滚动
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
const scrollTop = window.scrollY;
|
|
if (scrollTop > 100) {
|
|
setShowWhiteMode(true);
|
|
} else {
|
|
setShowWhiteMode(false);
|
|
}
|
|
}
|
|
window.addEventListener("scroll", handleScroll);
|
|
return () => {
|
|
window.removeEventListener("scroll", handleScroll);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<header className={`${styles.header} ${(showDropPanel || showWhiteMode) ? styles.hoverMenu : ""}`}
|
|
onMouseLeave={() => setShowDropPanel(false)}
|
|
>
|
|
<div className={`header-row ${styles.headerInner}`}>
|
|
<Link to="/" className={styles.logo}>
|
|
<img src={logo} alt="logo" style={{ width: "5.75rem", height: "3.4375rem" }} />
|
|
</Link>
|
|
<div className={styles.headerRight}>
|
|
<nav>
|
|
<ul className={styles.nav}>
|
|
{navItems.map((item) => (
|
|
<li key={item.path} className={styles.navItem} >
|
|
<div className={styles.navLink} onMouseEnter={(e) => handleNavEnter(e, item.path)}>
|
|
{item.label}
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
<div className={styles.actions}>
|
|
<Link to="/search" className={styles.searchBtn} type="button" aria-label="搜索">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1" width="26" height="26" viewBox="0 0 26 26"><g><path d="M16.739973374999998,17.76985499081421C15.429279375,18.85114099081421,13.749150275,19.50064899081421,11.917317875,19.50064899081421C7.729150975,19.50064899081421,4.333984375,16.105480990814208,4.333984375,11.91731549081421C4.333984375,7.729148590814209,7.729150975,4.333981990814209,11.917317875,4.333981990814209C16.105485375,4.333981990814209,19.500651375,7.729148590814209,19.500651375,11.91731549081421C19.500651375,13.80559489081421,18.810497375,15.532677990814209,17.668674375000002,16.86007799081421L21.741985375,20.93338899081421C21.953518375,21.144921990814208,21.953518375,21.48788599081421,21.741983375,21.69941999081421L21.588777375,21.852625990814207C21.377243375,22.06416099081421,21.034278375,22.06416099081421,20.822744375,21.852625990814207L16.739973374999998,17.76985499081421ZM18.199479375,11.916142890814209C18.199479375,15.38633899081421,15.386343375,18.199475990814207,11.916145775,18.199475990814207C8.445947675,18.199475990814207,5.632812475,15.38633899081421,5.632812475,11.916142890814209C5.632812475,8.44594479081421,8.445947675,5.632810090814209,11.916145775,5.632810090814209C15.386343375,5.632810090814209,18.199479375,8.44594479081421,18.199479375,11.916142890814209Z" fillRule="evenodd" fill="#FFFFFF" fillOpacity="1" /></g></svg>
|
|
</Link>
|
|
<Dropdown
|
|
menu={{
|
|
items: langMenuItems.map((item: SupportLocale) => ({
|
|
...item,
|
|
onClick: () => setLocale(item.key),
|
|
})),
|
|
}}
|
|
placement="bottomRight"
|
|
trigger={["click"]}
|
|
>
|
|
<button className={styles.langTrigger} type="button">
|
|
<span style={{ fontSize: "18px" }}>
|
|
{langMenuItems.find((i: SupportLocale) => i.key === locale)?.label ?? "中文"}
|
|
</span>
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1" width="24" height="24" viewBox="0 0 24 24"><g transform="matrix(0,1,-1,0,24,-24)"><path d="M46.720364062499996,30.4020875C46.720364062499996,30.4020875,45.6597041625,31.462787499999997,45.6597041625,31.462787499999997C45.6597041625,31.462787499999997,39.8815300725,25.6845775,39.8815300725,25.6845775C39.4910054655,25.2940474,39.4910053615,24.6608877,39.8815300725,24.2703576C39.8815300725,24.2703576,45.6597041625,18.4921875,45.6597041625,18.4921875C45.6597041625,18.4921875,46.720364062499996,19.5528475,46.720364062499996,19.5528475C46.720364062499996,19.5528475,41.2957440625,24.9774675,41.2957440625,24.9774675C41.2957440625,24.9774675,46.720364062499996,30.4020875,46.720364062499996,30.4020875C46.720364062499996,30.4020875,46.720364062499996,30.4020875,46.720364062499996,30.4020875Z" fillRule="evenodd" fill="#FFFFFF" fillOpacity="1" transform="matrix(-1,0,0,-1,79.173828125,36.984375)" /></g></svg>
|
|
</button>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{showDropPanel && activePanelItem.length > 0 && <DropPanel items={activePanelItem} left={hoverElLeft} onLinkClick={() => setShowDropPanel(false)} />}
|
|
</header>
|
|
);
|
|
}
|
|
function DropPanel({ items, left, onLinkClick }: { items: NavChild[]; left: number, onLinkClick: () => void }) {
|
|
|
|
return (
|
|
<div id="drop-panel" className={styles.dropPanel} style={{ paddingLeft: left, }}>
|
|
<div className={styles.dropPanelContent}>
|
|
{items.map((item) => (
|
|
<div key={item.path} className={styles.dropPanelItem}>
|
|
<Link to={item.path} className={styles.dropPanelLink} onClick={onLinkClick}>
|
|
{item.label}
|
|
</Link>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
} |