| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import { Images } from '@/constants/images';
- import { Image } from 'expo-image';
- import React, { useMemo } from 'react';
- import { Dimensions, ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
- const { width } = Dimensions.get('window');
- interface ProductListYfsProps {
- products: any[];
- poolId: string;
- box: any;
- onProductClick?: (product: any) => void;
- }
- // Level Configuration - reused from ProductList logic
- const LEVEL_CONFIG: Record<string, { title: string; color: string; bgColor: string; productItem: string }> = {
- A: { title: '超神款', color: '#fff', bgColor: '#FF4444', productItem: Images.box.detail.productItemA },
- B: { title: '欧皇款', color: '#fff', bgColor: '#FF9900', productItem: Images.box.detail.productItemB },
- C: { title: '隐藏款', color: '#fff', bgColor: '#9966FF', productItem: Images.box.detail.productItemC },
- D: { title: '普通款', color: '#fff', bgColor: '#00CCFF', productItem: Images.box.detail.productItemD },
- };
- export default function ProductListYfs({ products = [], poolId, box, onProductClick }: ProductListYfsProps) {
-
- const levels = useMemo(() => {
- // Group by level
- const grouped: Record<string, any[]> = { A: [], B: [], C: [], D: [] };
- products.forEach(p => {
- const level = p.level || 'D';
- if (grouped[level]) {
- grouped[level].push(p);
- }
- });
- const result = [
- { level: 'A', list: grouped.A },
- { level: 'B', list: grouped.B },
- { level: 'C', list: grouped.C },
- { level: 'D', list: grouped.D },
- ].filter(g => g.list && g.list.length > 0);
- return result;
- }, [products]);
- const getLeftNum = (item: any) => {
- // Robust check for box and usedStat (camelCase or snake_case)
- const usedStat = box?.usedStat || box?.used_stat;
- if (!box || !usedStat) {
- return item.quantity;
- }
-
- // Try multiple key variations for robustness
- const spuId = String(item.spu?.id || item.spu_id);
- const itemId = String(item.id);
- let used: any = null;
- // Check if usedStat is an Array (based on logs showing keys 0,1,2...)
- if (Array.isArray(usedStat)) {
- // Debug log to see structure of array items once
- if (!global.hasLoggedUsedStatStructure) {
- console.log('[DEBUG-ICHIBAN] usedStat is Array. First item:', usedStat[0]);
- global.hasLoggedUsedStatStructure = true;
- }
- // Search in array
- used = usedStat.find((u: any) => {
- const uSpuId = String(u.spuId || u.spu_id || u.id);
- return uSpuId === spuId || uSpuId === itemId;
- });
- } else {
- // Object lookup
- used = usedStat[spuId] || usedStat[itemId] || (item.spu?.id && usedStat[item.spu.id]);
- }
-
- if (used) {
- return item.quantity - (used.quantity || 0);
- }
- return item.quantity;
- };
- const getProbability = (item: any) => {
- if (!box || !box.leftQuantity) return '0';
- const left = getLeftNum(item);
- const prob = (left / box.leftQuantity * 100).toFixed(4);
- return parseFloat(prob) === 0 ? '0' : prob;
- };
- const getLevelProbability = (level: string) => {
- if (!box || !box.leftQuantity) return '0%';
- let sumLeft = 0;
- products.filter(p => p.level === level).forEach(p => {
- sumLeft += getLeftNum(p);
- });
- const prob = (sumLeft / box.leftQuantity * 100).toFixed(4);
- return parseFloat(prob) === 0 ? '0%' : `${prob}%`;
- };
- if (!products || products.length === 0) return null;
- return (
- <View style={styles.container}>
- {levels.map((levelItem) => {
- const config = LEVEL_CONFIG[levelItem.level] || LEVEL_CONFIG['D'];
-
- return (
- <View key={levelItem.level} style={styles.levelGroup}>
- {/* Level Title Row */}
- <View style={styles.levelHeader}>
- <Text style={[styles.levelTitle, { color: config.bgColor }]}>{config.title}</Text>
- <View style={styles.levelProportion}>
- <Text style={styles.probabilityLabel}>概率:</Text>
- <Text style={styles.probabilityValue}>{getLevelProbability(levelItem.level)}</Text>
- </View>
- </View>
- {/* Horizontal List */}
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
- {levelItem.list.map((item, index) => (
- <TouchableOpacity
- key={index}
- style={styles.itemContainer}
- onPress={() => onProductClick && onProductClick(item)}
- activeOpacity={0.8}
- >
- <ImageBackground
- source={{ uri: Images.box.detail.levelBoxBg }}
- style={styles.itemBg}
- resizeMode="stretch"
- >
- {/* Cover Image */}
- <Image source={{ uri: item.spu.cover }} style={styles.itemImage} contentFit="contain" />
-
- {/* Bottom Banner (Probability + Quantity Badge) */}
- <ImageBackground
- source={{ uri: config.productItem }}
- style={styles.textBox}
- resizeMode="stretch"
- >
- {/* Probability centered */}
- <View style={styles.probTag}>
- <Text style={styles.probLabel}>概率:</Text>
- <Text style={styles.probValue}>{getProbability(item)}%</Text>
- </View>
- {/* Quantity Tag (positioned top right of the banner, overlapping image) */}
- <View style={[styles.countTag, { backgroundColor: config.bgColor }]}>
- <Text style={styles.countText}>{getLeftNum(item)}/{item.quantity}</Text>
- </View>
- </ImageBackground>
- </ImageBackground>
- </TouchableOpacity>
- ))}
- </ScrollView>
- </View>
- );
- })}
- </View>
- );
- }
- const styles = StyleSheet.create({
- // ... (previous styles)
- // Only changing countTag and related if needed
- container: {
- paddingHorizontal: 10,
- marginTop: 0,
- },
- levelGroup: {
- marginBottom: 20,
- },
- levelHeader: {
- flexDirection: 'row',
- justifyContent: 'center',
- alignItems: 'center',
- marginBottom: 10,
- position: 'relative',
- height: 30,
- },
- levelTitle: {
- fontSize: 18,
- fontWeight: 'bold',
- textShadowColor: '#000',
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 1,
- },
- levelProportion: {
- position: 'absolute',
- right: 0,
- bottom: 5,
- flexDirection: 'row',
- alignItems: 'center',
- },
- probabilityLabel: {
- fontSize: 12,
- color: '#ffc901',
- },
- probabilityValue: {
- fontSize: 12,
- color: '#fff',
- },
- scrollContent: {
- paddingRight: 10,
- },
- itemContainer: {
- width: 88,
- height: 110,
- marginRight: 10,
- },
- itemBg: {
- width: '100%',
- height: '100%',
- alignItems: 'center',
- },
- itemImage: {
- width: 88,
- height: 90,
- },
- textBox: {
- width: 88,
- height: 53,
- marginTop: -18,
- justifyContent: 'center',
- paddingTop: 12,
- alignItems: 'center',
- position: 'relative', // Ensure absolute children position relative to this
- },
- probTag: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- },
- probLabel: {
- fontSize: 8,
- color: '#fff',
- marginRight: 2,
- },
- probValue: {
- fontSize: 9,
- fontWeight: 'bold',
- color: '#fff',
- },
- countTag: {
- position: 'absolute',
- top: -6, // Shift up to overlap image (~ -15rpx)
- right: 0,
- paddingHorizontal: 4,
- borderTopLeftRadius: 4,
- borderBottomRightRadius: 4, // Maybe just border radius? Original had border-radius: 5rpx (2.5px)
- borderRadius: 2,
- zIndex: 10,
- minWidth: 30,
- alignItems: 'center',
- },
- countText: {
- color: '#fff',
- fontSize: 8,
- fontWeight: 'bold',
- }
- });
|