重构layout重构header和菜单
This commit is contained in:
parent
7cb5ccec85
commit
260bce7b28
195
app/layout.jsx
195
app/layout.jsx
@ -1,205 +1,14 @@
|
|||||||
"use client";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import "../public/assets/style.css";
|
import "../public/assets/style.css";
|
||||||
import "photoswipe/dist/photoswipe.css";
|
import "photoswipe/dist/photoswipe.css";
|
||||||
import iTooltip from "itooltip";
|
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
import scrollQue from "../utils/scrollCue.min.js";
|
|
||||||
import Context from "@/context/Context";
|
import Context from "@/context/Context";
|
||||||
import ProgressWrap from "@/components/common/ProgressWrap";
|
import ProgressWrap from "@/components/common/ProgressWrap";
|
||||||
import initPlayer from "@/utils/initPlayer";
|
|
||||||
import SearchModal from "@/components/modals/SearchModal";
|
import SearchModal from "@/components/modals/SearchModal";
|
||||||
import InfoModal from "@/components/modals/InfoModal";
|
import InfoModal from "@/components/modals/InfoModal";
|
||||||
import Header from "@/components/headers/Header";
|
import Header from "@/components/headers/Header";
|
||||||
import Footer from "@/components/footers/Footer";
|
import Footer from "@/components/footers/Footer";
|
||||||
|
import ClientAppEffects from "@/components/common/ClientAppEffects";
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
export default function RootLayout({ children }) {
|
||||||
const pathname = usePathname();
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
import("bootstrap/dist/js/bootstrap.esm").then((module) => {
|
|
||||||
// Module is imported, you can access any exported functionality if
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
// const rellaxRef = useRef(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
scrollQue().init();
|
|
||||||
window.dispatchEvent(new Event("scroll"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// rellaxRef.current?.destroy();
|
|
||||||
};
|
|
||||||
}, [pathname]);
|
|
||||||
useEffect(() => {
|
|
||||||
initPlayer();
|
|
||||||
const overlayElements = document.querySelectorAll(
|
|
||||||
".overlay > a, .overlay > span"
|
|
||||||
);
|
|
||||||
overlayElements.forEach((element) => {
|
|
||||||
const overlayBg = document.createElement("span");
|
|
||||||
overlayBg.className = "bg";
|
|
||||||
element.appendChild(overlayBg);
|
|
||||||
});
|
|
||||||
}, [pathname]);
|
|
||||||
useEffect(() => {
|
|
||||||
const tooltipTriggerList = document.querySelectorAll(
|
|
||||||
'[data-bs-toggle="tooltip"]'
|
|
||||||
);
|
|
||||||
const popoverTriggerList = document.querySelectorAll(
|
|
||||||
'[data-bs-toggle="popover"]'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tooltipTriggerList.length > 0 || popoverTriggerList.length > 0) {
|
|
||||||
import("bootstrap/dist/js/bootstrap.bundle.min").then((bootstrap) => {
|
|
||||||
// Initialize tooltips
|
|
||||||
const tooltipList = Array.from(tooltipTriggerList).map(
|
|
||||||
(tooltipTriggerEl) => {
|
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
|
||||||
trigger: "hover",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize popovers
|
|
||||||
const popoverList = Array.from(popoverTriggerList).map(
|
|
||||||
(popoverTriggerEl) => {
|
|
||||||
return new bootstrap.Popover(popoverTriggerEl);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cleanup tooltips and popovers on component unmount
|
|
||||||
return () => {
|
|
||||||
tooltipList.forEach((tooltip) => tooltip.dispose());
|
|
||||||
popoverList.forEach((popover) => popover.dispose());
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [pathname]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleSticky = () => {
|
|
||||||
const navbar = document.querySelector(".navbar");
|
|
||||||
if (navbar) {
|
|
||||||
if (window.scrollY > 120) {
|
|
||||||
navbar.classList.add("fixed");
|
|
||||||
navbar.classList.add("navbar-clone");
|
|
||||||
if (
|
|
||||||
navbar.classList.contains("transparent") &&
|
|
||||||
navbar.classList.contains("navbar-dark")
|
|
||||||
) {
|
|
||||||
navbar.classList.remove("navbar-dark");
|
|
||||||
navbar.classList.add("navbar-light");
|
|
||||||
navbar.classList.add("navbar-dark-removed");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
navbar.classList.remove("fixed");
|
|
||||||
navbar.classList.remove("navbar-clone");
|
|
||||||
if (
|
|
||||||
navbar.classList.contains("transparent") &&
|
|
||||||
navbar.classList.contains("navbar-dark-removed")
|
|
||||||
) {
|
|
||||||
navbar.classList.add("navbar-dark");
|
|
||||||
navbar.classList.remove("navbar-light");
|
|
||||||
navbar.classList.remove("navbar-dark-removed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (window.scrollY > 300) {
|
|
||||||
navbar.classList.add("navbar-stick");
|
|
||||||
} else {
|
|
||||||
navbar.classList.remove("navbar-stick");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("scroll", handleSticky);
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
// Close any open modal
|
|
||||||
const bootstrap = require("bootstrap"); // dynamically import bootstrap
|
|
||||||
const modalElements = document.querySelectorAll(".modal.show");
|
|
||||||
modalElements.forEach((modal) => {
|
|
||||||
const modalInstance = bootstrap.Modal.getInstance(modal);
|
|
||||||
if (modalInstance) {
|
|
||||||
modalInstance.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close any open offcanvas
|
|
||||||
const offcanvasElements = document.querySelectorAll(".offcanvas.show");
|
|
||||||
offcanvasElements.forEach((offcanvas) => {
|
|
||||||
const offcanvasInstance = bootstrap.Offcanvas.getInstance(offcanvas);
|
|
||||||
if (offcanvasInstance) {
|
|
||||||
offcanvasInstance.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Select all elements with the class 'offcanvas-backdrop'
|
|
||||||
const backdrops = document.querySelectorAll(".offcanvas-backdrop");
|
|
||||||
|
|
||||||
// Check if any backdrop elements exist and remove them
|
|
||||||
backdrops?.forEach((backdrop) => {
|
|
||||||
backdrop?.remove();
|
|
||||||
});
|
|
||||||
}, [pathname]); // Runs every time the route changes
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Assuming iTooltip is globally available
|
|
||||||
var tooltip = new iTooltip(".itooltip");
|
|
||||||
tooltip.init({
|
|
||||||
className: "itooltip-inner",
|
|
||||||
indentX: 15,
|
|
||||||
indentY: 15,
|
|
||||||
positionX: "right",
|
|
||||||
positionY: "bottom",
|
|
||||||
});
|
|
||||||
}, [pathname]);
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
import("bootstrap").then(({ Offcanvas }) => {
|
|
||||||
const navbar = document.querySelector(".navbar");
|
|
||||||
if (!navbar) return;
|
|
||||||
|
|
||||||
const navOffCanvasBtn = document.querySelectorAll(".offcanvas-nav-btn");
|
|
||||||
const navOffCanvas = document.querySelector(
|
|
||||||
".navbar:not(.navbar-clone) .offcanvas-nav"
|
|
||||||
);
|
|
||||||
if (!navOffCanvas) return;
|
|
||||||
|
|
||||||
const bsOffCanvas = new Offcanvas(navOffCanvas, { scroll: true });
|
|
||||||
const scrollLink = document.querySelectorAll(
|
|
||||||
".onepage .navbar li a.scroll"
|
|
||||||
);
|
|
||||||
const searchOffcanvas = document.getElementById("offcanvas-search");
|
|
||||||
|
|
||||||
const handleNavClick = () => bsOffCanvas.show();
|
|
||||||
const handleScrollClick = () => bsOffCanvas.hide();
|
|
||||||
|
|
||||||
navOffCanvasBtn.forEach((e) =>
|
|
||||||
e.addEventListener("click", handleNavClick)
|
|
||||||
);
|
|
||||||
scrollLink.forEach((e) =>
|
|
||||||
e.addEventListener("click", handleScrollClick)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (searchOffcanvas) {
|
|
||||||
searchOffcanvas.addEventListener("shown.bs.offcanvas", () => {
|
|
||||||
document.getElementById("search-form")?.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
navOffCanvasBtn.forEach((e) =>
|
|
||||||
e.removeEventListener("click", handleNavClick)
|
|
||||||
);
|
|
||||||
scrollLink.forEach((e) =>
|
|
||||||
e.removeEventListener("click", handleScrollClick)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [pathname]);
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -213,7 +22,6 @@ export default function RootLayout({ children }) {
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Header />
|
<Header />
|
||||||
<Context>
|
<Context>
|
||||||
@ -223,6 +31,7 @@ export default function RootLayout({ children }) {
|
|||||||
<ProgressWrap />
|
<ProgressWrap />
|
||||||
</Context>
|
</Context>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<ClientAppEffects />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
194
components/common/ClientAppEffects.jsx
Normal file
194
components/common/ClientAppEffects.jsx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import iTooltip from "itooltip";
|
||||||
|
import scrollQue from "@/utils/scrollCue.min.js";
|
||||||
|
import initPlayer from "@/utils/initPlayer";
|
||||||
|
|
||||||
|
export default function ClientAppEffects() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
import("bootstrap/dist/js/bootstrap.esm");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
scrollQue().init();
|
||||||
|
window.dispatchEvent(new Event("scroll"));
|
||||||
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initPlayer();
|
||||||
|
const overlayElements = document.querySelectorAll(
|
||||||
|
".overlay > a, .overlay > span"
|
||||||
|
);
|
||||||
|
overlayElements.forEach((element) => {
|
||||||
|
const overlayBg = document.createElement("span");
|
||||||
|
overlayBg.className = "bg";
|
||||||
|
element.appendChild(overlayBg);
|
||||||
|
});
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tooltipTriggerList = document.querySelectorAll(
|
||||||
|
'[data-bs-toggle="tooltip"]'
|
||||||
|
);
|
||||||
|
const popoverTriggerList = document.querySelectorAll(
|
||||||
|
'[data-bs-toggle="popover"]'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tooltipTriggerList.length > 0 || popoverTriggerList.length > 0) {
|
||||||
|
import("bootstrap/dist/js/bootstrap.bundle.min").then((bootstrap) => {
|
||||||
|
// Initialize tooltips
|
||||||
|
const tooltipList = Array.from(tooltipTriggerList).map(
|
||||||
|
(tooltipTriggerEl) => {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
||||||
|
trigger: "hover",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize popovers
|
||||||
|
const popoverList = Array.from(popoverTriggerList).map(
|
||||||
|
(popoverTriggerEl) => {
|
||||||
|
return new bootstrap.Popover(popoverTriggerEl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cleanup tooltips and popovers on component unmount
|
||||||
|
return () => {
|
||||||
|
tooltipList.forEach((tooltip) => tooltip.dispose());
|
||||||
|
popoverList.forEach((popover) => popover.dispose());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleSticky = () => {
|
||||||
|
const navbar = document.querySelector(".navbar");
|
||||||
|
if (navbar) {
|
||||||
|
if (window.scrollY > 120) {
|
||||||
|
navbar.classList.add("fixed");
|
||||||
|
navbar.classList.add("navbar-clone");
|
||||||
|
if (
|
||||||
|
navbar.classList.contains("transparent") &&
|
||||||
|
navbar.classList.contains("navbar-dark")
|
||||||
|
) {
|
||||||
|
navbar.classList.remove("navbar-dark");
|
||||||
|
navbar.classList.add("navbar-light");
|
||||||
|
navbar.classList.add("navbar-dark-removed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navbar.classList.remove("fixed");
|
||||||
|
navbar.classList.remove("navbar-clone");
|
||||||
|
if (
|
||||||
|
navbar.classList.contains("transparent") &&
|
||||||
|
navbar.classList.contains("navbar-dark-removed")
|
||||||
|
) {
|
||||||
|
navbar.classList.add("navbar-dark");
|
||||||
|
navbar.classList.remove("navbar-light");
|
||||||
|
navbar.classList.remove("navbar-dark-removed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window.scrollY > 300) {
|
||||||
|
navbar.classList.add("navbar-stick");
|
||||||
|
} else {
|
||||||
|
navbar.classList.remove("navbar-stick");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("scroll", handleSticky);
|
||||||
|
return () => window.removeEventListener("scroll", handleSticky);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
import("bootstrap").then((bootstrap) => {
|
||||||
|
const modalElements = document.querySelectorAll(".modal.show");
|
||||||
|
modalElements.forEach((modal) => {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close any open offcanvas
|
||||||
|
const offcanvasElements = document.querySelectorAll(".offcanvas.show");
|
||||||
|
offcanvasElements.forEach((offcanvas) => {
|
||||||
|
const offcanvasInstance = bootstrap.Offcanvas.getInstance(offcanvas);
|
||||||
|
if (offcanvasInstance) {
|
||||||
|
offcanvasInstance.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Select all elements with the class 'offcanvas-backdrop'
|
||||||
|
const backdrops = document.querySelectorAll(".offcanvas-backdrop");
|
||||||
|
backdrops?.forEach((backdrop) => {
|
||||||
|
backdrop?.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
var tooltip = new iTooltip(".itooltip");
|
||||||
|
tooltip.init({
|
||||||
|
className: "itooltip-inner",
|
||||||
|
indentX: 15,
|
||||||
|
indentY: 15,
|
||||||
|
positionX: "right",
|
||||||
|
positionY: "bottom",
|
||||||
|
});
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
import("bootstrap").then(({ Offcanvas }) => {
|
||||||
|
const navbar = document.querySelector(".navbar");
|
||||||
|
if (!navbar) return;
|
||||||
|
|
||||||
|
const navOffCanvasBtn = document.querySelectorAll(".offcanvas-nav-btn");
|
||||||
|
const navOffCanvas = document.querySelector(
|
||||||
|
".navbar:not(.navbar-clone) .offcanvas-nav"
|
||||||
|
);
|
||||||
|
if (!navOffCanvas) return;
|
||||||
|
|
||||||
|
const bsOffCanvas = new Offcanvas(navOffCanvas, { scroll: true });
|
||||||
|
const scrollLink = document.querySelectorAll(
|
||||||
|
".onepage .navbar li a.scroll"
|
||||||
|
);
|
||||||
|
const searchOffcanvas = document.getElementById("offcanvas-search");
|
||||||
|
|
||||||
|
const handleNavClick = () => bsOffCanvas.show();
|
||||||
|
const handleScrollClick = () => bsOffCanvas.hide();
|
||||||
|
|
||||||
|
navOffCanvasBtn.forEach((e) =>
|
||||||
|
e.addEventListener("click", handleNavClick)
|
||||||
|
);
|
||||||
|
scrollLink.forEach((e) =>
|
||||||
|
e.addEventListener("click", handleScrollClick)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (searchOffcanvas) {
|
||||||
|
searchOffcanvas.addEventListener("shown.bs.offcanvas", () => {
|
||||||
|
document.getElementById("search-form")?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
navOffCanvasBtn.forEach((e) =>
|
||||||
|
e.removeEventListener("click", handleNavClick)
|
||||||
|
);
|
||||||
|
scrollLink.forEach((e) =>
|
||||||
|
e.removeEventListener("click", handleScrollClick)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
"use client";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
'use client';
|
import React from "react";
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Nav from "./Nav";
|
import Nav from "./Nav";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@ -7,13 +6,8 @@ import LoginButton from "./LoginButton";
|
|||||||
import SocialLinks from "../contact/SocialLinks";
|
import SocialLinks from "../contact/SocialLinks";
|
||||||
import { getSiteSettings } from "@/utils/siteSettings";
|
import { getSiteSettings } from "@/utils/siteSettings";
|
||||||
|
|
||||||
export default function Header15() {
|
export default async function Header() {
|
||||||
const [siteSettings, setSiteSettings] = useState({});
|
const siteSettings = await getSiteSettings();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getSiteSettings().then(setSiteSettings);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const siteName = siteSettings.site_name || "Jsite";
|
const siteName = siteSettings.site_name || "Jsite";
|
||||||
const mobile = siteSettings.mobile;
|
const mobile = siteSettings.mobile;
|
||||||
const tel = siteSettings.tel;
|
const tel = siteSettings.tel;
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
"use client";
|
|
||||||
import Link from "next/link";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import {
|
|
||||||
blockItems,
|
|
||||||
blogItems,
|
|
||||||
demos,
|
|
||||||
docsPages,
|
|
||||||
otherPages,
|
|
||||||
projectPages,
|
|
||||||
} from "@/data/menu";
|
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
// !text-[var(--current-color)]
|
|
||||||
export default function Nav({ color = "#fab758" }) {
|
|
||||||
const [menu, setMenu] = useState([]);
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Dynamically import Bootstrap
|
|
||||||
import("bootstrap").then((Bootstrap) => {
|
|
||||||
const CLASS_NAME = "has-child-dropdown-show";
|
|
||||||
|
|
||||||
// Save the original toggle function
|
|
||||||
const originalToggle = Bootstrap.Dropdown.prototype.toggle;
|
|
||||||
|
|
||||||
// Override the toggle function
|
|
||||||
Bootstrap.Dropdown.prototype.toggle = function () {
|
|
||||||
// Remove the CLASS_NAME from all dropdowns
|
|
||||||
document.querySelectorAll("." + CLASS_NAME).forEach(function (e) {
|
|
||||||
e.classList.remove(CLASS_NAME);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Traverse up through the closest dropdown parents
|
|
||||||
let dd = this._element
|
|
||||||
.closest(".dropdown")
|
|
||||||
.parentNode.closest(".dropdown");
|
|
||||||
for (; dd && dd !== document; dd = dd.parentNode.closest(".dropdown")) {
|
|
||||||
dd.classList.add(CLASS_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the original toggle function
|
|
||||||
return originalToggle.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add event listeners for hide.bs.dropdown to remove the CLASS_NAME
|
|
||||||
document.querySelectorAll(".dropdown").forEach(function (dd) {
|
|
||||||
dd.addEventListener("hide.bs.dropdown", function (e) {
|
|
||||||
if (this.classList.contains(CLASS_NAME)) {
|
|
||||||
this.classList.remove(CLASS_NAME);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取菜单数据
|
|
||||||
fetch("/api/get-menu")
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => setMenu(data.menu || []));
|
|
||||||
|
|
||||||
// Optional cleanup function for any potential side effects
|
|
||||||
return () => {
|
|
||||||
// Cleanup code here (if needed)
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 只在顶级菜单过滤 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") + " dropdown-toggle"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)] ${!hasHref && pathname === item.href ? "!text-[var(--current-color)]" : ""}`}
|
|
||||||
href="#"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
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)}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
62
components/headers/Nav/NavUI.jsx
Normal file
62
components/headers/Nav/NavUI.jsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function NavUI({ menu = [], color = "#fab758" }) {
|
||||||
|
const filterHeaderMenu = (items) => {
|
||||||
|
return items.filter(item => item.position === 'Header');
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderMenu = (items, level = 0) => {
|
||||||
|
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' }}>
|
||||||
|
{hasHref && (
|
||||||
|
<Link
|
||||||
|
className={`${level > 0 ? "dropdown-item" : "nav-link"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)]`}
|
||||||
|
href={item.href}
|
||||||
|
style={{ paddingRight: 0 }}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<a
|
||||||
|
className={`${hasHref ? "dropdown-toggle" : (level > 0 ? "dropdown-item" : "nav-link") + " dropdown-toggle"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)]`}
|
||||||
|
href="#"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
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)]`}
|
||||||
|
href={item.href || "#"}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="navbar-nav" style={{ "--current-color": color }}>
|
||||||
|
{renderMenu(menu)}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
components/headers/Nav/index.jsx
Normal file
7
components/headers/Nav/index.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { getMenuData } from '@/utils/data';
|
||||||
|
import NavUI from './NavUI';
|
||||||
|
|
||||||
|
export default async function Nav(props) {
|
||||||
|
const { menu } = await getMenuData();
|
||||||
|
return <NavUI menu={menu} {...props} />;
|
||||||
|
}
|
||||||
@ -238,3 +238,35 @@ export async function fetchCategoryData({ pagetype, name }) {
|
|||||||
return { data: null };
|
return { data: null };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通用获取菜单数据函数
|
||||||
|
export async function getMenuData() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${JINGROW_SERVER_URL}/api/method/jsite.api.v1.get_menu`
|
||||||
|
);
|
||||||
|
const items = response.data.message?.data || [];
|
||||||
|
// 递归组装菜单树
|
||||||
|
function buildMenuTree(items, parent = null, parentPath = "") {
|
||||||
|
return items
|
||||||
|
.filter(item => (item.parent_jsite_menu === parent || (!item.parent_jsite_menu && !parent)))
|
||||||
|
.map(item => {
|
||||||
|
let currentPath = parentPath ? `${parentPath}/${item.slug.replace(/^\//, "")}` : item.slug;
|
||||||
|
if (!currentPath.startsWith('/')) currentPath = '/' + currentPath;
|
||||||
|
return {
|
||||||
|
id: item.name,
|
||||||
|
label: item.title,
|
||||||
|
href: currentPath,
|
||||||
|
position: item.position,
|
||||||
|
order: item.order,
|
||||||
|
icon: item.icon,
|
||||||
|
children: buildMenuTree(items, item.name, currentPath),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const menuTree = buildMenuTree(items);
|
||||||
|
return { menu: menuTree };
|
||||||
|
} catch (error) {
|
||||||
|
return { error: error.message, detail: error?.response?.data || null };
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user