import { useState, useEffect, useMemo, type ComponentType, type ElementType, Suspense } from 'react'; import { motion } from 'motion/react'; import { Building2, ChevronRight, ShieldCheck } from 'lucide-react'; import { useAuth } from '../auth/useAuth'; import { DemoModeProvider } from './Blur'; import FeedbackFab from './FeedbackFab'; import { cn } from '../lib/cn'; import { LoadingState } from './ui/surface'; export interface ModuleConfig { id: string; label: string; icon: ComponentType<{ size?: number; className?: string }>; component?: ElementType; children?: ModuleConfig[]; } function flattenModules(modules: ModuleConfig[]): ModuleConfig[] { return modules.flatMap((module) => module.children ?? [module]); } /** hash 一级段(`#` 或 `#/` 都只取 id) */ function getHashHead(): string { return window.location.hash.slice(1).split('/')[0]; } function getInitialModule(modules: ModuleConfig[]): string { const head = getHashHead(); if (modules.some((m) => m.id === head)) return head; return modules[0]?.id ?? ''; } function getHashModule(modules: ModuleConfig[]): string { const head = getHashHead(); return modules.some((m) => m.id === head) ? head : ''; } export function Shell({ modules }: { modules: ModuleConfig[] }) { const flatModules = useMemo(() => flattenModules(modules), [modules]); const [activeModule, setActiveModule] = useState(() => getInitialModule(flattenModules(modules))); const [openGroupId, setOpenGroupId] = useState(null); useEffect(() => { const onHashChange = () => { const h = getHashModule(flatModules); if (h) setActiveModule(h); }; window.addEventListener('hashchange', onHashChange); return () => window.removeEventListener('hashchange', onHashChange); }, [flatModules]); useEffect(() => { if (!openGroupId) return undefined; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setOpenGroupId(null); }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [openGroupId]); useEffect(() => { // 同步 hash 一段到当前模块:使用 replaceState 避免产生多余的 history 记录, // 否则在小程序/webview 环境下首次进入需要点两次返回才能退出。 // 注意只比对一级段,避免把子模块写入的 `#/` 二级段抹掉。 if (getHashHead() !== activeModule) { const { pathname, search } = window.location; window.history.replaceState(null, '', `${pathname}${search}#${activeModule}`); } }, [activeModule]); const switchModule = (id: string) => { setOpenGroupId(null); if (getHashHead() === id) { setActiveModule(id); return; } const { pathname, search } = window.location; window.history.replaceState(null, '', `${pathname}${search}#${id}`); setActiveModule(id); }; const ActiveComponent = flatModules.find((module) => module.id === activeModule)?.component ?? flatModules[0]?.component; const { user } = useAuth(); const activeLabel = flatModules.find((module) => module.id === activeModule)?.label ?? '业务看板'; const watermarkText = useMemo(() => { const name = user?.userName || '未登录'; const time = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-'); return `${name}-${time}`; }, [user]); return (
{/* 全局水印 */}
${watermarkText}`)}")`, backgroundRepeat: 'repeat', }} />
{/* Web 侧边栏 (md 及以上) */} {/* 内容区 */}
羚牛氢能 BI / {activeLabel}
}> {ActiveComponent && } {/* 移动端底部导航 (md 以下) */}
); }