后端实现: - 创建错题数据模型和数据库表结构 - 实现错题记录、查询、统计、标记和清空API - 答题错误时自动记录到错题本 - 支持重复错误累计次数和更新时间 前端实现: - 创建错题本页面,支持查看、筛选和管理错题 - 实现错题统计展示(总数、已掌握、待掌握) - 支持标记已掌握、清空错题本和重做题目 - 在首页和个人中心添加错题本入口 - 完整的响应式设计适配移动端和PC端 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
3.8 KiB
TypeScript
149 lines
3.8 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import {
|
|
Card,
|
|
Avatar,
|
|
List,
|
|
Button,
|
|
Modal,
|
|
message,
|
|
Typography,
|
|
Space,
|
|
} from 'antd'
|
|
import {
|
|
RightOutlined,
|
|
SettingOutlined,
|
|
FileTextOutlined,
|
|
UserOutlined,
|
|
BookOutlined,
|
|
} from '@ant-design/icons'
|
|
import styles from './Profile.module.less'
|
|
|
|
const { Title, Text } = Typography
|
|
|
|
interface UserInfo {
|
|
username: string
|
|
nickname: string
|
|
avatar: string
|
|
}
|
|
|
|
const Profile: React.FC = () => {
|
|
const navigate = useNavigate()
|
|
const [userInfo, setUserInfo] = useState<UserInfo | null>(null)
|
|
|
|
useEffect(() => {
|
|
// 从 localStorage 获取用户信息
|
|
const token = localStorage.getItem('token')
|
|
const savedUserInfo = localStorage.getItem('user')
|
|
|
|
if (token && savedUserInfo) {
|
|
try {
|
|
setUserInfo(JSON.parse(savedUserInfo))
|
|
} catch (e) {
|
|
console.error('解析用户信息失败', e)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
const handleLogout = () => {
|
|
Modal.confirm({
|
|
title: '确定要退出登录吗?',
|
|
onOk: () => {
|
|
localStorage.removeItem('token')
|
|
localStorage.removeItem('user')
|
|
setUserInfo(null)
|
|
message.success('已退出登录')
|
|
navigate('/login')
|
|
},
|
|
})
|
|
}
|
|
|
|
const handleLogin = () => {
|
|
navigate('/login')
|
|
}
|
|
|
|
return (
|
|
<div className={styles.container}>
|
|
<div className={styles.content}>
|
|
{/* 用户信息卡片 */}
|
|
<Card className={styles.userCard}>
|
|
{userInfo ? (
|
|
<div className={styles.userInfo}>
|
|
<Avatar
|
|
src={userInfo.avatar || undefined}
|
|
size={80}
|
|
icon={<UserOutlined />}
|
|
/>
|
|
<div className={styles.userDetails}>
|
|
<Title level={4} className={styles.userNickname}>{userInfo.nickname}</Title>
|
|
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className={styles.userInfo}>
|
|
<Avatar size={80} icon={<UserOutlined />} />
|
|
<div className={styles.userDetails}>
|
|
<Title level={4} className={styles.userNickname}>未登录</Title>
|
|
<Button type="primary" onClick={handleLogin} style={{ marginTop: 8 }}>
|
|
点击登录
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
|
|
{/* 功能列表 */}
|
|
<Card title="功能" className={styles.menuCard}>
|
|
<List>
|
|
<List.Item
|
|
onClick={() => navigate('/wrong-questions')}
|
|
style={{ cursor: 'pointer' }}
|
|
>
|
|
<Space>
|
|
<BookOutlined />
|
|
<span>错题本</span>
|
|
</Space>
|
|
<RightOutlined />
|
|
</List.Item>
|
|
<List.Item
|
|
onClick={() => message.info('功能开发中')}
|
|
style={{ cursor: 'pointer' }}
|
|
>
|
|
<Space>
|
|
<FileTextOutlined />
|
|
<span>我的题目</span>
|
|
</Space>
|
|
<RightOutlined />
|
|
</List.Item>
|
|
<List.Item
|
|
onClick={() => message.info('功能开发中')}
|
|
style={{ cursor: 'pointer' }}
|
|
>
|
|
<Space>
|
|
<SettingOutlined />
|
|
<span>设置</span>
|
|
</Space>
|
|
<RightOutlined />
|
|
</List.Item>
|
|
</List>
|
|
</Card>
|
|
|
|
{/* 退出登录按钮 */}
|
|
{userInfo && (
|
|
<Button
|
|
danger
|
|
block
|
|
size="large"
|
|
onClick={handleLogout}
|
|
className={styles.logoutButton}
|
|
>
|
|
退出登录
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default Profile
|