feat(challenges): 新增挑战详情页与排行榜及轮播卡片交互
- 重构挑战列表为横向轮播,支持多进行中的挑战 - 新增挑战详情页 /challenges/[id]/index 与排行榜 /challenges/[id]/leaderboard - ChallengeProgressCard 支持小时级剩余时间显示 - 新增 ChallengeRankingItem 组件展示榜单项 - 排行榜支持分页加载、下拉刷新与错误重试 - 挑战卡片新增已结束角标与渐变遮罩 - 加入/退出挑战时展示庆祝动画与错误提示 - 统一背景渐变色与卡片阴影细节
This commit is contained in:
96
components/challenges/ChallengeRankingItem.tsx
Normal file
96
components/challenges/ChallengeRankingItem.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { RankingItem } from '@/store/challengesSlice';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Image } from 'expo-image';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
type ChallengeRankingItemProps = {
|
||||
item: RankingItem;
|
||||
index: number;
|
||||
showDivider?: boolean;
|
||||
};
|
||||
|
||||
export function ChallengeRankingItem({ item, index, showDivider = false }: ChallengeRankingItemProps) {
|
||||
return (
|
||||
<View style={[styles.rankingRow, showDivider && styles.rankingRowDivider]}>
|
||||
<View style={styles.rankingOrderCircle}>
|
||||
<Text style={styles.rankingOrder}>{index + 1}</Text>
|
||||
</View>
|
||||
{item.avatar ? (
|
||||
<Image source={{ uri: item.avatar }} style={styles.rankingAvatar} cachePolicy="memory-disk" />
|
||||
) : (
|
||||
<View style={styles.rankingAvatarPlaceholder}>
|
||||
<Ionicons name="person-outline" size={20} color="#6f7ba7" />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.rankingInfo}>
|
||||
<Text style={styles.rankingName} numberOfLines={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
<Text style={styles.rankingMetric}>{item.metric}</Text>
|
||||
</View>
|
||||
{item.badge ? <Text style={styles.rankingBadge}>{item.badge}</Text> : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
rankingRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 18,
|
||||
},
|
||||
rankingRowDivider: {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderTopColor: '#E5E7FF',
|
||||
},
|
||||
rankingOrderCircle: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#EEF0FF',
|
||||
marginRight: 12,
|
||||
},
|
||||
rankingOrder: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#4F5BD5',
|
||||
},
|
||||
rankingAvatar: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
marginRight: 14,
|
||||
backgroundColor: '#EEF0FF',
|
||||
},
|
||||
rankingAvatarPlaceholder: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
marginRight: 14,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#EEF0FF',
|
||||
},
|
||||
rankingInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
rankingName: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#1c1f3a',
|
||||
},
|
||||
rankingMetric: {
|
||||
marginTop: 4,
|
||||
fontSize: 13,
|
||||
color: '#6f7ba7',
|
||||
},
|
||||
rankingBadge: {
|
||||
fontSize: 12,
|
||||
color: '#A67CFF',
|
||||
fontWeight: '700',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user