Add exam score import and management workflows
This commit is contained in:
1 parent
71ee21b063
commit
24d2a96144
26 files changed
+3865
No files matched your search
@@ -0,0 +1,89 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
BellOutlined,
|
||||
IdcardOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { Avatar, Button, Layout, Menu, Tag } from 'antd'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { branding } from '../config/branding'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import BrandMark from '../components/BrandMark'
|
||||
|
||||
const { Header, Sider, Content } = Layout
|
||||
|
||||
export default function AppShell() {
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const user = useAuthStore((state) => state.user)
|
||||
const logout = useAuthStore((state) => state.logout)
|
||||
const refreshUser = useAuthStore((state) => state.refreshUser)
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
useEffect(() => { void refreshUser().catch(() => undefined) }, [refreshUser])
|
||||
|
||||
const items = useMemo(() => [
|
||||
{ key: '/dashboard', icon: <AppstoreOutlined />, label: '教务总览' },
|
||||
{ key: '/account', icon: <IdcardOutlined />, label: '个人账户' },
|
||||
...(user?.roles.includes('SuperAdmin')
|
||||
? [{ key: '/users', icon: <SafetyCertificateOutlined />, label: '用户与权限' }]
|
||||
: []),
|
||||
], [user?.roles])
|
||||
|
||||
const pageTitle = items.find((item) => location.pathname.startsWith(item.key))?.label ?? '迁移预览'
|
||||
|
||||
function signOut() {
|
||||
logout()
|
||||
navigate('/login', { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout className="app-shell">
|
||||
<Sider
|
||||
className="app-shell__sider"
|
||||
width={238}
|
||||
collapsedWidth={72}
|
||||
collapsed={collapsed}
|
||||
breakpoint="lg"
|
||||
onBreakpoint={setCollapsed}
|
||||
>
|
||||
<div className="shell-brand">
|
||||
<BrandMark />
|
||||
{!collapsed && <div><strong>{branding.shortName}</strong><small>REACT MIGRATION</small></div>}
|
||||
</div>
|
||||
<Menu
|
||||
className="shell-menu"
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
selectedKeys={[items.find((item) => location.pathname.startsWith(item.key))?.key ?? '/dashboard']}
|
||||
items={items}
|
||||
onClick={({ key }) => navigate(key)}
|
||||
/>
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Header className="shell-header">
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 14, minWidth: 0 }}>
|
||||
<Button
|
||||
type="text"
|
||||
aria-label={collapsed ? '展开导航' : '收起导航'}
|
||||
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
onClick={() => setCollapsed((value) => !value)}
|
||||
/>
|
||||
<div className="shell-context"><span>当前工作区</span><strong>{pageTitle}</strong></div>
|
||||
</div>
|
||||
<div className="shell-user">
|
||||
<Button type="text" aria-label="通知中心" icon={<BellOutlined />} />
|
||||
<Tag color="geekblue">预览</Tag>
|
||||
<div className="shell-user__copy"><strong>{user?.displayName ?? '用户'}</strong><small>{user?.roles.join(' · ') || '正在读取权限'}</small></div>
|
||||
<Avatar style={{ background: '#54cfbd', color: '#163f43' }}>{user?.displayName?.slice(0, 1) ?? '明'}</Avatar>
|
||||
<Button type="text" onClick={signOut}>退出</Button>
|
||||
</div>
|
||||
</Header>
|
||||
<Content className="shell-content"><Outlet /></Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user