diff --git a/app/layout.jsx b/app/layout.jsx
index d2cdb7a..fb31c92 100644
--- a/app/layout.jsx
+++ b/app/layout.jsx
@@ -1,205 +1,14 @@
-"use client";
-import { useEffect, useRef } from "react";
import "../public/assets/style.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 ProgressWrap from "@/components/common/ProgressWrap";
-import initPlayer from "@/utils/initPlayer";
import SearchModal from "@/components/modals/SearchModal";
import InfoModal from "@/components/modals/InfoModal";
import Header from "@/components/headers/Header";
import Footer from "@/components/footers/Footer";
+import ClientAppEffects from "@/components/common/ClientAppEffects";
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 (
@@ -213,7 +22,6 @@ export default function RootLayout({ children }) {
rel="stylesheet"
/>
-
@@ -223,6 +31,7 @@ export default function RootLayout({ children }) {
+
);
diff --git a/components/common/ClientAppEffects.jsx b/components/common/ClientAppEffects.jsx
new file mode 100644
index 0000000..933d49c
--- /dev/null
+++ b/components/common/ClientAppEffects.jsx
@@ -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;
+}
\ No newline at end of file
diff --git a/components/contact/SocialLinks.jsx b/components/contact/SocialLinks.jsx
index 2bc0613..e897f4c 100644
--- a/components/contact/SocialLinks.jsx
+++ b/components/contact/SocialLinks.jsx
@@ -1,3 +1,5 @@
+
+"use client";
import React, { useEffect, useState } from "react";
import axios from "axios";
import ReactDOM from "react-dom";
diff --git a/components/headers/Header.jsx b/components/headers/Header.jsx
index bea7940..7d79d8c 100644
--- a/components/headers/Header.jsx
+++ b/components/headers/Header.jsx
@@ -1,5 +1,4 @@
-'use client';
-import React, { useEffect, useState } from "react";
+import React from "react";
import Nav from "./Nav";
import Link from "next/link";
import Image from "next/image";
@@ -7,13 +6,8 @@ import LoginButton from "./LoginButton";
import SocialLinks from "../contact/SocialLinks";
import { getSiteSettings } from "@/utils/siteSettings";
-export default function Header15() {
- const [siteSettings, setSiteSettings] = useState({});
-
- useEffect(() => {
- getSiteSettings().then(setSiteSettings);
- }, []);
-
+export default async function Header() {
+ const siteSettings = await getSiteSettings();
const siteName = siteSettings.site_name || "Jsite";
const mobile = siteSettings.mobile;
const tel = siteSettings.tel;
diff --git a/components/headers/Nav.jsx b/components/headers/Nav.jsx
deleted file mode 100644
index 0853f1f..0000000
--- a/components/headers/Nav.jsx
+++ /dev/null
@@ -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 (
- 0 ? " dropdown-submenu dropend" : ""}`}>
-
- 0 ? " submenu" : ""}`}>
- {renderMenu(item.children, level + 1)}
-
-
- );
- } else {
- return (
-
- 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}
-
-
- );
- }
- });
- };
-
- return (
-
- );
-}
diff --git a/components/headers/Nav/NavUI.jsx b/components/headers/Nav/NavUI.jsx
new file mode 100644
index 0000000..703fe51
--- /dev/null
+++ b/components/headers/Nav/NavUI.jsx
@@ -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 (
+ 0 ? " dropdown-submenu dropend" : ""}`}>
+
+ 0 ? " submenu" : ""}`}>
+ {renderMenu(item.children, level + 1)}
+
+
+ );
+ } else {
+ return (
+
+ 0 ? "dropdown-item" : "nav-link"} !text-[.7rem] !tracking-[normal] hover:!text-[var(--current-color)] after:!text-[var(--current-color)]`}
+ href={item.href || "#"}
+ >
+ {item.label}
+
+
+ );
+ }
+ });
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/components/headers/Nav/index.jsx b/components/headers/Nav/index.jsx
new file mode 100644
index 0000000..2fcad4e
--- /dev/null
+++ b/components/headers/Nav/index.jsx
@@ -0,0 +1,7 @@
+import { getMenuData } from '@/utils/data';
+import NavUI from './NavUI';
+
+export default async function Nav(props) {
+ const { menu } = await getMenuData();
+ return ;
+}
\ No newline at end of file
diff --git a/utils/data.js b/utils/data.js
index 693a3a0..57d07ee 100644
--- a/utils/data.js
+++ b/utils/data.js
@@ -237,4 +237,36 @@ export async function fetchCategoryData({ pagetype, name }) {
console.error(`Error fetching category data:`, error.message);
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 };
+ }
}
\ No newline at end of file