199 lines
7.7 KiB
TypeScript
199 lines
7.7 KiB
TypeScript
import { useState, useEffect, useCallback } from "react";
|
|
import { Routes, Route, useNavigate, useLocation, Navigate } from "react-router-dom";
|
|
import { Layout, Menu, Button, Spin, Tag, Tooltip } from "antd";
|
|
import {
|
|
HomeOutlined, ApartmentOutlined, SettingOutlined, LogoutOutlined,
|
|
MenuFoldOutlined, MenuUnfoldOutlined,
|
|
ProjectOutlined, DatabaseOutlined, ApiOutlined,
|
|
} from "@ant-design/icons";
|
|
import AdminLogin from "./AdminLogin";
|
|
import AdminLoginV2 from "./AdminLoginV2";
|
|
import Brand from "./Brand";
|
|
import { getProjectContext, whoami, type KgProjectContext } from "./api";
|
|
import ProjectContextBar from "./ProjectContextBar";
|
|
import ProjectLanding from "./ProjectLanding";
|
|
import DataCenterPanel from "./panels/data-platform/DataCenterPanel";
|
|
|
|
// Plaza panels (STEP 02)
|
|
import PlazaOverview from "./panels/plaza/PlazaOverview";
|
|
import GraphCanvas from "./panels/plaza/GraphCanvas";
|
|
import ProjectUserExperiencePanel from "./panels/plaza/ProjectUserExperiencePanel";
|
|
|
|
// Schema viewer for schemas created from the graph center.
|
|
import OntologySchemaPanel from "./panels/modeling/OntologySchemaPanel";
|
|
|
|
// System panels
|
|
import UserManagement from "./panels/system/UserManagement";
|
|
import InterfaceCenterPanel from "./panels/system/InterfaceCenterPanel";
|
|
|
|
const { Sider, Content } = Layout;
|
|
const MENU_ITEMS = [
|
|
{ key: "/projects", icon: <ProjectOutlined />, label: "图谱中心" },
|
|
{ key: "/data", icon: <DatabaseOutlined />, label: "数据中心" },
|
|
{ key: "/plaza", icon: <HomeOutlined />, label: "知识广场", children: [
|
|
{ key: "/plaza/overview", label: "数据大屏" },
|
|
{ key: "/plaza/user", label: "业务问答" },
|
|
{ key: "/plaza/graph", label: "图谱浏览器" },
|
|
]},
|
|
{ key: "/modeling", icon: <ApartmentOutlined />, label: "本体建模", children: [
|
|
{ key: "/modeling/schema", label: "本体 Schema" },
|
|
]},
|
|
{ key: "/system", icon: <SettingOutlined />, label: "系统", children: [
|
|
{ key: "/system/interfaces", icon: <ApiOutlined />, label: "接口中心" },
|
|
{ key: "/system/users", label: "用户管理" },
|
|
]},
|
|
];
|
|
|
|
export default function App() {
|
|
const [authed, setAuthed] = useState(false);
|
|
const [checking, setChecking] = useState(true);
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const [roles, setRoles] = useState<string[]>([]);
|
|
const [projectContext, setProjectContextState] = useState<KgProjectContext>(getProjectContext());
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
|
|
const checkAuth = useCallback(async () => {
|
|
const token = localStorage.getItem("token");
|
|
if (!token) { setChecking(false); return; }
|
|
try {
|
|
const { data } = await whoami();
|
|
setRoles(Array.isArray(data?.roles) ? data.roles : []);
|
|
setAuthed(true);
|
|
} catch {
|
|
localStorage.removeItem("token");
|
|
setRoles([]);
|
|
}
|
|
setChecking(false);
|
|
}, []);
|
|
|
|
useEffect(() => { checkAuth(); }, [checkAuth]);
|
|
|
|
useEffect(() => {
|
|
const onChange = () => setProjectContextState(getProjectContext());
|
|
window.addEventListener("znkg-project-context", onChange);
|
|
return () => window.removeEventListener("znkg-project-context", onChange);
|
|
}, []);
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem("token");
|
|
setRoles([]);
|
|
setAuthed(false);
|
|
navigate("/admin/login");
|
|
};
|
|
|
|
if (checking) return <Spin size="large" style={{ display: "block", margin: "200px auto" }} />;
|
|
|
|
if (!authed) {
|
|
return (
|
|
<Routes>
|
|
<Route path="/admin/login" element={<AdminLogin onLogin={() => { setAuthed(true); void checkAuth(); navigate("/admin/projects"); }} />} />
|
|
<Route path="/admin/login-v2" element={<AdminLoginV2 onLogin={() => { setAuthed(true); void checkAuth(); navigate("/admin/projects"); }} />} />
|
|
<Route path="*" element={<Navigate to="/admin/login" />} />
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
const onMenuClick = ({ key }: { key: string }) => {
|
|
if (key === "/data") {
|
|
window.open("/admin/data", "_blank", "noopener,noreferrer");
|
|
return;
|
|
}
|
|
navigate(`/admin${key}`);
|
|
};
|
|
|
|
const selectedKey = location.pathname.replace("/admin", "") || "/projects";
|
|
const openKeys = [selectedKey.split("/").slice(0, 2).join("/")];
|
|
const showProjectContextBar = location.pathname === "/admin/projects";
|
|
const showDataCenterWorkbench = location.pathname === "/admin/data";
|
|
const isDataManager = roles.includes("admin");
|
|
const visibleMenuItems = isDataManager
|
|
? MENU_ITEMS
|
|
: MENU_ITEMS.filter((item) => item.key !== "/system");
|
|
|
|
return (
|
|
<Layout className="admin-layout" style={{ minHeight: "100vh" }}>
|
|
{!showDataCenterWorkbench && (
|
|
<Sider
|
|
theme="light"
|
|
width={220}
|
|
collapsedWidth={72}
|
|
collapsible
|
|
collapsed={collapsed}
|
|
trigger={null}
|
|
className="app-sider"
|
|
>
|
|
<div className="sider-inner">
|
|
<div className={`sider-brand${collapsed ? " collapsed" : ""}`}>
|
|
{!collapsed && <Brand size={22} />}
|
|
<Tooltip title={collapsed ? "展开侧栏" : "收起侧栏"} placement="right">
|
|
<Button
|
|
type="text"
|
|
className="collapse-btn"
|
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
|
|
<div className="sider-menu">
|
|
<Menu
|
|
mode="inline"
|
|
selectedKeys={[selectedKey]}
|
|
defaultOpenKeys={collapsed ? [] : openKeys}
|
|
items={visibleMenuItems}
|
|
onClick={onMenuClick}
|
|
style={{ borderRight: 0 }}
|
|
/>
|
|
</div>
|
|
|
|
<div className="sider-footer">
|
|
{!collapsed && (
|
|
<div className="ctx-row">
|
|
{projectContext.graphName && <Tag color="green">{projectContext.graphName}</Tag>}
|
|
</div>
|
|
)}
|
|
<div className={`footer-actions${collapsed ? " collapsed" : ""}`}>
|
|
<Tooltip title="退出登录" placement="right">
|
|
<Button type="text" icon={<LogoutOutlined />} onClick={handleLogout}>
|
|
{!collapsed && "退出"}
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Sider>
|
|
)}
|
|
<Layout className="app-main">
|
|
<Content className={`app-content${showDataCenterWorkbench ? " app-content-workbench" : ""}`}>
|
|
{showProjectContextBar && <ProjectContextBar />}
|
|
<Routes>
|
|
<Route path="/admin/projects" element={<ProjectLanding />} />
|
|
<Route path="/admin/data" element={<DataCenterPanel canManage={isDataManager} />} />
|
|
{/* Plaza */}
|
|
<Route path="/admin/plaza/overview" element={<PlazaOverview />} />
|
|
<Route path="/admin/plaza/user" element={<ProjectUserExperiencePanel />} />
|
|
<Route path="/admin/plaza/graph" element={<GraphCanvas />} />
|
|
|
|
{/* Schema viewer */}
|
|
<Route path="/admin/modeling/schema" element={<OntologySchemaPanel />} />
|
|
|
|
{/* System */}
|
|
<Route
|
|
path="/admin/system/interfaces"
|
|
element={isDataManager ? <InterfaceCenterPanel /> : <Navigate to="/admin/projects" />}
|
|
/>
|
|
<Route
|
|
path="/admin/system/users"
|
|
element={isDataManager ? <UserManagement /> : <Navigate to="/admin/projects" />}
|
|
/>
|
|
|
|
<Route path="/admin/login-v2" element={<AdminLoginV2 onLogin={() => navigate("/admin/projects")} />} />
|
|
<Route path="*" element={<Navigate to="/admin/projects" />} />
|
|
</Routes>
|
|
</Content>
|
|
</Layout>
|
|
</Layout>
|
|
);
|
|
}
|