第一版提交,答题功能OK,题库管理待完善

This commit is contained in:
2025-12-18 19:07:21 +08:00
parent e5600535be
commit ba252b2f56
93 changed files with 20431 additions and 1 deletions

225
src/pages/UserTaskPage.tsx Normal file
View File

@@ -0,0 +1,225 @@
import React, { useState, useEffect } from 'react';
import { Card, Table, Tag, Button, Space, Spin, message, Typography } from 'antd';
import { ClockCircleOutlined, BookOutlined, CalendarOutlined, CheckCircleOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { request } from '../utils/request';
import { useUserStore } from '../stores/userStore';
const { Title, Text } = Typography;
interface ExamTask {
id: string;
name: string;
subjectId: string;
subjectName: string;
startAt: string;
endAt: string;
totalScore: number;
timeLimitMinutes: number;
completed?: boolean;
score?: number;
}
export const UserTaskPage: React.FC = () => {
const [tasks, setTasks] = useState<ExamTask[]>([]);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const { user } = useUserStore();
useEffect(() => {
if (user) {
fetchUserTasks();
}
}, [user]);
const fetchUserTasks = async () => {
try {
setLoading(true);
const response = await request.get(`/api/exam-tasks/user/${user?.id}`);
if (response.data.success) {
setTasks(response.data.data);
}
} catch (error) {
message.error('获取考试任务失败');
} finally {
setLoading(false);
}
};
const startTask = (task: ExamTask) => {
const now = new Date();
const startAt = new Date(task.startAt);
const endAt = new Date(task.endAt);
if (now < startAt) {
message.warning('考试任务尚未开始');
return;
}
if (now > endAt) {
message.warning('考试任务已结束');
return;
}
// 跳转到科目选择页面带上任务ID
navigate('/subjects', { state: { selectedTask: task.id } });
};
const getStatusColor = (task: ExamTask) => {
const now = new Date();
const startAt = new Date(task.startAt);
const endAt = new Date(task.endAt);
if (now < startAt) return 'blue';
if (now > endAt) return 'red';
return 'green';
};
const getStatusText = (task: ExamTask) => {
const now = new Date();
const startAt = new Date(task.startAt);
const endAt = new Date(task.endAt);
if (now < startAt) return '未开始';
if (now > endAt) return '已结束';
return '进行中';
};
const columns = [
{
title: '任务名称',
dataIndex: 'name',
key: 'name',
render: (text: string) => <Text strong>{text}</Text>
},
{
title: '考试科目',
dataIndex: 'subjectName',
key: 'subjectName',
render: (text: string) => (
<Space>
<BookOutlined className="text-blue-600" />
<Text>{text}</Text>
</Space>
)
},
{
title: '总分',
dataIndex: 'totalScore',
key: 'totalScore',
render: (score: number) => <Text strong>{score}</Text>
},
{
title: '时长',
dataIndex: 'timeLimitMinutes',
key: 'timeLimitMinutes',
render: (minutes: number) => (
<Space>
<ClockCircleOutlined className="text-gray-600" />
<Text>{minutes}</Text>
</Space>
)
},
{
title: '时间范围',
key: 'timeRange',
render: (record: ExamTask) => (
<Space direction="vertical" size={0}>
<Space>
<CalendarOutlined className="text-gray-600" />
<Text type="secondary" style={{ fontSize: '12px' }}>
{new Date(record.startAt).toLocaleDateString()}
</Text>
</Space>
<Space>
<CalendarOutlined className="text-gray-600" />
<Text type="secondary" style={{ fontSize: '12px' }}>
{new Date(record.endAt).toLocaleDateString()}
</Text>
</Space>
</Space>
)
},
{
title: '状态',
key: 'status',
render: (record: ExamTask) => (
<Tag color={getStatusColor(record)}>
{getStatusText(record)}
</Tag>
)
},
{
title: '操作',
key: 'action',
render: (record: ExamTask) => {
const now = new Date();
const startAt = new Date(record.startAt);
const endAt = new Date(record.endAt);
const canStart = now >= startAt && now <= endAt;
return (
<Space>
<Button
type="primary"
size="small"
onClick={() => startTask(record)}
disabled={!canStart}
icon={<CheckCircleOutlined />}
>
{canStart ? '开始考试' : '不可用'}
</Button>
</Space>
);
}
}
];
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
<Spin size="large" />
</div>
);
}
return (
<div className="container mx-auto p-6 max-w-6xl">
<div className="mb-8">
<Title level={2} className="text-center mb-2">
</Title>
<Text type="secondary" className="text-center block">
</Text>
</div>
<Card className="shadow-sm">
<Table
columns={columns}
dataSource={tasks}
rowKey="id"
pagination={{
pageSize: 10,
showSizeChanger: true,
showTotal: (total) => `${total} 条记录`
}}
locale={{
emptyText: '暂无考试任务'
}}
/>
</Card>
<div className="mt-6 text-center">
<Button
type="default"
onClick={() => navigate('/subjects')}
icon={<BookOutlined />}
>
</Button>
</div>
</div>
);
};