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 (
加载中…
); } return ; } 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 (
navigate(pathForPage(page))} onToggleCollapse={() => setSidebarCollapsed((v) => !v)} onEndEditing={() => void endEditing(true)} onSelectScript={selectScript} editSessionRef={editSessionHandle} isSystemAdmin={user?.is_system_admin ?? false} />
); }