diff --git a/internal/handlers/practice_handler.go b/internal/handlers/practice_handler.go
index d686ce5..4912026 100644
--- a/internal/handlers/practice_handler.go
+++ b/internal/handlers/practice_handler.go
@@ -661,23 +661,25 @@ func GetStatistics(c *gin.Context) {
Where("user_id = ?", uid).
Count(&wrongQuestions)
- // 计算正确率
+ // 计算正确率和总答题数
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
}
- stats := models.UserStatistics{
- TotalQuestions: int(totalQuestions),
- AnsweredQuestions: int(answeredQuestions),
- CorrectAnswers: int(correctAnswers),
- WrongQuestions: int(wrongQuestions),
- Accuracy: accuracy,
+ stats := gin.H{
+ "total_questions": int(totalQuestions),
+ "answered_questions": int(answeredQuestions),
+ "correct_answers": int(correctAnswers),
+ "wrong_questions": int(wrongQuestions),
+ "total_answers": int(totalAnswers), // 刷题次数
+ "accuracy": accuracy,
}
c.JSON(http.StatusOK, gin.H{
diff --git a/internal/services/ai_grading.go b/internal/services/ai_grading.go
index 0311639..3d04beb 100644
--- a/internal/services/ai_grading.go
+++ b/internal/services/ai_grading.go
@@ -36,12 +36,12 @@ func NewAIGradingService() *AIGradingService {
// AIGradingResult AI评分结果
type AIGradingResult struct {
- Score float64 `json:"score"` // 得分 (0-100)
- IsCorrect bool `json:"is_correct"` // 是否正确 (Score >= 60 视为正确)
- Feedback string `json:"feedback"` // 评语
- Suggestion string `json:"suggestion"` // 改进建议
- ReferenceAnswer string `json:"reference_answer"` // 参考答案(论述题)
- ScoringRationale string `json:"scoring_rationale"` // 评分依据
+ Score float64 `json:"score"` // 得分 (0-100)
+ IsCorrect bool `json:"is_correct"` // 是否正确 (Score >= 60 视为正确)
+ Feedback string `json:"feedback"` // 评语
+ Suggestion string `json:"suggestion"` // 改进建议
+ ReferenceAnswer string `json:"reference_answer"` // 参考答案(论述题)
+ ScoringRationale string `json:"scoring_rationale"` // 评分依据
}
// GradeEssay 对论述题进行AI评分(不需要标准答案)
@@ -275,7 +275,6 @@ func (s *AIGradingService) ExplainQuestionStream(writer http.ResponseWriter, que
1. **必须基于保密法规**:解析时必须引用相关法规条文,说明依据哪些具体法律法规
2. **必须实事求是**:只基于题目内容、标准答案和实际法规进行解析
3. **不要胡编乱造**:如果某些信息不确定或题目没有提供,请如实说明,不要编造法规条文
-4. **使用Markdown格式**:使用标题、列表、加粗等markdown语法使内容更清晰易读
解析内容要求:
- **知识点**:说明题目考查的核心知识点,指出涉及哪些保密法规
diff --git a/pkg/config/config.go b/pkg/config/config.go
index fa3d2d5..b192a7b 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -78,7 +78,7 @@ func (c *DatabaseConfig) GetDSN() string {
// GetAIConfig 获取AI服务配置
// 优先使用环境变量,如果没有设置则使用默认值
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")
model := getEnv("AI_MODEL", "deepseek-v3")
diff --git a/web/src/pages/Home.module.less b/web/src/pages/Home.module.less
index 12a03b0..ff49900 100644
--- a/web/src/pages/Home.module.less
+++ b/web/src/pages/Home.module.less
@@ -8,7 +8,7 @@
.header {
display: flex;
justify-content: space-between;
- align-items: flex-start;
+ align-items: center;
margin-bottom: 24px;
.headerLeft {
@@ -29,62 +29,196 @@
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.08));
}
+ .titleRow {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 4px;
+ }
+
.title {
color: #1d1d1f !important;
- margin-bottom: 4px !important;
+ margin-bottom: 0 !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
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 {
color: #6e6e73;
font-size: 14px;
}
.userInfo {
- background: rgba(255, 255, 255, 0.85);
- padding: 12px 16px;
+ background: #fff;
+ padding: 20px;
border-radius: 16px;
- backdrop-filter: blur(30px) saturate(180%);
- -webkit-backdrop-filter: blur(30px) saturate(180%);
- box-shadow:
- 0 2px 8px rgba(0, 0, 0, 0.04),
- 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);
+ 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: box-shadow 0.2s ease;
+ min-width: 520px;
- &:hover {
- background: rgba(255, 255, 255, 0.95);
- box-shadow:
- 0 4px 12px rgba(0, 0, 0, 0.06),
- 0 2px 6px rgba(0, 0, 0, 0.04),
- 0 0 0 1px rgba(0, 0, 0, 0.04);
- transform: translateY(-2px);
+ .userInfoContent {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 16px;
+ padding-bottom: 16px;
+ 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 {
display: flex;
flex-direction: column;
- gap: 3px;
+ gap: 4px;
+ flex: 1;
+ min-width: 0;
.userNickname {
color: #1d1d1f !important;
- font-size: 15px;
+ font-size: 16px;
font-weight: 600;
- line-height: 1.2;
+ line-height: 1.3;
}
.userUsername {
color: #6e6e73 !important;
- font-size: 12px;
- line-height: 1.2;
+ font-size: 13px;
+ line-height: 1.3;
}
- .userType {
- font-size: 11px;
- color: #6e6e73;
+ .userTypeBadge {
+ display: inline-flex;
+ 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;
font-weight: 700;
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);
}
+ .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 {
- margin-bottom: 10px;
+ font-size: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
.typeTitle {
@@ -173,6 +399,13 @@
gap: 8px;
font-weight: 700;
font-size: 18px !important;
+
+ :global {
+ .anticon {
+ font-size: 20px;
+ color: #73d13d;
+ }
+ }
}
.quickCard {
@@ -197,6 +430,22 @@
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)
@@ -224,27 +473,104 @@
height: 48px;
}
+ .titleRow {
+ gap: 8px;
+ flex-wrap: wrap;
+ }
+
.title {
font-size: 22px !important;
}
+ .totalBadge {
+ padding: 3px 8px;
+ font-size: 12px;
+ border-radius: 16px;
+ }
+
.subtitle {
font-size: 13px;
}
.userInfo {
width: 100%;
- padding: 8px 12px;
+ min-width: auto;
+ padding: 16px;
+ border-radius: 12px;
- :global {
- .ant-space {
- width: 100%;
- justify-content: space-between;
+ .userInfoContent {
+ gap: 12px;
+ margin-bottom: 12px;
+ padding-bottom: 12px;
+ }
+
+ .avatarWrapper {
+ .userAvatar {
+ width: 48px !important;
+ height: 48px !important;
}
- .ant-btn {
- font-size: 12px;
- padding: 4px 8px;
+ .avatarBadge {
+ width: 20px;
+ 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 {
border-radius: 12px;
+ .typeIconWrapper {
+ width: 56px;
+ height: 56px;
+ border-radius: 12px;
+ margin-bottom: 10px;
+ }
+
.typeIcon {
- font-size: 32px !important;
+ font-size: 28px;
}
.typeTitle {
@@ -312,6 +686,16 @@
}
}
}
+
+ .quickIconWrapper {
+ width: 48px;
+ height: 48px;
+ border-radius: 12px;
+ }
+
+ .quickIcon {
+ font-size: 24px;
+ }
}
}
@@ -326,11 +710,38 @@
.title {
font-size: 28px !important;
}
+
+ .userInfo {
+ min-width: 450px;
+
+ .avatarWrapper .userAvatar {
+ width: 52px !important;
+ height: 52px !important;
+ }
+
+ .userStatsRow .statItem {
+ .statValue {
+ font-size: 17px;
+ }
+ }
+ }
}
.typeCard {
+ .typeIconWrapper {
+ width: 60px;
+ height: 60px;
+ border-radius: 14px;
+ }
+
.typeIcon {
- font-size: 36px !important;
+ font-size: 30px;
+ }
+ }
+
+ .progressSection {
+ .sectionTitle {
+ font-size: 17px !important;
}
}
}
@@ -354,6 +765,47 @@
.subtitle {
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 {
@@ -368,6 +820,14 @@
}
}
+ .progressSection {
+ margin-bottom: 32px;
+
+ .sectionTitle {
+ font-size: 20px !important;
+ }
+ }
+
.quickStart {
.sectionTitle {
font-size: 20px !important;
diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx
index 68d34fd..0d61554 100644
--- a/web/src/pages/Home.tsx
+++ b/web/src/pages/Home.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
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 {
FileTextOutlined,
@@ -15,7 +15,10 @@ import {
UnorderedListOutlined as ListOutlined,
LockOutlined,
IdcardOutlined,
- DownOutlined,
+ MoreOutlined,
+ CloseCircleOutlined,
+ FormOutlined,
+ FileMarkdownOutlined,
} from '@ant-design/icons'
import * as questionApi from '../api/question'
import { fetchWithAuth } from '../utils/request'
@@ -24,48 +27,48 @@ import styles from './Home.module.less'
const { Title, Paragraph, Text } = Typography
-// 题型配置 - 使用数据库中的实际类型
+// 题型配置 - 使用数据库中的实际类型,采用明快的配色方案
const questionTypes = [
{
key: 'multiple-choice',
title: '选择题',
icon: