成绩复盘与 PC 端上传优化:各科考试状态多选及树状统计。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { Button, Checkbox, Collapse, Select, Space, Table, Typography, message } from 'antd'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { examApi } from '../api/client'
|
||||
import type { Exam, ReviewStatus } from '../types'
|
||||
import { EXAM_TYPE_LABELS, REVIEW_STATUS_OPTIONS } from '../types'
|
||||
import ReviewTreeChart from './ReviewTreeChart'
|
||||
|
||||
interface Props {
|
||||
exams: Exam[]
|
||||
onRefresh: () => void
|
||||
}
|
||||
|
||||
export default function ExamReviewPanel({ exams, onRefresh }: Props) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [examId, setExamId] = useState<string>()
|
||||
const [statusMap, setStatusMap] = useState<Record<number, ReviewStatus[]>>({})
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const examOptions = useMemo(
|
||||
() =>
|
||||
exams.map((exam) => ({
|
||||
value: exam.id,
|
||||
label: `${exam.exam_date} · ${EXAM_TYPE_LABELS[exam.exam_type]}${exam.title ? ` · ${exam.title}` : ''}`,
|
||||
})),
|
||||
[exams],
|
||||
)
|
||||
|
||||
const selectedExam = exams.find((e) => e.id === examId)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
if (!examId && exams.length) {
|
||||
setExamId(exams[0].id)
|
||||
}
|
||||
}, [open, examId, exams])
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedExam) {
|
||||
setStatusMap({})
|
||||
return
|
||||
}
|
||||
const next: Record<number, ReviewStatus[]> = {}
|
||||
for (const score of selectedExam.scores) {
|
||||
next[score.subject_id] = [...(score.review_statuses || [])]
|
||||
}
|
||||
setStatusMap(next)
|
||||
}, [selectedExam])
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selectedExam) {
|
||||
message.warning('请选择考试')
|
||||
return
|
||||
}
|
||||
setSaving(true)
|
||||
try {
|
||||
await examApi.update(selectedExam.id, {
|
||||
scores: selectedExam.scores.map((score) => ({
|
||||
subject_id: score.subject_id,
|
||||
total_score: score.total_score,
|
||||
obtained_score: score.obtained_score,
|
||||
review_statuses: statusMap[score.subject_id] || [],
|
||||
})),
|
||||
})
|
||||
message.success('复盘已保存')
|
||||
onRefresh()
|
||||
} catch {
|
||||
message.error('保存失败')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
style={{ marginTop: 16 }}
|
||||
activeKey={open ? ['review'] : []}
|
||||
onChange={(keys) => setOpen(keys.includes('review'))}
|
||||
items={[
|
||||
{
|
||||
key: 'review',
|
||||
label: '复盘',
|
||||
children: (
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
{exams.length === 0 ? (
|
||||
<Typography.Text type="secondary">请先录入至少一次考试成绩</Typography.Text>
|
||||
) : (
|
||||
<>
|
||||
<Space wrap style={{ width: '100%' }}>
|
||||
<Select
|
||||
style={{ minWidth: 260, flex: 1 }}
|
||||
placeholder="选择考试"
|
||||
value={examId}
|
||||
onChange={setExamId}
|
||||
options={examOptions}
|
||||
/>
|
||||
<Button type="primary" loading={saving} onClick={handleSave}>
|
||||
保存复盘
|
||||
</Button>
|
||||
</Space>
|
||||
{selectedExam && (
|
||||
<Table
|
||||
size="small"
|
||||
pagination={false}
|
||||
rowKey="subject_id"
|
||||
dataSource={selectedExam.scores}
|
||||
scroll={{ x: 480 }}
|
||||
columns={[
|
||||
{
|
||||
title: '科目',
|
||||
dataIndex: 'subject_name',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '得分',
|
||||
width: 100,
|
||||
render: (_, row) => `${row.obtained_score}/${row.total_score}`,
|
||||
},
|
||||
{
|
||||
title: '考试状态(可多选)',
|
||||
render: (_, row) => (
|
||||
<Checkbox.Group
|
||||
options={REVIEW_STATUS_OPTIONS}
|
||||
value={statusMap[row.subject_id] || []}
|
||||
onChange={(values) =>
|
||||
setStatusMap((prev) => ({
|
||||
...prev,
|
||||
[row.subject_id]: values as ReviewStatus[],
|
||||
}))
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Typography.Text strong>复盘统计</Typography.Text>
|
||||
<ReviewTreeChart exams={exams} />
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user