import { lazy, Suspense, useEffect, useMemo, useState } from "react"; import { Truck, Route, Activity, Fuel, BatteryCharging, Receipt, MapPinned } from "lucide-react"; import { Shell, type ModuleConfig } from "./components/Shell"; import AuthProvider from "./auth/AuthProvider"; import { useAuth } from "./auth/useAuth"; import UnauthorizedPage from "./auth/UnauthorizedPage"; import { canAccessScheduling, canAccessEnergy } from "./shared/auth/roles"; import { LoadingState, SkeletonBlock, SurfaceCard } from "./components/ui/surface"; const AssetsModule = lazy(() => import("./modules/assets/AssetsModule")); const MileageModule = lazy(() => import("./modules/mileage/MileageModule")); const SchedulingModule = lazy(() => import("./modules/scheduling/SchedulingModule")); const HydrogenModule = lazy(() => import("./modules/energy/HydrogenModule")); const ElectricModule = lazy(() => import("./modules/energy/ElectricModule")); const EtcModule = lazy(() => import("./modules/energy/EtcModule")); const EleImportPage = lazy(() => import("./modules/ele/EleImportPage")); const FeedbackAdminPage = lazy(() => import("./modules/admin/FeedbackAdminPage")); const VehicleHeatmapModule = lazy(() => import("./modules/vehicle-heatmap/VehicleHeatmapModule")); const HydrogenHeatmapModule = lazy(() => import("./modules/hydrogen-heatmap/HydrogenHeatmapModule")); const ASSETS_MODULE: ModuleConfig = { id: "assets", label: "资产管理", icon: Truck, component: AssetsModule, }; const MILEAGE_MODULE: ModuleConfig = { id: "mileage", label: "里程管理", icon: Route, component: MileageModule, }; const VEHICLE_HEATMAP_MODULE: ModuleConfig = { id: "vehicle-heatmap", label: "车辆", icon: MapPinned, component: VehicleHeatmapModule, }; const SCHEDULING_MODULE: ModuleConfig = { id: "scheduling", label: "智能调度", icon: Activity, component: SchedulingModule, }; const HYDROGEN_MODULE: ModuleConfig = { id: "hydrogen", label: "氢能", icon: Fuel, component: HydrogenModule, }; const HYDROGEN_HEATMAP_MODULE: ModuleConfig = { id: "hydrogen-heatmap", label: "加氢", icon: MapPinned, component: HydrogenHeatmapModule, }; const ELECTRIC_MODULE: ModuleConfig = { id: "electric", label: "电能", icon: BatteryCharging, component: ElectricModule, }; const ETC_MODULE: ModuleConfig = { id: "etc", label: "ETC", icon: Receipt, component: EtcModule, }; /** * 把旧路径 / 根路径归一化到 `/asset` 或 `/energy` 主路径, * 必要时携带 hash 一段定位到具体模块。已是主路径或后台管理页则不动。 */ function normalizePath() { if (typeof window === "undefined") return; const { pathname, hash, search } = window.location; // 主路径 & 隐藏后台页保持不变 if (pathname === "/asset" || pathname === "/energy") return; if (pathname === "/ele/import" || pathname === "/admin/feedback") return; const legacyMap: Record = { "/": { path: "/asset" }, "/vehicle": { path: "/asset", hash: "assets" }, "/assets": { path: "/asset", hash: "assets" }, "/mileage": { path: "/asset", hash: "mileage" }, "/scheduling": { path: "/asset", hash: "scheduling" }, }; // 未知路径兜底到 /asset(保留原 hash 让 Shell 内部继续解析) const target = legacyMap[pathname] ?? { path: "/asset" }; const finalHash = target.hash ? `#${target.hash}` : hash || ""; window.history.replaceState(null, "", `${target.path}${search}${finalHash}`); } normalizePath(); type PathSet = "asset" | "energy"; function getPathSet(): PathSet { return window.location.pathname === "/energy" ? "energy" : "asset"; } function getRouteKey(): string { if (typeof window === "undefined") return ""; const path = window.location.pathname; const hash = window.location.hash; if ( path === "/ele/import" || hash === "#/ele/import" || hash === "#ele/import" ) return "ele/import"; if ( path === "/admin/feedback" || hash === "#/admin/feedback" || hash === "#admin/feedback" ) return "admin/feedback"; return ""; } function AuthGate() { const { isLoading, isAuthenticated, error, user } = useAuth(); const [routeKey, setRouteKey] = useState(getRouteKey); const [pathSet, setPathSet] = useState(getPathSet); // 监听 hashchange / popstate,让 a href="#/..." 跳转与浏览器前进后退能即时生效 useEffect(() => { const update = () => { setRouteKey(getRouteKey()); setPathSet(getPathSet()); }; window.addEventListener("hashchange", update); window.addEventListener("popstate", update); return () => { window.removeEventListener("hashchange", update); window.removeEventListener("popstate", update); }; }, []); useEffect(() => { document.title = pathSet === "energy" ? "羚牛氢能-能源BI" : "羚牛氢能-资产BI"; }, [pathSet]); const modules = useMemo(() => { if (pathSet === "energy") { return [HYDROGEN_MODULE, ELECTRIC_MODULE, ETC_MODULE]; } const heatmapChildren = canAccessEnergy(user?.roles) ? [HYDROGEN_HEATMAP_MODULE, VEHICLE_HEATMAP_MODULE] : [VEHICLE_HEATMAP_MODULE]; const heatmapModule: ModuleConfig = { id: "heatmap", label: "热力图", icon: MapPinned, children: heatmapChildren, }; const result: ModuleConfig[] = [ASSETS_MODULE, MILEAGE_MODULE, heatmapModule]; if (canAccessScheduling(user?.roles)) result.push(SCHEDULING_MODULE); return result; }, [pathSet, user?.roles]); if (isLoading) { return (
LN BI ACCESS
正在进入羚牛氢能 BI
校验登录态、权限与本地开发环境配置
{[0, 1, 2, 3].map(item => )}
); } if (!isAuthenticated) { return ; } // 隐藏后端管理页:通过路径或 hash 直接访问,主导航不出现 if (routeKey === "ele/import") { return ( }> ); } if (routeKey === "admin/feedback") { return ( }> ); } // /energy 整组按能源权限控制 if (pathSet === "energy" && !canAccessEnergy(user?.roles)) { return ; } // key={pathSet} 让两套底栏切换时 Shell 内部 state 重置,避免残留旧 activeModule return ; } export default function App() { return ( ); }