整合三个排行榜为单一可切换的排行榜界面
- 合并每日一练排行榜、今日排行榜、总排行榜到一个界面 - 添加三个可点击切换的标签页 - 优化滑块动画,支持三个位置(左、中、右) - 统一加载状态显示 - 保持各排行榜原有的数据展示和样式
This commit is contained in:
parent
fa2964e144
commit
4f7dfae855
@ -108,8 +108,8 @@ const Home: React.FC = () => {
|
|||||||
const [dailyRanking, setDailyRanking] = useState<questionApi.UserStats[]>([])
|
const [dailyRanking, setDailyRanking] = useState<questionApi.UserStats[]>([])
|
||||||
const [totalRanking, setTotalRanking] = useState<questionApi.UserStats[]>([])
|
const [totalRanking, setTotalRanking] = useState<questionApi.UserStats[]>([])
|
||||||
const [rankingLoading, setRankingLoading] = useState(false)
|
const [rankingLoading, setRankingLoading] = useState(false)
|
||||||
const [rankingType, setRankingType] = useState<'daily' | 'total'>('daily') // 排行榜类型:每日或总榜
|
const [rankingType, setRankingType] = useState<'daily-exam' | 'daily' | 'total'>('daily-exam') // 排行榜类型:每日一练、每日或总榜
|
||||||
const [sliderPosition, setSliderPosition] = useState<'left' | 'right'>('left') // 滑块位置
|
const [sliderPosition, setSliderPosition] = useState<'left' | 'center' | 'right'>('left') // 滑块位置
|
||||||
|
|
||||||
// 每日一练排行榜状态
|
// 每日一练排行榜状态
|
||||||
const [dailyExamRanking, setDailyExamRanking] = useState<{
|
const [dailyExamRanking, setDailyExamRanking] = useState<{
|
||||||
@ -188,7 +188,9 @@ const Home: React.FC = () => {
|
|||||||
|
|
||||||
// 加载当前选中的排行榜数据
|
// 加载当前选中的排行榜数据
|
||||||
const loadCurrentRanking = async () => {
|
const loadCurrentRanking = async () => {
|
||||||
if (rankingType === 'daily') {
|
if (rankingType === 'daily-exam') {
|
||||||
|
await loadDailyExamRanking()
|
||||||
|
} else if (rankingType === 'daily') {
|
||||||
await loadDailyRanking()
|
await loadDailyRanking()
|
||||||
} else {
|
} else {
|
||||||
await loadTotalRanking()
|
await loadTotalRanking()
|
||||||
@ -196,9 +198,15 @@ const Home: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 切换排行榜类型
|
// 切换排行榜类型
|
||||||
const switchRankingType = (type: 'daily' | 'total') => {
|
const switchRankingType = (type: 'daily-exam' | 'daily' | 'total') => {
|
||||||
setRankingType(type)
|
setRankingType(type)
|
||||||
setSliderPosition(type === 'daily' ? 'left' : 'right')
|
if (type === 'daily-exam') {
|
||||||
|
setSliderPosition('left')
|
||||||
|
} else if (type === 'daily') {
|
||||||
|
setSliderPosition('center')
|
||||||
|
} else {
|
||||||
|
setSliderPosition('right')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载每日一练排行榜
|
// 加载每日一练排行榜
|
||||||
@ -241,7 +249,6 @@ const Home: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStatistics()
|
loadStatistics()
|
||||||
loadCurrentRanking()
|
loadCurrentRanking()
|
||||||
loadDailyExamRanking()
|
|
||||||
}, [rankingType])
|
}, [rankingType])
|
||||||
|
|
||||||
// 动态加载聊天插件(仅在首页加载)
|
// 动态加载聊天插件(仅在首页加载)
|
||||||
@ -724,94 +731,18 @@ const Home: React.FC = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
</div>
|
</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>
|
|
||||||
|
|
||||||
{/* 排行榜 */}
|
{/* 排行榜 */}
|
||||||
<div className={styles.rankingSection}>
|
<div className={styles.rankingSection}>
|
||||||
<Title level={4} className={styles.sectionTitle}>
|
<Title level={4} className={styles.sectionTitle}>
|
||||||
<TrophyOutlined /> 排行榜
|
<TrophyOutlined /> 排行榜
|
||||||
</Title>
|
</Title>
|
||||||
<div className={styles.rankingSwitch}>
|
<div className={styles.rankingSwitch}>
|
||||||
|
<div
|
||||||
|
className={`${styles.rankingSwitchButton} ${rankingType === 'daily-exam' ? styles.active : ''}`}
|
||||||
|
onClick={() => switchRankingType('daily-exam')}
|
||||||
|
>
|
||||||
|
每日一练
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`${styles.rankingSwitchButton} ${rankingType === 'daily' ? styles.active : ''}`}
|
className={`${styles.rankingSwitchButton} ${rankingType === 'daily' ? styles.active : ''}`}
|
||||||
onClick={() => switchRankingType('daily')}
|
onClick={() => switchRankingType('daily')}
|
||||||
@ -827,13 +758,87 @@ const Home: React.FC = () => {
|
|||||||
<div
|
<div
|
||||||
className={styles.rankingSwitchSlider}
|
className={styles.rankingSwitchSlider}
|
||||||
style={{
|
style={{
|
||||||
width: 'calc(50% - 4px)',
|
width: 'calc(33.33% - 4px)',
|
||||||
left: sliderPosition === 'left' ? '4px' : 'calc(50% + 0px)',
|
left: sliderPosition === 'left' ? '4px' : sliderPosition === 'center' ? 'calc(33.33% + 0px)' : 'calc(66.66% - 4px)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{rankingLoading ? (
|
{(rankingLoading || dailyExamLoading) ? (
|
||||||
<Card className={styles.rankingCard} loading={true} />
|
<Card className={styles.rankingCard} loading={true} />
|
||||||
|
) : rankingType === 'daily-exam' ? (
|
||||||
|
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>
|
||||||
|
)
|
||||||
) : rankingType === 'daily' ? (
|
) : rankingType === 'daily' ? (
|
||||||
dailyRanking.length === 0 ? (
|
dailyRanking.length === 0 ? (
|
||||||
<Card className={styles.rankingCard}>
|
<Card className={styles.rankingCard}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user