主要改动: 1. 组件拆分:将Question.tsx(605行)拆分为4个子组件(303行) - QuestionProgress: 进度条和统计显示 - QuestionCard: 题目卡片和答题界面 - AnswerResult: 答案结果展示 - CompletionSummary: 完成统计摘要 2. 新增功能: - 答题进度条:显示当前进度、正确数、错误数 - 进度保存:使用localStorage持久化答题进度 - 完成统计:答完所有题目后显示统计摘要和正确率 - 从第一题开始:改为顺序答题而非随机 3. UI优化: - 移除右上角统计按钮 - 移除底部随机题目、题目列表、筛选按钮 - 移除"开始xxx答题"提示消息 - 简化页面布局 4. 代码优化: - 提高代码可维护性和可测试性 - 单一职责原则,每个组件负责一个特定功能 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
267 lines
7.5 KiB
TypeScript
267 lines
7.5 KiB
TypeScript
import React, { useEffect, useState } from 'react'
|
||
import { useNavigate } from 'react-router-dom'
|
||
import { Card, Statistic, Row, Col, Typography, message, Space, Avatar, Button, Modal } from 'antd'
|
||
import {
|
||
FileTextOutlined,
|
||
CheckCircleOutlined,
|
||
UnorderedListOutlined,
|
||
EditOutlined,
|
||
RocketOutlined,
|
||
BookOutlined,
|
||
UserOutlined,
|
||
LogoutOutlined,
|
||
} from '@ant-design/icons'
|
||
import * as questionApi from '../api/question'
|
||
import type { Statistics } from '../types/question'
|
||
import styles from './Home.module.less'
|
||
|
||
const { Title, Paragraph, Text } = Typography
|
||
|
||
// 题型配置 - 使用数据库中的实际类型
|
||
const questionTypes = [
|
||
{
|
||
key: 'multiple-choice',
|
||
title: '选择题',
|
||
icon: <CheckCircleOutlined />,
|
||
color: '#1677ff',
|
||
description: '基础知识考察',
|
||
},
|
||
{
|
||
key: 'multiple-selection',
|
||
title: '多选题',
|
||
icon: <UnorderedListOutlined />,
|
||
color: '#52c41a',
|
||
description: '综合能力提升',
|
||
},
|
||
{
|
||
key: 'true-false',
|
||
title: '判断题',
|
||
icon: <CheckCircleOutlined />,
|
||
color: '#fa8c16',
|
||
description: '快速判断训练',
|
||
},
|
||
{
|
||
key: 'fill-in-blank',
|
||
title: '填空题',
|
||
icon: <FileTextOutlined />,
|
||
color: '#722ed1',
|
||
description: '填空补充练习',
|
||
},
|
||
{
|
||
key: 'short-answer',
|
||
title: '简答题',
|
||
icon: <EditOutlined />,
|
||
color: '#eb2f96',
|
||
description: '深度理解练习',
|
||
},
|
||
]
|
||
|
||
interface UserInfo {
|
||
username: string
|
||
nickname: string
|
||
avatar: string
|
||
}
|
||
|
||
const Home: React.FC = () => {
|
||
const navigate = useNavigate()
|
||
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
|
||
const [statistics, setStatistics] = useState<Statistics>({
|
||
total_questions: 0,
|
||
answered_questions: 0,
|
||
correct_answers: 0,
|
||
accuracy: 0,
|
||
})
|
||
|
||
// 加载统计数据
|
||
const loadStatistics = async () => {
|
||
try {
|
||
const res = await questionApi.getStatistics()
|
||
if (res.success && res.data) {
|
||
setStatistics(res.data)
|
||
}
|
||
} catch (error) {
|
||
console.error('加载统计失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载用户信息
|
||
useEffect(() => {
|
||
const token = localStorage.getItem('token')
|
||
const savedUserInfo = localStorage.getItem('user')
|
||
|
||
if (token && savedUserInfo) {
|
||
try {
|
||
setUserInfo(JSON.parse(savedUserInfo))
|
||
} catch (e) {
|
||
console.error('解析用户信息失败', e)
|
||
}
|
||
}
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
loadStatistics()
|
||
}, [])
|
||
|
||
// 点击题型卡片
|
||
const handleTypeClick = async (type: string) => {
|
||
try {
|
||
// 加载该题型的题目列表
|
||
const res = await questionApi.getQuestions({ type })
|
||
if (res.success && res.data && res.data.length > 0) {
|
||
// 跳转到答题页面,并传递题型参数
|
||
navigate(`/question?type=${type}`)
|
||
} else {
|
||
message.warning('该题型暂无题目')
|
||
}
|
||
} catch (error) {
|
||
message.error('加载题目失败')
|
||
}
|
||
}
|
||
|
||
// 退出登录
|
||
const handleLogout = () => {
|
||
Modal.confirm({
|
||
title: '确定要退出登录吗?',
|
||
onOk: () => {
|
||
localStorage.removeItem('token')
|
||
localStorage.removeItem('user')
|
||
setUserInfo(null)
|
||
message.success('已退出登录')
|
||
navigate('/login')
|
||
},
|
||
})
|
||
}
|
||
|
||
return (
|
||
<div className={styles.container}>
|
||
{/* 头部 */}
|
||
<div className={styles.header}>
|
||
<div className={styles.headerLeft}>
|
||
<Title level={2} className={styles.title}>AnKao 刷题</Title>
|
||
<Paragraph className={styles.subtitle}>选择题型开始练习</Paragraph>
|
||
</div>
|
||
{/* 用户信息 */}
|
||
{userInfo && (
|
||
<div className={styles.userInfo}>
|
||
<Space size="middle">
|
||
<Avatar
|
||
src={userInfo.avatar || undefined}
|
||
size={40}
|
||
icon={<UserOutlined />}
|
||
/>
|
||
<div className={styles.userDetails}>
|
||
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
|
||
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
|
||
</div>
|
||
<Button
|
||
type="text"
|
||
danger
|
||
icon={<LogoutOutlined />}
|
||
onClick={handleLogout}
|
||
>
|
||
退出
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 统计卡片 */}
|
||
<Card className={styles.statsCard}>
|
||
<Row gutter={[16, 16]}>
|
||
<Col xs={8} sm={8} md={8}>
|
||
<Statistic
|
||
title="题库总数"
|
||
value={statistics.total_questions}
|
||
valueStyle={{ color: '#1677ff', fontSize: '24px' }}
|
||
/>
|
||
</Col>
|
||
<Col xs={8} sm={8} md={8}>
|
||
<Statistic
|
||
title="已答题数"
|
||
value={statistics.answered_questions}
|
||
valueStyle={{ color: '#52c41a', fontSize: '24px' }}
|
||
/>
|
||
</Col>
|
||
<Col xs={8} sm={8} md={8}>
|
||
<Statistic
|
||
title="正确率"
|
||
value={statistics.accuracy.toFixed(0)}
|
||
suffix="%"
|
||
valueStyle={{ color: '#fa8c16', fontSize: '24px' }}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
{/* 题型选择 */}
|
||
<div className={styles.typeSection}>
|
||
<Title level={4} className={styles.sectionTitle}>
|
||
<FileTextOutlined /> 选择题型
|
||
</Title>
|
||
<Row gutter={[16, 16]}>
|
||
{questionTypes.map(type => (
|
||
<Col xs={24} sm={12} md={8} lg={8} key={type.key}>
|
||
<Card
|
||
hoverable
|
||
className={styles.typeCard}
|
||
onClick={() => handleTypeClick(type.key)}
|
||
styles={{
|
||
body: {
|
||
textAlign: 'center',
|
||
padding: '24px',
|
||
}
|
||
}}
|
||
>
|
||
<div className={styles.typeIcon} style={{ color: type.color, fontSize: '48px' }}>
|
||
{type.icon}
|
||
</div>
|
||
<Title level={5} className={styles.typeTitle}>{type.title}</Title>
|
||
<Paragraph type="secondary" className={styles.typeDesc}>{type.description}</Paragraph>
|
||
</Card>
|
||
</Col>
|
||
))}
|
||
</Row>
|
||
</div>
|
||
|
||
{/* 快速开始 */}
|
||
<div className={styles.quickStart}>
|
||
<Card
|
||
hoverable
|
||
className={styles.quickCard}
|
||
onClick={() => navigate('/question')}
|
||
>
|
||
<Space align="center" size="large">
|
||
<div className={styles.quickIcon}>
|
||
<RocketOutlined style={{ fontSize: '32px', color: '#722ed1' }} />
|
||
</div>
|
||
<div>
|
||
<Title level={5} style={{ margin: 0 }}>随机练习</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0 }}>从所有题型中随机抽取</Paragraph>
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
|
||
<Card
|
||
hoverable
|
||
className={styles.quickCard}
|
||
onClick={() => navigate('/wrong-questions')}
|
||
style={{ marginTop: '16px' }}
|
||
>
|
||
<Space align="center" size="large">
|
||
<div className={styles.quickIcon}>
|
||
<BookOutlined style={{ fontSize: '32px', color: '#ff4d4f' }} />
|
||
</div>
|
||
<div>
|
||
<Title level={5} style={{ margin: 0 }}>错题本</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0 }}>复习错题,巩固薄弱知识点</Paragraph>
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default Home
|