yearning
12 小时以前 3f96c9c6502243b06efd4bb34a189e72e3a5557c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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>
  );
}