Files
secondary-school-grade-archive/frontend/src/pages/StudentsPage.tsx
T
dekun e329d3398a Initial commit: secondary school grade archive system.
Add FastAPI/React app with Docker deployment, Ubuntu one-click install, and docs for junior/senior high score tracking and mistake bank.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 11:18:58 +08:00

147 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { LogoutOutlined, PlusOutlined, UserOutlined } from '@ant-design/icons'
import { Button, Card, Col, Form, Input, Modal, Row, Select, Space, Spin, Tag, Typography, message } from 'antd'
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { studentApi } from '../api/client'
import { formatStudentMeta, GRADE_OPTIONS, SCHOOL_LEVEL_LABELS } from '../constants/school'
import { useAuth } from '../context/AuthContext'
import type { SchoolLevel, Student } from '../types'
export default function StudentsPage() {
const { user, logout } = useAuth()
const [students, setStudents] = useState<Student[]>([])
const [loading, setLoading] = useState(true)
const [modalOpen, setModalOpen] = useState(false)
const [form] = Form.useForm()
const schoolLevel = Form.useWatch('school_level', form) as SchoolLevel | undefined
const load = async () => {
setLoading(true)
try {
const { data } = await studentApi.list()
setStudents(data)
} finally {
setLoading(false)
}
}
useEffect(() => {
load()
}, [])
const openCreate = () => {
form.setFieldsValue({ school_level: 'junior_high', grade: undefined })
setModalOpen(true)
}
const handleCreate = async () => {
const values = await form.validateFields()
await studentApi.create(values)
message.success('学生已添加')
setModalOpen(false)
form.resetFields()
load()
}
return (
<div style={{ maxWidth: 960, margin: '0 auto', padding: '16px 16px 32px' }}>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 24,
flexWrap: 'wrap',
gap: 12,
}}
>
<div>
<Typography.Title level={3} style={{ margin: 0 }}>
</Typography.Title>
<Typography.Text type="secondary">{user?.username}</Typography.Text>
</div>
<Space wrap>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button>
<Button icon={<LogoutOutlined />} onClick={logout}>
退
</Button>
</Space>
</div>
<Spin spinning={loading}>
<Row gutter={[16, 16]}>
{students.map((s) => (
<Col xs={24} sm={12} md={8} key={s.id}>
<Link to={`/students/${s.id}`} style={{ textDecoration: 'none' }}>
<Card hoverable>
<Space align="start">
<UserOutlined style={{ fontSize: 24, color: '#1677ff' }} />
<div>
<Space size={4}>
<Typography.Text strong>{s.name}</Typography.Text>
<Tag color={s.school_level === 'senior_high' ? 'purple' : 'blue'}>
{SCHOOL_LEVEL_LABELS[s.school_level]}
</Tag>
</Space>
<br />
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{formatStudentMeta(s)}
</Typography.Text>
</div>
</Space>
</Card>
</Link>
</Col>
))}
{!loading && students.length === 0 && (
<Col span={24}>
<Card>
<Typography.Text type="secondary"></Typography.Text>
</Card>
</Col>
)}
</Row>
</Spin>
<Modal
title="添加学生"
open={modalOpen}
onCancel={() => setModalOpen(false)}
onOk={handleCreate}
destroyOnHidden
>
<Form form={form} layout="vertical" initialValues={{ school_level: 'junior_high' }}>
<Form.Item name="name" label="姓名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="school_level" label="学段" rules={[{ required: true }]}>
<Select
options={Object.entries(SCHOOL_LEVEL_LABELS).map(([value, label]) => ({
value,
label,
}))}
onChange={() => form.setFieldValue('grade', undefined)}
/>
</Form.Item>
<Form.Item name="grade" label="年级">
<Select
allowClear
placeholder={schoolLevel === 'senior_high' ? '如:高一' : '如:初二'}
options={(GRADE_OPTIONS[schoolLevel || 'junior_high'] || []).map((g) => ({
value: g,
label: g,
}))}
/>
</Form.Item>
<Form.Item name="class_name" label="班级">
<Input placeholder="如:3班" />
</Form.Item>
</Form>
</Modal>
</div>
)
}