import { useState, useEffect } from "react"; import { Outlet, Link, useLocation } from "react-router"; import type { LucideIcon } from "lucide-react"; import { Menu, Home, Share2, Compass, Library, Globe, BookOpen, Wrench, } from "lucide-react"; import { TibetanPattern } from "./TibetanPattern"; import { fetchAndApplyKioskBundle } from "../api/kioskApi"; import { DEFAULT_HOME_BG_URLS, KIOSK_EVENT, loadHomeBackgrounds } from "../kioskStorage"; import { useI18n } from "../i18n"; const BG_ROTATE_MS = 7000; function preloadImages(urls: readonly string[]) { urls.forEach((src) => { const img = new Image(); img.src = src; }); } export function Layout() { const { t } = useI18n(); const [sidebarOpen, setSidebarOpen] = useState(false); const [homeBgIndex, setHomeBgIndex] = useState(0); const [homeBackgrounds, setHomeBackgrounds] = useState(loadHomeBackgrounds); const location = useLocation(); const isHome = location.pathname === "/"; const isTourPage = location.pathname === "/tour"; const isSearchPage = location.pathname === "/search"; const isGuidePage = location.pathname === "/guide"; const isWebPage = location.pathname === "/web"; const isAdminPage = location.pathname === "/admin"; const isViewportLockPage = isTourPage || isSearchPage || isGuidePage || isWebPage; /** 顶栏品牌标题全站统一(非首页顶栏显示) */ const headerBrandTitle = t("brand.title"); useEffect(() => { const sync = () => { setHomeBackgrounds(loadHomeBackgrounds()); }; window.addEventListener(KIOSK_EVENT, sync); return () => { window.removeEventListener(KIOSK_EVENT, sync); }; }, []); /** 启动时从后端拉取 bundle 写入内存(不写 localStorage;失败时首页背景等仍用内置默认图) */ useEffect(() => { let cancelled = false; void (async () => { try { await fetchAndApplyKioskBundle(); } catch (e) { if (!cancelled) { console.warn("[kiosk] 无法从后端拉取 bundle", e); } } })(); return () => { cancelled = true; }; }, []); useEffect(() => { preloadImages(homeBackgrounds); }, [homeBackgrounds]); useEffect(() => { setHomeBgIndex((i) => homeBackgrounds.length === 0 ? 0 : Math.min(i, homeBackgrounds.length - 1) ); }, [homeBackgrounds.length]); useEffect(() => { if (!isHome) return; const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); if (reduceMotion.matches) return; const n = homeBackgrounds.length; if (n <= 1) return; const interval = window.setInterval(() => { setHomeBgIndex((prev) => (prev + 1) % n); }, BG_ROTATE_MS); return () => window.clearInterval(interval); }, [isHome, homeBackgrounds.length]); useEffect(() => { const root = document.documentElement; if (isViewportLockPage) { root.classList.add("app-viewport-lock"); } else { root.classList.remove("app-viewport-lock"); } return () => root.classList.remove("app-viewport-lock"); }, [isViewportLockPage]); /** 与首页六块入口顺序、文案一致;首页不含「首页」项,顶栏保留首页便于返回 */ const navItems: ( | { id: string; path: string; label: string; icon: LucideIcon } | { id: string; href: string; label: string; icon: LucideIcon; external: true } )[] = [ { id: "home", path: "/", label: t("nav.home"), icon: Home }, { id: "tour", path: "/tour", label: t("nav.tour"), icon: Share2 }, { id: "planetarium", path: "/planetarium", label: t("nav.planetarium"), icon: Compass }, { id: "wiki", path: "/search", label: t("nav.wiki"), icon: Library }, { id: "web", path: "/web", label: t("nav.web"), icon: Globe }, { id: "guide", path: "/guide", label: t("nav.guide"), icon: BookOpen }, { id: "maintain", path: "/admin", label: t("nav.maintain"), icon: Wrench }, ]; return (
{/* 动态背景层:首页多图轮播;其余页固定首页首张 + 与操作指南同款渐变 */}
{isHome ? ( <> {(homeBackgrounds.length ? homeBackgrounds : [...DEFAULT_HOME_BG_URLS]).map((bg, index) => (
))}
) : ( <>
)}
{/* 藏式装饰纹理叠层(全站隐藏,与操作指南所用背景风格一致) */}
{/* 主容器:共享天文台为固定一屏高度;其余页至少一屏高 */}
{/* 顶部导航栏 */}
{!isHome ? (

{headerBrandTitle}

) : null}
{/* 移动端侧边栏 */} {sidebarOpen && (
setSidebarOpen(false)} />

导航菜单

)} {/* 页面内容;共享天文台单屏布局:占满剩余高度且不出现主区域整页滚动 */}
); }