Files
Web_BLV_OA_Exam_Prod/src/pages/UserTaskPage.tsx

225 lines
5.7 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 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>
);
};