新增功能: 1. AI智能评分系统 - 集成OpenAI兼容API进行简答题评分 - 提供分数、评语和改进建议 - 支持自定义AI服务配置(BaseURL、APIKey、Model) 2. 题目列表页面 - 展示所有题目和答案 - Tab标签页形式的题型筛选(选择题、多选题、判断题、填空题、简答题) - 关键词搜索功能(支持题目内容和编号搜索) - 填空题特殊渲染:****显示为下划线 - 判断题不显示选项,界面更简洁 3. UI优化 - 答题结果组件重构,支持AI评分显示 - 首页新增"题目列表"快速入口 - 响应式设计,适配移动端和PC端 技术改进: - 添加AI评分服务层(internal/services/ai_grading.go) - 扩展题目模型支持AI评分结果 - 更新配置管理支持AI服务配置 - 添加AI评分测试脚本和文档 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
324 lines
10 KiB
TypeScript
324 lines
10 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,
|
||
SettingOutlined,
|
||
UnorderedListOutlined as ListOutlined,
|
||
} 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,
|
||
wrong_questions: 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={12} sm={12} md={6}>
|
||
<Statistic
|
||
title="题库总数"
|
||
value={statistics.total_questions}
|
||
valueStyle={{ color: '#1677ff', fontSize: '24px' }}
|
||
/>
|
||
</Col>
|
||
<Col xs={12} sm={12} md={6}>
|
||
<Statistic
|
||
title="已答题数"
|
||
value={statistics.answered_questions}
|
||
valueStyle={{ color: '#52c41a', fontSize: '24px' }}
|
||
/>
|
||
</Col>
|
||
<Col xs={12} sm={12} md={6}>
|
||
<Statistic
|
||
title="错题数量"
|
||
value={statistics.wrong_questions}
|
||
valueStyle={{ color: '#ff4d4f', fontSize: '24px' }}
|
||
/>
|
||
</Col>
|
||
<Col xs={12} sm={12} md={6}>
|
||
<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={[12, 12]}>
|
||
{questionTypes.map(type => (
|
||
<Col xs={12} sm={12} md={8} lg={6} xl={4} key={type.key}>
|
||
<Card
|
||
hoverable
|
||
className={styles.typeCard}
|
||
onClick={() => handleTypeClick(type.key)}
|
||
styles={{
|
||
body: {
|
||
textAlign: 'center',
|
||
padding: '20px 12px',
|
||
}
|
||
}}
|
||
>
|
||
<div className={styles.typeIcon} style={{ color: type.color, fontSize: '40px' }}>
|
||
{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}>
|
||
<Title level={4} className={styles.sectionTitle}>
|
||
<RocketOutlined /> 快速开始
|
||
</Title>
|
||
<Row gutter={[12, 12]}>
|
||
<Col xs={24} sm={24} md={12} lg={8}>
|
||
<Card
|
||
hoverable
|
||
className={styles.quickCard}
|
||
onClick={() => navigate('/question')}
|
||
>
|
||
<Space align="center" size="middle" style={{ width: '100%' }}>
|
||
<div className={styles.quickIcon}>
|
||
<RocketOutlined style={{ fontSize: '32px', color: '#722ed1' }} />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<Title level={5} style={{ margin: 0 }}>随机练习</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}>从所有题型中随机抽取</Paragraph>
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
</Col>
|
||
|
||
<Col xs={24} sm={24} md={12} lg={8}>
|
||
<Card
|
||
hoverable
|
||
className={styles.quickCard}
|
||
onClick={() => navigate('/wrong-questions')}
|
||
>
|
||
<Space align="center" size="middle" style={{ width: '100%' }}>
|
||
<div className={styles.quickIcon}>
|
||
<BookOutlined style={{ fontSize: '32px', color: '#ff4d4f' }} />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<Title level={5} style={{ margin: 0 }}>错题本</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}>复习错题,巩固薄弱知识点</Paragraph>
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
</Col>
|
||
|
||
<Col xs={24} sm={24} md={12} lg={8}>
|
||
<Card
|
||
hoverable
|
||
className={styles.quickCard}
|
||
onClick={() => navigate('/question-list')}
|
||
>
|
||
<Space align="center" size="middle" style={{ width: '100%' }}>
|
||
<div className={styles.quickIcon}>
|
||
<ListOutlined style={{ fontSize: '32px', color: '#1677ff' }} />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<Title level={5} style={{ margin: 0 }}>题目列表</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}>查看所有题目和答案</Paragraph>
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
</Col>
|
||
|
||
{/* 仅 yanlongqi 用户显示题库管理 */}
|
||
{userInfo?.username === 'yanlongqi' && (
|
||
<Col xs={24} sm={24} md={12} lg={8}>
|
||
<Card
|
||
hoverable
|
||
className={styles.quickCard}
|
||
onClick={() => navigate('/question-management')}
|
||
>
|
||
<Space align="center" size="middle" style={{ width: '100%' }}>
|
||
<div className={styles.quickIcon}>
|
||
<SettingOutlined style={{ fontSize: '32px', color: '#13c2c2' }} />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<Title level={5} style={{ margin: 0 }}>题库管理</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0, fontSize: '13px' }}>添加、编辑和删除题目</Paragraph>
|
||
</div>
|
||
</Space>
|
||
</Card>
|
||
</Col>
|
||
)}
|
||
</Row>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default Home
|