import { Layout, Menu, Button, Grid, Drawer } from "antd";
|
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
import { useMemo, useState } from "react";
|
import { MenuOutlined } from "@ant-design/icons";
|
import type { UserMe } from "../App";
|
import { clearToken } from "../api";
|
import SystemBrand from "../components/SystemBrand";
|
import ChangePasswordModal from "../components/ChangePasswordModal";
|
import AppBreadcrumb from "./AppBreadcrumb";
|
import "./MainLayout.css";
|
|
const { Header, Sider, Content } = Layout;
|
const { useBreakpoint } = Grid;
|
|
export default function MainLayout({
|
user,
|
onLogout,
|
}: {
|
user: UserMe;
|
onLogout: () => void;
|
}) {
|
const nav = useNavigate();
|
const loc = useLocation();
|
const screens = useBreakpoint();
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [pwdOpen, setPwdOpen] = useState(false);
|
|
const items = useMemo(() => {
|
const m = [{ key: "/scores", label: "成绩查询" }];
|
if (user.role === "admin") {
|
m.push({ key: "/admin/import", label: "数据导入" });
|
m.push({ key: "/admin/graders", label: "考评员管理" });
|
m.push({ key: "/admin/settings", label: "系统设置" });
|
}
|
return m;
|
}, [user.role]);
|
|
const menu = (
|
<Menu
|
className="app-sider-menu"
|
mode="inline"
|
selectedKeys={[loc.pathname.startsWith("/attempts") ? "/scores" : loc.pathname]}
|
items={items}
|
onClick={({ key }) => {
|
nav(key);
|
setDrawerOpen(false);
|
}}
|
/>
|
);
|
|
const isMobile = !screens.md;
|
|
return (
|
<Layout style={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}>
|
<Header className="app-header">
|
{isMobile && (
|
<Button type="text" icon={<MenuOutlined />} onClick={() => setDrawerOpen(true)} style={{ marginRight: 8 }} />
|
)}
|
<div style={{ flex: 1, minWidth: 0 }}>
|
<SystemBrand size="sm" />
|
</div>
|
<span style={{ marginRight: 12 }}>{user.display_name || user.username}</span>
|
<Button type="link" onClick={() => setPwdOpen(true)}>修改密码</Button>
|
<Button
|
type="link"
|
onClick={() => {
|
clearToken();
|
onLogout();
|
nav("/login");
|
}}
|
>
|
退出
|
</Button>
|
</Header>
|
<ChangePasswordModal open={pwdOpen} onClose={() => setPwdOpen(false)} />
|
<Layout className="app-shell-body" style={{ flex: 1, minHeight: 0 }}>
|
{!isMobile && (
|
<Sider width={200} theme="light" className="app-sider">
|
{menu}
|
</Sider>
|
)}
|
<Drawer open={drawerOpen} onClose={() => setDrawerOpen(false)} placement="left" width={220}>
|
{menu}
|
</Drawer>
|
<Content
|
className={[
|
"main-layout__content",
|
loc.pathname.startsWith("/attempts/") ? "main-layout__content--attempt" : "",
|
loc.pathname === "/scores" ? "main-layout__content--scores" : "",
|
]
|
.filter(Boolean)
|
.join(" ")}
|
style={{
|
padding: 16,
|
background: loc.pathname.startsWith("/attempts/") ? "#fff" : "#f5f5f5",
|
}}
|
>
|
<AppBreadcrumb />
|
<div className="main-layout__content-inner">
|
<Outlet />
|
</div>
|
</Content>
|
</Layout>
|
</Layout>
|
);
|
}
|