diff --git a/apps/admin/src/pages/AiConfig/index.tsx b/apps/admin/src/pages/AiConfig/index.tsx index 809b3c1..83c785d 100644 --- a/apps/admin/src/pages/AiConfig/index.tsx +++ b/apps/admin/src/pages/AiConfig/index.tsx @@ -21,7 +21,6 @@ import { ApiOutlined, CheckCircleOutlined, CloseCircleOutlined, - KeyOutlined, WarningOutlined, ReloadOutlined, CloudServerOutlined, diff --git a/apps/admin/src/pages/Exams/detail.tsx b/apps/admin/src/pages/Exams/detail.tsx index 17f6dc7..f3539f5 100644 --- a/apps/admin/src/pages/Exams/detail.tsx +++ b/apps/admin/src/pages/Exams/detail.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Button, Card, Descriptions, Empty, Space, Spin, Table, Tooltip } from 'antd'; +import { Alert, Button, Card, Descriptions, Empty, Space, Spin, Table, Tag, Tooltip } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { ArrowLeftOutlined, EyeOutlined } from '@ant-design/icons'; import { useNavigate, useParams } from 'react-router-dom'; @@ -67,11 +67,14 @@ const ExamDetailPage: React.FC = () => { useEffect(() => { void load(); }, [load]); - const saveScore = async (row: ScoreRow, value: number | undefined) => { - await api.put(`/exams/${id}/scores/${row.id}`, { score: value ?? null }); - message.success('成绩已保存'); - await load(); - }; + const saveScore = useCallback( + async (row: ScoreRow, value: number | undefined) => { + await api.put(`/exams/${id}/scores/${row.id}`, { score: value ?? null }); + message.success('成绩已保存'); + await load(); + }, + [id, load], + ); const columns = useMemo>(() => { if (!detail) return []; @@ -93,16 +96,20 @@ const ExamDetailPage: React.FC = () => { dataIndex: 'score', width: 100, render: (value: number | null, row: ScoreRow) => ( - - value={value ?? undefined} - editor="money" - min={0} - max={999.99} - permission="exam:view" - onSave={(next) => saveScore(row, next)} - > - {value ?? '-'} - + detail.status === 'archived' ? ( + value ?? '-' + ) : ( + + value={value ?? undefined} + editor="money" + min={0} + max={999.99} + permission="exam:view" + onSave={(next) => saveScore(row, next)} + > + {value ?? '-'} + + ) ), }, { @@ -120,7 +127,7 @@ const ExamDetailPage: React.FC = () => { { title: '考试日期', width: 110, render: () => detail.examDate }, { title: '关联报读(班级名)', width: 180, render: () => detail.className }, ]; - }, [detail]); + }, [detail, saveScore]); if (loading && !detail) return ( @@ -139,8 +146,12 @@ const ExamDetailPage: React.FC = () => { 返回

{detail.examName}

+ {detail.status === 'archived' ? 已归档 : null} + {detail.status === 'archived' ? ( + + ) : null} {detail.examType} diff --git a/apps/admin/src/pages/Exams/index.tsx b/apps/admin/src/pages/Exams/index.tsx index 6d3ea7a..e487235 100644 --- a/apps/admin/src/pages/Exams/index.tsx +++ b/apps/admin/src/pages/Exams/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Button, Card, Col, Empty, Form, Input, Progress, Row, Select, Space, Tag } from 'antd'; -import { CalendarOutlined, PlusOutlined, SearchOutlined, TeamOutlined } from '@ant-design/icons'; +import { Button, Card, Col, Empty, Form, Input, Popconfirm, Progress, Row, Select, Space, Switch, Tag } from 'antd'; +import { CalendarOutlined, InboxOutlined, PlusOutlined, SearchOutlined, TeamOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import { useNavigate } from 'react-router-dom'; import api from '../../api'; @@ -21,6 +21,7 @@ const ExamsPage: React.FC = () => { const [keyword, setKeyword] = useState(''); const [examType, setExamType] = useState(); const [classId, setClassId] = useState(); + const [showArchived, setShowArchived] = useState(false); const loadClasses = useCallback(async () => { const result = await api.get('/classes'); @@ -34,6 +35,7 @@ const ExamsPage: React.FC = () => { if (keyword.trim()) params.set('keyword', keyword.trim()); if (examType) params.set('examType', examType); if (classId) params.set('classId', String(classId)); + params.set('isArchived', String(showArchived)); const result = await api.get(`/exams?${params.toString()}`); setData(result ?? []); } catch (error) { @@ -41,7 +43,7 @@ const ExamsPage: React.FC = () => { } finally { setLoading(false); } - }, [classId, examType, keyword]); + }, [classId, examType, keyword, showArchived]); useEffect(() => { void loadClasses().catch((error: { message?: string }) => message.error(error?.message || '加载班级失败')); @@ -80,6 +82,16 @@ const ExamsPage: React.FC = () => { } }; + const changeArchiveStatus = async (exam: ExamItem, archive: boolean) => { + try { + await api.put(`/exams/${exam.id}/${archive ? 'archive' : 'restore'}`); + message.success(archive ? '考试已归档' : '考试已恢复'); + await loadExams(); + } catch (error) { + message.error((error as { message?: string })?.message || '操作失败'); + } + }; + return (
@@ -89,6 +101,11 @@ const ExamsPage: React.FC = () => {