/** * 主布局与路由配置。 * * - Ant Design Layout:Sider + Content + Footer(状态栏) * - react-router-dom:6 个功能页面路由 + 登录页路由 * - 路由守卫:未登录重定向到登录页 */ import React, { useEffect, useState, useCallback } from "react"; import { Routes, Route, Navigate, useNavigate, useLocation } from "react-router-dom"; import { Layout, Menu, Spin, Space, Typography, Tag, Button, Tooltip } from "antd"; import { SettingOutlined, UnorderedListOutlined, ToolOutlined, DatabaseOutlined, DashboardOutlined, FileTextOutlined, LogoutOutlined, DesktopOutlined, } from "@ant-design/icons"; import type { MenuProps } from "antd"; import { useAuthStore } from "./store/authStore"; import { useBusinessDayStore } from "./store/businessDayStore"; import { fetchQueue } from "./api/execution"; import type { QueuedTask } from "./types"; import Login from "./pages/Login"; import TaskConfig from "./pages/TaskConfig"; import TaskManager from "./pages/TaskManager"; import EnvConfig from "./pages/EnvConfig"; import DBViewer from "./pages/DBViewer"; import ETLStatus from "./pages/ETLStatus"; import LogViewer from "./pages/LogViewer"; import OpsPanel from "./pages/OpsPanel"; const { Sider, Content, Footer } = Layout; const { Text } = Typography; /* ------------------------------------------------------------------ */ /* 侧边栏导航配置 */ /* ------------------------------------------------------------------ */ const NAV_ITEMS: MenuProps["items"] = [ { key: "/", icon: , label: "任务配置" }, { key: "/task-manager", icon: , label: "任务管理" }, { key: "/etl-status", icon: , label: "ETL 状态" }, { key: "/db-viewer", icon: , label: "数据库" }, { key: "/log-viewer", icon: , label: "日志" }, { key: "/env-config", icon: , label: "环境配置" }, { key: "/ops-panel", icon: , label: "运维面板" }, ]; /* ------------------------------------------------------------------ */ /* 路由守卫 */ /* ------------------------------------------------------------------ */ const PrivateRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => { const isAuthenticated = useAuthStore((s) => s.isAuthenticated); return isAuthenticated ? <>{children}> : ; }; /* ------------------------------------------------------------------ */ /* 主布局 */ /* ------------------------------------------------------------------ */ const AppLayout: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const logout = useAuthStore((s) => s.logout); const [runningTask, setRunningTask] = useState(null); const pollQueue = useCallback(async () => { try { const queue = await fetchQueue(); const running = queue.find((t) => t.status === "running") ?? null; setRunningTask(running); } catch { // 网络异常时不更新状态 } }, []); useEffect(() => { pollQueue(); const timer = setInterval(pollQueue, 5_000); return () => clearInterval(timer); }, [pollQueue]); const onMenuClick: MenuProps["onClick"] = ({ key }) => { navigate(key); }; const handleLogout = () => { logout(); navigate("/login", { replace: true }); }; return ( NeoZQYY } onClick={handleLogout} style={{ color: "rgba(255,255,255,0.65)", width: "100%" }} > 退出 } /> } /> } /> } /> } /> } /> } /> ); }; /* ------------------------------------------------------------------ */ /* 根组件 */ /* ------------------------------------------------------------------ */ const App: React.FC = () => { const hydrate = useAuthStore((s) => s.hydrate); const initBusinessDay = useBusinessDayStore((s) => s.init); const [hydrated, setHydrated] = useState(false); useEffect(() => { hydrate(); setHydrated(true); // 启动时请求一次营业日配置,降级策略在 store 内部处理 initBusinessDay(); }, [hydrate, initBusinessDay]); /* hydrate 完成前不渲染路由,避免 PrivateRoute 误判跳转到 /login */ if (!hydrated) return ; return ( } /> } /> ); }; export default App;