添加每日一练排行榜功能
- 修复 daily_exam_service.go 中的类型转换错误 - 在首页添加每日一练排行榜组件 - 显示今日每日一练的考试成绩和用时排行 - 当今日尚未生成每日一练时显示友好提示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
960f557ca4
commit
b1551e6deb
@ -75,7 +75,7 @@ func (s *DailyExamService) GenerateDailyExam() error {
|
||||
}
|
||||
|
||||
exam := models.Exam{
|
||||
UserID: firstUser.ID, // 使用第一个用户作为创建者
|
||||
UserID: uint(firstUser.ID), // 使用第一个用户作为创建者
|
||||
Title: title,
|
||||
TotalScore: int(totalScore),
|
||||
Duration: 60,
|
||||
@ -92,7 +92,7 @@ func (s *DailyExamService) GenerateDailyExam() error {
|
||||
log.Printf("成功创建每日一练试卷: ID=%d, Title=%s", exam.ID, exam.Title)
|
||||
|
||||
// 6. 分享给所有用户
|
||||
if err := s.shareToAllUsers(exam.ID, firstUser.ID); err != nil {
|
||||
if err := s.shareToAllUsers(exam.ID, uint(firstUser.ID)); err != nil {
|
||||
log.Printf("分享试卷失败: %v", err)
|
||||
// 不返回错误,因为试卷已创建成功
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
CrownOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import * as questionApi from '../api/question'
|
||||
import * as examApi from '../api/exam'
|
||||
import { fetchWithAuth } from '../utils/request'
|
||||
import type { Statistics } from '../types/question'
|
||||
import styles from './Home.module.less'
|
||||
@ -110,6 +111,23 @@ const Home: React.FC = () => {
|
||||
const [rankingType, setRankingType] = useState<'daily' | 'total'>('daily') // 排行榜类型:每日或总榜
|
||||
const [sliderPosition, setSliderPosition] = useState<'left' | 'right'>('left') // 滑块位置
|
||||
|
||||
// 每日一练排行榜状态
|
||||
const [dailyExamRanking, setDailyExamRanking] = useState<{
|
||||
exam_id?: number
|
||||
exam_title?: string
|
||||
rankings: Array<{
|
||||
user_id: number
|
||||
username: string
|
||||
nickname: string
|
||||
avatar: string
|
||||
score: number
|
||||
time_spent: number
|
||||
rank: number
|
||||
}>
|
||||
total: number
|
||||
}>({ rankings: [], total: 0 })
|
||||
const [dailyExamLoading, setDailyExamLoading] = useState(false)
|
||||
|
||||
// 答题设置状态
|
||||
const [autoNext, setAutoNext] = useState(() => {
|
||||
const saved = localStorage.getItem('autoNextEnabled')
|
||||
@ -183,6 +201,23 @@ const Home: React.FC = () => {
|
||||
setSliderPosition(type === 'daily' ? 'left' : 'right')
|
||||
}
|
||||
|
||||
// 加载每日一练排行榜
|
||||
const loadDailyExamRanking = async () => {
|
||||
setDailyExamLoading(true)
|
||||
try {
|
||||
const res = await examApi.getDailyExamRanking()
|
||||
if (res.success && res.data) {
|
||||
setDailyExamRanking(res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载每日一练排行榜失败:', error)
|
||||
// 如果失败,设置为空数据(可能是今日尚未生成)
|
||||
setDailyExamRanking({ rankings: [], total: 0 })
|
||||
} finally {
|
||||
setDailyExamLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载用户信息
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token')
|
||||
@ -206,6 +241,7 @@ const Home: React.FC = () => {
|
||||
useEffect(() => {
|
||||
loadStatistics()
|
||||
loadCurrentRanking()
|
||||
loadDailyExamRanking()
|
||||
}, [rankingType])
|
||||
|
||||
// 动态加载聊天插件(仅在首页加载)
|
||||
@ -823,6 +859,88 @@ const Home: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 每日一练排行榜 */}
|
||||
<div className={styles.rankingSection}>
|
||||
<Title level={4} className={styles.sectionTitle}>
|
||||
<CrownOutlined style={{ color: '#fa8c16' }} /> 每日一练排行榜
|
||||
</Title>
|
||||
{dailyExamLoading ? (
|
||||
<Card className={styles.rankingCard} loading={true} />
|
||||
) : dailyExamRanking.rankings.length === 0 ? (
|
||||
<Card className={styles.rankingCard}>
|
||||
<div style={{ textAlign: 'center', padding: '40px 20px', color: '#8c8c8c' }}>
|
||||
<CrownOutlined style={{ fontSize: 48, marginBottom: 16, opacity: 0.3, color: '#fa8c16' }} />
|
||||
<div>今日每日一练尚未生成</div>
|
||||
<div style={{ fontSize: 13, marginTop: 8 }}>请等待系统每天凌晨1点自动生成</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<Card className={styles.rankingCard}>
|
||||
{dailyExamRanking.exam_title && (
|
||||
<div style={{
|
||||
padding: '12px 16px',
|
||||
background: 'linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%)',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '16px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<FileTextOutlined style={{ color: '#fa8c16', fontSize: 16 }} />
|
||||
<Text strong style={{ color: '#fa8c16' }}>{dailyExamRanking.exam_title}</Text>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.rankingList}>
|
||||
{dailyExamRanking.rankings.map((user, index) => (
|
||||
<div key={user.user_id} className={styles.rankingItem}>
|
||||
<div className={styles.rankingLeft}>
|
||||
{index < 3 ? (
|
||||
<div className={`${styles.rankBadge} ${styles[`rank${index + 1}`]}`}>
|
||||
{index === 0 && <CrownOutlined />}
|
||||
{index === 1 && <CrownOutlined />}
|
||||
{index === 2 && <CrownOutlined />}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.rankNumber}>{index + 1}</div>
|
||||
)}
|
||||
<Avatar
|
||||
src={user.avatar || undefined}
|
||||
size={40}
|
||||
icon={<UserOutlined />}
|
||||
className={styles.rankAvatar}
|
||||
/>
|
||||
<div className={styles.rankUserInfo}>
|
||||
<div className={styles.rankNickname}>{user.nickname}</div>
|
||||
<div className={styles.rankUsername}>@{user.username}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rankingRight}>
|
||||
<div className={styles.rankStat}>
|
||||
<div className={styles.rankStatValue} style={{
|
||||
color: user.score >= 80 ? '#52c41a' : user.score >= 60 ? '#faad14' : '#ff4d4f',
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{user.score}
|
||||
</div>
|
||||
<div className={styles.rankStatLabel}>得分</div>
|
||||
</div>
|
||||
<div className={styles.rankDivider}></div>
|
||||
<div className={styles.rankStat}>
|
||||
<div className={styles.rankStatValue}>
|
||||
{Math.floor(user.time_spent / 60)}'
|
||||
{user.time_spent % 60 < 10 ? '0' : ''}{user.time_spent % 60}"
|
||||
</div>
|
||||
<div className={styles.rankStatLabel}>用时</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 用户类型补充模态框 */}
|
||||
<Modal
|
||||
title="请选择您的身份类型"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user