103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import { lazy, Suspense, useEffect, useMemo, useState } from "react";
|
|
import { Shell } from "./components/Shell";
|
|
import AuthProvider from "./auth/AuthProvider";
|
|
import { useAuth } from "./auth/useAuth";
|
|
import UnauthorizedPage from "./auth/UnauthorizedPage";
|
|
import { canAccessEnergy } from "./shared/auth/roles";
|
|
import { LoadingState, SkeletonBlock, SurfaceCard } from "./components/ui/surface";
|
|
import { buildModules } from "./app/modules";
|
|
import {
|
|
normalizeBrowserPath,
|
|
readBrowserRoute,
|
|
} from "./app/routing";
|
|
|
|
const EleImportPage = lazy(() => import("./modules/ele/EleImportPage"));
|
|
const FeedbackAdminPage = lazy(() => import("./modules/admin/FeedbackAdminPage"));
|
|
normalizeBrowserPath();
|
|
|
|
function AuthGate() {
|
|
const { isLoading, isAuthenticated, error, user } = useAuth();
|
|
const [route, setRoute] = useState(readBrowserRoute);
|
|
const { routeKey, pathSet } = route;
|
|
|
|
// 监听 hashchange / popstate,让 a href="#/..." 跳转与浏览器前进后退能即时生效
|
|
useEffect(() => {
|
|
const update = () => {
|
|
setRoute(readBrowserRoute());
|
|
};
|
|
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(
|
|
() => buildModules(pathSet, user?.roles),
|
|
[pathSet, user?.roles],
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen bg-[var(--app-bg)] p-4 text-slate-800 md:p-8">
|
|
<div className="mx-auto flex max-w-5xl flex-col gap-4">
|
|
<div className="rounded-2xl border border-white/70 bg-white/86 p-5 shadow-[0_18px_60px_rgba(15,23,42,0.08)] backdrop-blur-xl">
|
|
<div className="mb-3 text-[11px] font-black text-blue-600">LN BI ACCESS</div>
|
|
<div className="text-xl font-black text-slate-950">正在进入羚牛氢能 BI</div>
|
|
<div className="mt-2 text-xs font-bold text-slate-400">校验登录态、权限与本地开发环境配置</div>
|
|
</div>
|
|
<SurfaceCard className="p-4">
|
|
<div className="grid gap-3 md:grid-cols-4">
|
|
{[0, 1, 2, 3].map(item => <SkeletonBlock key={item} className="h-24" />)}
|
|
</div>
|
|
<div className="mt-4">
|
|
<LoadingState label="正在验证身份" />
|
|
</div>
|
|
</SurfaceCard>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return <UnauthorizedPage message={error || undefined} />;
|
|
}
|
|
|
|
// 隐藏后端管理页:通过路径或 hash 直接访问,主导航不出现
|
|
if (routeKey === "ele/import") {
|
|
return (
|
|
<Suspense fallback={<LoadingState label="正在加载导入工作台" />}>
|
|
<EleImportPage />
|
|
</Suspense>
|
|
);
|
|
}
|
|
if (routeKey === "admin/feedback") {
|
|
return (
|
|
<Suspense fallback={<LoadingState label="正在加载反馈后台" />}>
|
|
<FeedbackAdminPage />
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
// /energy 整组按能源权限控制
|
|
if (pathSet === "energy" && !canAccessEnergy(user?.roles)) {
|
|
return <UnauthorizedPage message="无能源管理模块访问权限" />;
|
|
}
|
|
|
|
// key={pathSet} 让两套底栏切换时 Shell 内部 state 重置,避免残留旧 activeModule
|
|
return <Shell key={pathSet} modules={modules} />;
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<AuthProvider>
|
|
<AuthGate />
|
|
</AuthProvider>
|
|
);
|
|
}
|