在准备环境前提交次全部更改。

This commit is contained in:
Neo
2026-02-19 08:35:13 +08:00
parent ded6dfb9d8
commit 4eac07da47
1387 changed files with 6107191 additions and 33002 deletions

196
apps/admin-web/src/App.tsx Normal file
View File

@@ -0,0 +1,196 @@
/**
* 主布局与路由配置。
*
* - Ant Design LayoutSider + Content + Footer状态栏
* - react-router-dom6 个功能页面路由 + 登录页路由
* - 路由守卫:未登录重定向到登录页
*/
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,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import { useAuthStore } from "./store/authStore";
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";
const { Sider, Content, Footer } = Layout;
const { Text } = Typography;
/* ------------------------------------------------------------------ */
/* 侧边栏导航配置 */
/* ------------------------------------------------------------------ */
const NAV_ITEMS: MenuProps["items"] = [
{ key: "/", icon: <SettingOutlined />, label: "任务配置" },
{ key: "/task-manager", icon: <UnorderedListOutlined />, label: "任务管理" },
{ key: "/etl-status", icon: <DashboardOutlined />, label: "ETL 状态" },
{ key: "/db-viewer", icon: <DatabaseOutlined />, label: "数据库" },
{ key: "/log-viewer", icon: <FileTextOutlined />, label: "日志" },
{ key: "/env-config", icon: <ToolOutlined />, label: "环境配置" },
];
/* ------------------------------------------------------------------ */
/* 路由守卫 */
/* ------------------------------------------------------------------ */
const PrivateRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
return isAuthenticated ? <>{children}</> : <Navigate to="/login" replace />;
};
/* ------------------------------------------------------------------ */
/* 主布局 */
/* ------------------------------------------------------------------ */
const AppLayout: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const logout = useAuthStore((s) => s.logout);
const [runningTask, setRunningTask] = useState<QueuedTask | null>(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 (
<Layout style={{ minHeight: "100vh" }}>
<Sider
collapsible
style={{ display: "flex", flexDirection: "column" }}
>
<div
style={{
height: 48,
margin: "12px 16px",
color: "#fff",
fontWeight: 700,
fontSize: 18,
textAlign: "center",
lineHeight: "48px",
whiteSpace: "nowrap",
overflow: "hidden",
letterSpacing: 1,
}}
>
NeoZQYY
</div>
<Menu
theme="dark"
mode="inline"
selectedKeys={[location.pathname]}
items={NAV_ITEMS}
onClick={onMenuClick}
/>
<div style={{ flex: 1 }} />
<div style={{ padding: "12px 16px" }}>
<Tooltip title="退出登录">
<Button
type="text" icon={<LogoutOutlined />}
onClick={handleLogout}
style={{ color: "rgba(255,255,255,0.65)", width: "100%" }}
>
退
</Button>
</Tooltip>
</div>
</Sider>
<Layout>
<Content style={{ margin: 16, minHeight: 280 }}>
<Routes>
<Route path="/" element={<TaskConfig />} />
<Route path="/task-manager" element={<TaskManager />} />
<Route path="/env-config" element={<EnvConfig />} />
<Route path="/db-viewer" element={<DBViewer />} />
<Route path="/etl-status" element={<ETLStatus />} />
<Route path="/log-viewer" element={<LogViewer />} />
</Routes>
</Content>
<Footer
style={{
textAlign: "center",
padding: "6px 16px",
background: "#fafafa",
borderTop: "1px solid #f0f0f0",
}}
>
{runningTask ? (
<Space size={8}>
<Spin size="small" />
<Text></Text>
<Tag color="processing">{runningTask.config.pipeline}</Tag>
<Text type="secondary" style={{ fontSize: 12 }}>
{runningTask.config.tasks.slice(0, 3).join(", ")}
{runningTask.config.tasks.length > 3 && ` +${runningTask.config.tasks.length - 3}`}
</Text>
</Space>
) : (
<Text type="secondary" style={{ fontSize: 12 }}></Text>
)}
</Footer>
</Layout>
</Layout>
);
};
/* ------------------------------------------------------------------ */
/* 根组件 */
/* ------------------------------------------------------------------ */
const App: React.FC = () => {
const hydrate = useAuthStore((s) => s.hydrate);
useEffect(() => { hydrate(); }, [hydrate]);
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/*"
element={
<PrivateRoute>
<AppLayout />
</PrivateRoute>
}
/>
</Routes>
);
};
export default App;