- ChallengeRankingItem 新增 unit 字段,支持按单位格式化今日进度 - FitnessRingsCard 监听圆环闭合,自动向进行中的运动挑战上报 1 次进度 - 过滤已结束挑战,确保睡眠、喝水、运动进度仅上报进行中活动 - 移除 StressMeter 调试日志与 challengesSlice 多余打印
144 lines
3.7 KiB
TypeScript
144 lines
3.7 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;
|
|
unit?: string;
|
|
};
|
|
|
|
const formatNumber = (value: number): string => {
|
|
if (Number.isInteger(value)) {
|
|
return value.toString();
|
|
}
|
|
return value.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
|
|
};
|
|
|
|
const formatMinutes = (value: number): string => {
|
|
const safeValue = Math.max(0, Math.round(value));
|
|
const hours = safeValue / 60;
|
|
return `${hours.toFixed(1)} 小时`;
|
|
};
|
|
|
|
const formatValueWithUnit = (value: number | undefined, unit?: string): string | undefined => {
|
|
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
return undefined;
|
|
}
|
|
if (unit === 'min') {
|
|
return formatMinutes(value);
|
|
}
|
|
const formatted = formatNumber(value);
|
|
return unit ? `${formatted} ${unit}` : formatted;
|
|
};
|
|
|
|
export function ChallengeRankingItem({ item, index, showDivider = false, unit }: ChallengeRankingItemProps) {
|
|
console.log('unit', unit);
|
|
|
|
const reportedLabel = formatValueWithUnit(item.todayReportedValue, unit);
|
|
const targetLabel = formatValueWithUnit(item.todayTargetValue, unit);
|
|
const progressLabel = reportedLabel && targetLabel
|
|
? `今日 ${reportedLabel} / ${targetLabel}`
|
|
: reportedLabel
|
|
? `今日 ${reportedLabel}`
|
|
: targetLabel
|
|
? `今日目标 ${targetLabel}`
|
|
: undefined;
|
|
|
|
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>
|
|
{progressLabel ? (
|
|
<Text style={styles.rankingProgress} numberOfLines={1}>
|
|
{progressLabel}
|
|
</Text>
|
|
) : null}
|
|
</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: 12,
|
|
color: '#6f7ba7',
|
|
},
|
|
rankingProgress: {
|
|
marginTop: 2,
|
|
fontSize: 10,
|
|
color: '#8a94c1',
|
|
},
|
|
rankingBadge: {
|
|
fontSize: 12,
|
|
color: '#A67CFF',
|
|
fontWeight: '700',
|
|
},
|
|
});
|