140 lines
4.4 KiB
TypeScript
140 lines
4.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Outlet, useLocation, useNavigate } from "react-router";
|
|
|
|
import type { Route } from "./+types/platform";
|
|
import { Sidebar } from "../components/common/Sidebar";
|
|
import { Toast } from "../components/common/Toast";
|
|
import { Topbar } from "../components/common/Topbar";
|
|
import { useApi, useAuth } from "../context/AuthContext";
|
|
import { useEditSessionLifecycle } from "../features/platform/hooks/useEditSessionLifecycle";
|
|
import {
|
|
bindScriptWorkspaceApi,
|
|
editSessionHandle,
|
|
useScriptWorkspaceStore,
|
|
} from "../features/platform/state/scriptWorkspaceStore";
|
|
import { useUiStore } from "../features/platform/state/uiStore";
|
|
import { bindAdminApi } from "../features/admin/state/adminStore";
|
|
import { bindSchedulesApi } from "../features/schedules/state/schedulesStore";
|
|
|
|
import "../styles/platform.css";
|
|
|
|
type ActivePage = "home" | "scripts" | "schedules" | "system";
|
|
|
|
function pageFromPath(pathname: string): ActivePage {
|
|
const page = pathname.replace(/^\/+|\/+$/g, "");
|
|
return ["scripts", "schedules", "system"].includes(page)
|
|
? (page as ActivePage)
|
|
: "home";
|
|
}
|
|
|
|
function pathForPage(page: ActivePage): string {
|
|
return page === "home" ? "/workbench" : `/${page}`;
|
|
}
|
|
|
|
export function meta({}: Route.MetaArgs) {
|
|
return [
|
|
{ title: "模型实验开发平台" },
|
|
{ name: "description", content: "模型实验、脚本版本与 DAG 调度平台" },
|
|
];
|
|
}
|
|
|
|
export default function PlatformLayout() {
|
|
const { currentWorkspace } = useAuth();
|
|
if (!currentWorkspace) {
|
|
return (
|
|
<div
|
|
className="app-shell"
|
|
style={{ display: "flex", alignItems: "center", justifyContent: "center" }}
|
|
>
|
|
<div style={{ textAlign: "center" }}>
|
|
<span style={{ fontSize: 18 }}>加载中…</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
return <AuthenticatedLayout />;
|
|
}
|
|
|
|
function AuthenticatedLayout() {
|
|
const api = useApi();
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const activePage = pageFromPath(location.pathname);
|
|
const auth = useAuth();
|
|
const {
|
|
user,
|
|
workspaces,
|
|
currentWorkspace,
|
|
setCurrentWorkspace,
|
|
logout,
|
|
} = auth;
|
|
|
|
const apiOnline = useScriptWorkspaceStore((s) => s.apiOnline);
|
|
const endEditing = useScriptWorkspaceStore((s) => s.endEditing);
|
|
const selectScript = useScriptWorkspaceStore((s) => s.selectScript);
|
|
|
|
const toast = useUiStore((s) => s.toast);
|
|
const dismissToast = useUiStore((s) => s.dismissToast);
|
|
const workspaceMenuOpen = useUiStore((s) => s.workspaceMenuOpen);
|
|
const setWorkspaceMenuOpen = useUiStore((s) => s.setWorkspaceMenuOpen);
|
|
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
|
|
// 绑定 api 到 script workspace store。
|
|
// 必须放在 render body(同步),不能用 useEffect([api]) + cleanup —
|
|
// 后者会在 deps 变化时先 cleanup(null),再让 ScriptsPage 的 useEffect 跑,
|
|
// 此时 _api 为 null,load() 抛 "script workspace API 未绑定"。
|
|
bindScriptWorkspaceApi(api);
|
|
bindSchedulesApi(api);
|
|
bindAdminApi(api);
|
|
useEffect(() => {
|
|
return () => {
|
|
bindScriptWorkspaceApi(null);
|
|
bindSchedulesApi(null);
|
|
bindAdminApi(null);
|
|
};
|
|
}, []);
|
|
|
|
// 心跳 / cleanup / 切页结束编辑 / 卸载前释放
|
|
useEditSessionLifecycle({ activePage });
|
|
|
|
// toast 自动消失
|
|
useEffect(() => {
|
|
if (!toast) return;
|
|
const timer = window.setTimeout(dismissToast, 3200);
|
|
return () => window.clearTimeout(timer);
|
|
}, [toast, dismissToast]);
|
|
|
|
return (
|
|
<div className="app-shell">
|
|
<Sidebar
|
|
activePage={activePage}
|
|
collapsed={sidebarCollapsed}
|
|
onNavigate={(page) => navigate(pathForPage(page))}
|
|
onToggleCollapse={() => setSidebarCollapsed((v) => !v)}
|
|
onEndEditing={() => void endEditing(true)}
|
|
onSelectScript={selectScript}
|
|
editSessionRef={editSessionHandle}
|
|
isSystemAdmin={user?.is_system_admin ?? false}
|
|
/>
|
|
|
|
<main className="main-area">
|
|
<Topbar
|
|
activePage={activePage}
|
|
apiOnline={apiOnline}
|
|
user={user}
|
|
currentWorkspace={currentWorkspace!}
|
|
workspaces={workspaces}
|
|
workspaceMenuOpen={workspaceMenuOpen}
|
|
onSetWorkspaceMenuOpen={setWorkspaceMenuOpen}
|
|
onSetCurrentWorkspace={setCurrentWorkspace}
|
|
onLogout={logout}
|
|
/>
|
|
|
|
<Outlet />
|
|
</main>
|
|
|
|
<Toast toast={toast} />
|
|
</div>
|
|
);
|
|
} |