优化首页UI设计和图标样式

主要改进:
- 重构用户信息卡片,新增统计数据展示区域
- 统一题型卡片图标样式,添加渐变背景容器
- 优化快速开始卡片的图标设计
- 调整配色方案为明快风格,提升视觉效果
- 修改题型图标:判断题使用CloseCircleOutlined,填空题使用FormOutlined,论述题使用FileMarkdownOutlined
- 完善响应式设计,优化移动端和PC端显示效果

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
燕陇琪 2025-11-08 00:26:44 +08:00
parent b082e708ae
commit 4a7c9a2593
6 changed files with 624 additions and 133 deletions

View File

@ -661,23 +661,25 @@ func GetStatistics(c *gin.Context) {
Where("user_id = ?", uid). Where("user_id = ?", uid).
Count(&wrongQuestions) Count(&wrongQuestions)
// 计算正确率 // 计算正确率和总答题数
var accuracy float64 var accuracy float64
if answeredQuestions > 0 { var totalAnswers int64
db.Model(&models.UserAnswerRecord{}).
Where("user_id = ?", uid).
Count(&totalAnswers)
if totalAnswers > 0 {
// 正确率 = 答对题数 / 总答题数 // 正确率 = 答对题数 / 总答题数
var totalAnswers int64
db.Model(&models.UserAnswerRecord{}).
Where("user_id = ?", uid).
Count(&totalAnswers)
accuracy = float64(correctAnswers) / float64(totalAnswers) * 100 accuracy = float64(correctAnswers) / float64(totalAnswers) * 100
} }
stats := models.UserStatistics{ stats := gin.H{
TotalQuestions: int(totalQuestions), "total_questions": int(totalQuestions),
AnsweredQuestions: int(answeredQuestions), "answered_questions": int(answeredQuestions),
CorrectAnswers: int(correctAnswers), "correct_answers": int(correctAnswers),
WrongQuestions: int(wrongQuestions), "wrong_questions": int(wrongQuestions),
Accuracy: accuracy, "total_answers": int(totalAnswers), // 刷题次数
"accuracy": accuracy,
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{

View File

@ -36,12 +36,12 @@ func NewAIGradingService() *AIGradingService {
// AIGradingResult AI评分结果 // AIGradingResult AI评分结果
type AIGradingResult struct { type AIGradingResult struct {
Score float64 `json:"score"` // 得分 (0-100) Score float64 `json:"score"` // 得分 (0-100)
IsCorrect bool `json:"is_correct"` // 是否正确 (Score >= 60 视为正确) IsCorrect bool `json:"is_correct"` // 是否正确 (Score >= 60 视为正确)
Feedback string `json:"feedback"` // 评语 Feedback string `json:"feedback"` // 评语
Suggestion string `json:"suggestion"` // 改进建议 Suggestion string `json:"suggestion"` // 改进建议
ReferenceAnswer string `json:"reference_answer"` // 参考答案(论述题) ReferenceAnswer string `json:"reference_answer"` // 参考答案(论述题)
ScoringRationale string `json:"scoring_rationale"` // 评分依据 ScoringRationale string `json:"scoring_rationale"` // 评分依据
} }
// GradeEssay 对论述题进行AI评分不需要标准答案 // GradeEssay 对论述题进行AI评分不需要标准答案
@ -275,7 +275,6 @@ func (s *AIGradingService) ExplainQuestionStream(writer http.ResponseWriter, que
1. **必须基于保密法规**解析时必须引用相关法规条文说明依据哪些具体法律法规 1. **必须基于保密法规**解析时必须引用相关法规条文说明依据哪些具体法律法规
2. **必须实事求是**只基于题目内容标准答案和实际法规进行解析 2. **必须实事求是**只基于题目内容标准答案和实际法规进行解析
3. **不要胡编乱造**如果某些信息不确定或题目没有提供请如实说明不要编造法规条文 3. **不要胡编乱造**如果某些信息不确定或题目没有提供请如实说明不要编造法规条文
4. **使用Markdown格式**使用标题列表加粗等markdown语法使内容更清晰易读
解析内容要求 解析内容要求
- **知识点**说明题目考查的核心知识点指出涉及哪些保密法规 - **知识点**说明题目考查的核心知识点指出涉及哪些保密法规

View File

@ -78,7 +78,7 @@ func (c *DatabaseConfig) GetDSN() string {
// GetAIConfig 获取AI服务配置 // GetAIConfig 获取AI服务配置
// 优先使用环境变量,如果没有设置则使用默认值 // 优先使用环境变量,如果没有设置则使用默认值
func GetAIConfig() *AIConfig { func GetAIConfig() *AIConfig {
baseURL := getEnv("AI_BASE_URL", "https://ai.yuchat.top") baseURL := getEnv("AI_BASE_URL", "http://172.20.0.117")
apiKey := getEnv("AI_API_KEY", "sk-OKBmOpJx855juSOPU14cWG6Iz87tZQuv3Xg9PiaJYXdHoKcN") apiKey := getEnv("AI_API_KEY", "sk-OKBmOpJx855juSOPU14cWG6Iz87tZQuv3Xg9PiaJYXdHoKcN")
model := getEnv("AI_MODEL", "deepseek-v3") model := getEnv("AI_MODEL", "deepseek-v3")

View File

@ -8,7 +8,7 @@
.header { .header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; align-items: center;
margin-bottom: 24px; margin-bottom: 24px;
.headerLeft { .headerLeft {
@ -29,62 +29,196 @@
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.08)); filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.08));
} }
.titleRow {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 4px;
}
.title { .title {
color: #1d1d1f !important; color: #1d1d1f !important;
margin-bottom: 4px !important; margin-bottom: 0 !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.02); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
font-weight: 700; font-weight: 700;
} }
.totalBadge {
display: inline-flex;
align-items: center;
padding: 4px 12px;
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%);
border: 1px solid #91d5ff;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
color: #0958d9;
line-height: 1;
box-shadow: 0 2px 4px rgba(9, 88, 217, 0.1);
}
.subtitle { .subtitle {
color: #6e6e73; color: #6e6e73;
font-size: 14px; font-size: 14px;
} }
.userInfo { .userInfo {
background: rgba(255, 255, 255, 0.85); background: #fff;
padding: 12px 16px; padding: 20px;
border-radius: 16px; border-radius: 16px;
backdrop-filter: blur(30px) saturate(180%); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px rgba(0, 0, 0, 0.02);
-webkit-backdrop-filter: blur(30px) saturate(180%); border: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: transition: box-shadow 0.2s ease;
0 2px 8px rgba(0, 0, 0, 0.04), min-width: 520px;
0 1px 3px rgba(0, 0, 0, 0.02),
0 0 0 1px rgba(0, 0, 0, 0.03);
border: 0.5px solid rgba(0, 0, 0, 0.04);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover { .userInfoContent {
background: rgba(255, 255, 255, 0.95); display: flex;
box-shadow: align-items: center;
0 4px 12px rgba(0, 0, 0, 0.06), gap: 16px;
0 2px 6px rgba(0, 0, 0, 0.04), margin-bottom: 16px;
0 0 0 1px rgba(0, 0, 0, 0.04); padding-bottom: 16px;
transform: translateY(-2px); border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.avatarWrapper {
position: relative;
flex-shrink: 0;
.userAvatar {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid #fff;
}
.avatarBadge {
position: absolute;
bottom: -2px;
right: -2px;
width: 22px;
height: 22px;
background: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
font-size: 12px;
:global {
.anticon {
&.anticon-check-circle {
color: #52c41a;
}
&.anticon-setting {
color: #ff4d4f;
}
}
}
}
} }
.userDetails { .userDetails {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px; gap: 4px;
flex: 1;
min-width: 0;
.userNickname { .userNickname {
color: #1d1d1f !important; color: #1d1d1f !important;
font-size: 15px; font-size: 16px;
font-weight: 600; font-weight: 600;
line-height: 1.2; line-height: 1.3;
} }
.userUsername { .userUsername {
color: #6e6e73 !important; color: #6e6e73 !important;
font-size: 12px; font-size: 13px;
line-height: 1.2; line-height: 1.3;
} }
.userType { .userTypeBadge {
font-size: 11px; display: inline-flex;
color: #6e6e73; align-items: center;
gap: 5px;
padding: 5px 12px;
background: linear-gradient(135deg, #e6f7ff 0%, #d6f0ff 100%);
border-radius: 8px;
border: 1px solid #91caff;
width: fit-content;
margin-top: 2px;
.badgeIcon {
font-size: 12px;
color: #1890ff;
}
.userTypeText {
font-size: 12px;
color: #1890ff;
font-weight: 500;
line-height: 1;
}
}
}
.dropdownTrigger {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: transparent;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
&:hover {
background: #f5f5f5;
}
&:active {
background: #e8e8e8;
}
.dropdownIcon {
font-size: 12px;
color: #8c8c8c;
}
}
.userStatsRow {
display: flex;
align-items: center;
justify-content: space-around;
gap: 12px;
.statItem {
flex: 1;
text-align: center;
padding: 8px 4px;
border-radius: 8px;
background: #fafafa;
.statValue {
font-size: 18px;
font-weight: 700;
color: #1890ff;
line-height: 1.3;
margin-bottom: 2px;
}
.statLabel {
font-size: 11px;
color: #8c8c8c;
font-weight: 500;
}
}
.statDivider {
width: 1px;
height: 24px;
background: #e8e8e8;
} }
} }
} }
@ -121,6 +255,83 @@
gap: 8px; gap: 8px;
font-weight: 700; font-weight: 700;
font-size: 18px !important; font-size: 18px !important;
:global {
.anticon {
font-size: 20px;
color: #1890ff;
}
}
}
}
.progressSection {
margin-bottom: 24px;
.sectionTitle {
color: #1d1d1f !important;
margin-bottom: 16px !important;
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 18px !important;
}
.progressCard {
border-radius: 16px;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02), 0 2px 4px rgba(0, 0, 0, 0.02);
border: 1px solid rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
height: 100%;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
:global {
.ant-card-body {
padding: 20px;
}
}
.progressCardContent {
display: flex;
align-items: center;
gap: 16px;
}
.progressIcon {
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.progressInfo {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
.progressLabel {
font-size: 12px;
line-height: 1;
}
.progressValue {
margin: 4px 0 0 0 !important;
font-size: 28px !important;
font-weight: 700;
line-height: 1;
color: #1d1d1f;
}
}
} }
} }
@ -146,8 +357,23 @@
0 0 0 1px rgba(0, 0, 0, 0.04); 0 0 0 1px rgba(0, 0, 0, 0.04);
} }
.typeIconWrapper {
width: 64px;
height: 64px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 12px;
border: 1.5px solid;
transition: all 0.3s ease;
}
.typeIcon { .typeIcon {
margin-bottom: 10px; font-size: 32px;
display: flex;
align-items: center;
justify-content: center;
} }
.typeTitle { .typeTitle {
@ -173,6 +399,13 @@
gap: 8px; gap: 8px;
font-weight: 700; font-weight: 700;
font-size: 18px !important; font-size: 18px !important;
:global {
.anticon {
font-size: 20px;
color: #73d13d;
}
}
} }
.quickCard { .quickCard {
@ -197,6 +430,22 @@
0 0 0 1px rgba(0, 0, 0, 0.04); 0 0 0 1px rgba(0, 0, 0, 0.04);
} }
} }
.quickIconWrapper {
width: 56px;
height: 56px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: 1.5px solid;
transition: all 0.3s ease;
}
.quickIcon {
font-size: 28px;
}
} }
// 响应式设计 - 移动端 (< 768px) // 响应式设计 - 移动端 (< 768px)
@ -224,27 +473,104 @@
height: 48px; height: 48px;
} }
.titleRow {
gap: 8px;
flex-wrap: wrap;
}
.title { .title {
font-size: 22px !important; font-size: 22px !important;
} }
.totalBadge {
padding: 3px 8px;
font-size: 12px;
border-radius: 16px;
}
.subtitle { .subtitle {
font-size: 13px; font-size: 13px;
} }
.userInfo { .userInfo {
width: 100%; width: 100%;
padding: 8px 12px; min-width: auto;
padding: 16px;
border-radius: 12px;
:global { .userInfoContent {
.ant-space { gap: 12px;
width: 100%; margin-bottom: 12px;
justify-content: space-between; padding-bottom: 12px;
}
.avatarWrapper {
.userAvatar {
width: 48px !important;
height: 48px !important;
} }
.ant-btn { .avatarBadge {
font-size: 12px; width: 20px;
padding: 4px 8px; height: 20px;
font-size: 11px;
bottom: -1px;
right: -1px;
}
}
.userDetails {
gap: 3px;
.userNickname {
font-size: 15px !important;
}
.userUsername {
font-size: 12px !important;
}
.userTypeBadge {
padding: 3px 8px;
margin-top: 1px;
.badgeIcon {
font-size: 10px;
}
.userTypeText {
font-size: 10px;
}
}
}
.dropdownTrigger {
width: 24px;
height: 24px;
.dropdownIcon {
font-size: 11px;
}
}
.userStatsRow {
gap: 8px;
.statItem {
padding: 6px 4px;
border-radius: 6px;
.statValue {
font-size: 16px;
}
.statLabel {
font-size: 10px;
}
}
.statDivider {
height: 20px;
} }
} }
} }
@ -273,11 +599,59 @@
} }
} }
.progressSection {
margin-bottom: 16px;
.sectionTitle {
font-size: 16px !important;
margin-bottom: 12px !important;
}
.progressCard {
border-radius: 12px;
:global {
.ant-card-body {
padding: 16px;
}
}
.progressIcon {
width: 48px;
height: 48px;
border-radius: 10px;
:global {
svg {
font-size: 20px !important;
}
}
}
.progressInfo {
.progressLabel {
font-size: 11px;
}
.progressValue {
font-size: 22px !important;
}
}
}
}
.typeCard { .typeCard {
border-radius: 12px; border-radius: 12px;
.typeIconWrapper {
width: 56px;
height: 56px;
border-radius: 12px;
margin-bottom: 10px;
}
.typeIcon { .typeIcon {
font-size: 32px !important; font-size: 28px;
} }
.typeTitle { .typeTitle {
@ -312,6 +686,16 @@
} }
} }
} }
.quickIconWrapper {
width: 48px;
height: 48px;
border-radius: 12px;
}
.quickIcon {
font-size: 24px;
}
} }
} }
@ -326,11 +710,38 @@
.title { .title {
font-size: 28px !important; font-size: 28px !important;
} }
.userInfo {
min-width: 450px;
.avatarWrapper .userAvatar {
width: 52px !important;
height: 52px !important;
}
.userStatsRow .statItem {
.statValue {
font-size: 17px;
}
}
}
} }
.typeCard { .typeCard {
.typeIconWrapper {
width: 60px;
height: 60px;
border-radius: 14px;
}
.typeIcon { .typeIcon {
font-size: 36px !important; font-size: 30px;
}
}
.progressSection {
.sectionTitle {
font-size: 17px !important;
} }
} }
} }
@ -354,6 +765,47 @@
.subtitle { .subtitle {
font-size: 15px; font-size: 15px;
} }
.userInfo {
min-width: 560px;
.avatarWrapper .userAvatar {
width: 60px !important;
height: 60px !important;
}
.userDetails {
.userNickname {
font-size: 17px !important;
}
.userUsername {
font-size: 14px !important;
}
.userTypeBadge {
padding: 5px 12px;
.badgeIcon {
font-size: 12px;
}
.userTypeText {
font-size: 12px;
}
}
}
.userStatsRow .statItem {
.statValue {
font-size: 20px;
}
.statLabel {
font-size: 12px;
}
}
}
} }
.statsCard { .statsCard {
@ -368,6 +820,14 @@
} }
} }
.progressSection {
margin-bottom: 32px;
.sectionTitle {
font-size: 20px !important;
}
}
.quickStart { .quickStart {
.sectionTitle { .sectionTitle {
font-size: 20px !important; font-size: 20px !important;

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Card, Statistic, Row, Col, Typography, message, Space, Avatar, Button, Modal, Form, Radio, Alert, Input, Switch, InputNumber, Divider, Badge, Dropdown } from 'antd' import { Typography, message, Space, Avatar, Button, Modal, Form, Radio, Alert, Input, Switch, InputNumber, Divider, Dropdown, Row, Col, Card } from 'antd'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { import {
FileTextOutlined, FileTextOutlined,
@ -15,7 +15,10 @@ import {
UnorderedListOutlined as ListOutlined, UnorderedListOutlined as ListOutlined,
LockOutlined, LockOutlined,
IdcardOutlined, IdcardOutlined,
DownOutlined, MoreOutlined,
CloseCircleOutlined,
FormOutlined,
FileMarkdownOutlined,
} from '@ant-design/icons' } from '@ant-design/icons'
import * as questionApi from '../api/question' import * as questionApi from '../api/question'
import { fetchWithAuth } from '../utils/request' import { fetchWithAuth } from '../utils/request'
@ -24,48 +27,48 @@ import styles from './Home.module.less'
const { Title, Paragraph, Text } = Typography const { Title, Paragraph, Text } = Typography
// 题型配置 - 使用数据库中的实际类型 // 题型配置 - 使用数据库中的实际类型,采用明快的配色方案
const questionTypes = [ const questionTypes = [
{ {
key: 'multiple-choice', key: 'multiple-choice',
title: '选择题', title: '选择题',
icon: <CheckCircleOutlined />, icon: <CheckCircleOutlined />,
color: '#1677ff', color: '#1890ff', // 明亮的蓝色
description: '基础知识考察', description: '基础知识考察',
}, },
{ {
key: 'multiple-selection', key: 'multiple-selection',
title: '多选题', title: '多选题',
icon: <UnorderedListOutlined />, icon: <UnorderedListOutlined />,
color: '#52c41a', color: '#52c41a', // 明亮的绿色
description: '综合能力提升', description: '综合能力提升',
}, },
{ {
key: 'true-false', key: 'true-false',
title: '判断题', title: '判断题',
icon: <CheckCircleOutlined />, icon: <CloseCircleOutlined />,
color: '#fa8c16', color: '#fa8c16', // 明亮的橙色
description: '快速判断训练', description: '快速判断训练',
}, },
{ {
key: 'fill-in-blank', key: 'fill-in-blank',
title: '填空题', title: '填空题',
icon: <FileTextOutlined />, icon: <FormOutlined />,
color: '#722ed1', color: '#faad14', // 明亮的金色
description: '填空补充练习', description: '填空补充练习',
}, },
{ {
key: 'short-answer', key: 'short-answer',
title: '简答题', title: '简答题',
icon: <EditOutlined />, icon: <EditOutlined />,
color: '#eb2f96', color: '#722ed1', // 明亮的紫色
description: '深度理解练习', description: '深度理解练习',
}, },
{ {
key: 'essay', // 特殊标识,根据用户类型动态路由 key: 'essay', // 特殊标识,根据用户类型动态路由
title: '论述题', title: '论述题',
icon: <FileTextOutlined />, icon: <FileMarkdownOutlined />,
color: '#f759ab', color: '#eb2f96', // 明亮的粉色
description: '深度分析与表达', description: '深度分析与表达',
}, },
] ]
@ -93,6 +96,7 @@ const Home: React.FC = () => {
answered_questions: 0, answered_questions: 0,
correct_answers: 0, correct_answers: 0,
wrong_questions: 0, wrong_questions: 0,
total_answers: 0,
accuracy: 0, accuracy: 0,
}) })
@ -360,75 +364,74 @@ const Home: React.FC = () => {
<div className={styles.logoArea}> <div className={styles.logoArea}>
<img src="/icon.svg" alt="AnKao Logo" className={styles.logo} /> <img src="/icon.svg" alt="AnKao Logo" className={styles.logo} />
<div> <div>
<Title level={2} className={styles.title}>AnKao </Title> <div className={styles.titleRow}>
<Title level={2} className={styles.title}>AnKao </Title>
<span className={styles.totalBadge}>{statistics.total_questions} </span>
</div>
<Paragraph className={styles.subtitle}></Paragraph> <Paragraph className={styles.subtitle}></Paragraph>
</div> </div>
</div> </div>
</div> </div>
{/* 用户信息 */} {/* 用户信息 */}
{userInfo && ( {userInfo && (
<Dropdown menu={{ items: userMenuItems }} trigger={['click']}> <div className={styles.userInfo}>
<div className={styles.userInfo}> <div className={styles.userInfoContent}>
<Space size="middle"> <div className={styles.avatarWrapper}>
<Avatar <Avatar
src={userInfo.avatar || undefined} src={userInfo.avatar || undefined}
size={44} size={56}
icon={<UserOutlined />} icon={<UserOutlined />}
className={styles.userAvatar}
/> />
<div className={styles.userDetails}> <div className={styles.avatarBadge}>
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text> {userInfo.user_type ? (
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text> <CheckCircleOutlined />
<Badge ) : (
color={userInfo.user_type ? '#52c41a' : '#ff4d4f'} <SettingOutlined />
text={ )}
<Text className={styles.userType}>
{getUserTypeText(userInfo.user_type)}
</Text>
}
/>
</div> </div>
<DownOutlined style={{ fontSize: '12px', color: '#8c8c8c' }} /> </div>
</Space> <div className={styles.userDetails}>
<Text strong className={styles.userNickname}>{userInfo.nickname}</Text>
<Text type="secondary" className={styles.userUsername}>@{userInfo.username}</Text>
<div className={styles.userTypeBadge}>
<IdcardOutlined className={styles.badgeIcon} />
<Text className={styles.userTypeText}>
{getUserTypeText(userInfo.user_type)}
</Text>
</div>
</div>
<Dropdown menu={{ items: userMenuItems }} trigger={['click']} placement="bottomRight">
<div className={styles.dropdownTrigger}>
<MoreOutlined className={styles.dropdownIcon} />
</div>
</Dropdown>
</div> </div>
</Dropdown> <div className={styles.userStatsRow}>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.total_answers}</div>
<div className={styles.statLabel}></div>
</div>
<div className={styles.statDivider}></div>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.answered_questions}</div>
<div className={styles.statLabel}></div>
</div>
<div className={styles.statDivider}></div>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.wrong_questions}</div>
<div className={styles.statLabel}></div>
</div>
<div className={styles.statDivider}></div>
<div className={styles.statItem}>
<div className={styles.statValue}>{statistics.accuracy.toFixed(0)}%</div>
<div className={styles.statLabel}></div>
</div>
</div>
</div>
)} )}
</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}> <div className={styles.typeSection}>
<Title level={4} className={styles.sectionTitle}> <Title level={4} className={styles.sectionTitle}>
@ -448,8 +451,16 @@ const Home: React.FC = () => {
} }
}} }}
> >
<div className={styles.typeIcon} style={{ color: type.color, fontSize: '40px' }}> <div
{type.icon} className={styles.typeIconWrapper}
style={{
background: `linear-gradient(135deg, ${type.color}15 0%, ${type.color}08 100%)`,
borderColor: `${type.color}30`
}}
>
<div className={styles.typeIcon} style={{ color: type.color }}>
{type.icon}
</div>
</div> </div>
<Title level={5} className={styles.typeTitle}>{type.title}</Title> <Title level={5} className={styles.typeTitle}>{type.title}</Title>
<Paragraph type="secondary" className={styles.typeDesc}>{type.description}</Paragraph> <Paragraph type="secondary" className={styles.typeDesc}>{type.description}</Paragraph>
@ -472,8 +483,14 @@ const Home: React.FC = () => {
onClick={() => navigate('/wrong-questions')} onClick={() => navigate('/wrong-questions')}
> >
<Space align="center" size="middle" style={{ width: '100%' }}> <Space align="center" size="middle" style={{ width: '100%' }}>
<div className={styles.quickIcon}> <div
<BookOutlined style={{ fontSize: '32px', color: '#ff4d4f' }} /> className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #fff1f0 0%, #ffe7e6 100%)',
borderColor: '#ffccc7'
}}
>
<BookOutlined className={styles.quickIcon} style={{ color: '#ff4d4f' }} />
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title> <Title level={5} style={{ margin: 0 }}></Title>
@ -490,8 +507,14 @@ const Home: React.FC = () => {
onClick={() => navigate('/question-list')} onClick={() => navigate('/question-list')}
> >
<Space align="center" size="middle" style={{ width: '100%' }}> <Space align="center" size="middle" style={{ width: '100%' }}>
<div className={styles.quickIcon}> <div
<ListOutlined style={{ fontSize: '32px', color: '#1677ff' }} /> className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #e6f7ff 0%, #d6f0ff 100%)',
borderColor: '#91caff'
}}
>
<ListOutlined className={styles.quickIcon} style={{ color: '#1890ff' }} />
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title> <Title level={5} style={{ margin: 0 }}></Title>
@ -510,8 +533,14 @@ const Home: React.FC = () => {
onClick={() => navigate('/question-management')} onClick={() => navigate('/question-management')}
> >
<Space align="center" size="middle" style={{ width: '100%' }}> <Space align="center" size="middle" style={{ width: '100%' }}>
<div className={styles.quickIcon}> <div
<SettingOutlined style={{ fontSize: '32px', color: '#13c2c2' }} /> className={styles.quickIconWrapper}
style={{
background: 'linear-gradient(135deg, #e6fffb 0%, #d6f5f0 100%)',
borderColor: '#87e8de'
}}
>
<SettingOutlined className={styles.quickIcon} style={{ color: '#36cfc9' }} />
</div> </div>
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>
<Title level={5} style={{ margin: 0 }}></Title> <Title level={5} style={{ margin: 0 }}></Title>

View File

@ -48,6 +48,7 @@ export interface Statistics {
answered_questions: number answered_questions: number
correct_answers: number correct_answers: number
wrong_questions: number wrong_questions: number
total_answers: number // 刷题次数
accuracy: number accuracy: number
} }