- 重构挑战列表为横向轮播,支持多进行中的挑战 - 新增挑战详情页 /challenges/[id]/index 与排行榜 /challenges/[id]/leaderboard - ChallengeProgressCard 支持小时级剩余时间显示 - 新增 ChallengeRankingItem 组件展示榜单项 - 排行榜支持分页加载、下拉刷新与错误重试 - 挑战卡片新增已结束角标与渐变遮罩 - 加入/退出挑战时展示庆祝动画与错误提示 - 统一背景渐变色与卡片阴影细节
97 lines
2.4 KiB
TypeScript
97 lines
2.4 KiB
TypeScript
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',
|
|
},
|
|
});
|