Просмотр исходного кода

修复一番赏商品信息显示

zbb 3 месяцев назад
Родитель
Сommit
44f6a82e67
2 измененных файлов с 134 добавлено и 81 удалено
  1. 15 5
      app/award-detail-yfs/index.tsx
  2. 119 76
      components/award-detail-yfs/ProductListYfs.tsx

+ 15 - 5
app/award-detail-yfs/index.tsx

@@ -160,10 +160,13 @@ export default function AwardDetailYfsScreen() {
   const loadBox = async (boxNum?: string) => {
   const loadBox = async (boxNum?: string) => {
       try {
       try {
           const id = getSafePoolId();
           const id = getSafePoolId();
+          console.log(`[DEBUG-ICHIBAN] loadBox called for pool: ${id}, boxNum: ${boxNum}`);
           const res = await getBoxDetail(id, boxNum);
           const res = await getBoxDetail(id, boxNum);
+          console.log(`[DEBUG-ICHIBAN] getBoxDetail res:`, res ? `ID: ${res.number}, UsedStatKeys: ${Object.keys(res.usedStat || {})}` : 'null');
+          
           if (res) setCurrentBox(res);
           if (res) setCurrentBox(res);
       } catch (error) {
       } catch (error) {
-          console.error('Failed to load box', error);
+          console.error('[DEBUG-ICHIBAN] Failed to load box', error);
       }
       }
   };
   };
 
 
@@ -171,14 +174,20 @@ export default function AwardDetailYfsScreen() {
     if (!poolId) return;
     if (!poolId) return;
     setLoading(true);
     setLoading(true);
     try {
     try {
-      const safePoolId = getSafePoolId();
+      const safePoolId = getSafePoolId(); // calling getSafePoolId inside loadData too to be sure
+      console.log(`[DEBUG-ICHIBAN] loadData called for pool: ${safePoolId}`);
+      
       const res = await getPoolDetail(safePoolId);
       const res = await getPoolDetail(safePoolId);
       setData(res);
       setData(res);
+      console.log(`[DEBUG-ICHIBAN] Pool detail loaded, calling loadBox...`);
       await loadBox(); // Initial box
       await loadBox(); // Initial box
     } catch (error) {
     } catch (error) {
-      console.error('Failed to load detail:', error);
+      console.error('[DEBUG-ICHIBAN] Failed to load detail', error);
+    } finally {
+      setLoading(false);
+      // Assuming setRefreshing is defined elsewhere or will be added by the user
+      // setRefreshing(false); 
     }
     }
-    setLoading(false);
   }, [poolId]);
   }, [poolId]);
 
 
   useEffect(() => {
   useEffect(() => {
@@ -191,7 +200,8 @@ export default function AwardDetailYfsScreen() {
   }, [poolId]);
   }, [poolId]);
 
 
   const handleBoxSelect = (box: any) => {
   const handleBoxSelect = (box: any) => {
-      setCurrentBox(box);
+      // Must load full detail to get usedStat
+      loadBox(box.number);
   };
   };
 
 
   const handlePrevBox = async () => {
   const handlePrevBox = async () => {

+ 119 - 76
components/award-detail-yfs/ProductListYfs.tsx

@@ -1,8 +1,7 @@
-import { LEVEL_MAP } from '@/constants/config';
 import { Images } from '@/constants/images';
 import { Images } from '@/constants/images';
-import { Image, ImageBackground } from 'expo-image';
+import { Image } from 'expo-image';
 import React, { useMemo } from 'react';
 import React, { useMemo } from 'react';
-import { Dimensions, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+import { Dimensions, ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
 
 
 const { width } = Dimensions.get('window');
 const { width } = Dimensions.get('window');
 
 
@@ -13,43 +12,78 @@ interface ProductListYfsProps {
   onProductClick?: (product: any) => void;
   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) {
 export default function ProductListYfs({ products = [], poolId, box, onProductClick }: ProductListYfsProps) {
   
   
   const levels = useMemo(() => {
   const levels = useMemo(() => {
-    console.log('ProductListYfs products:', products ? products.length : 'null');
+    // Group by level
     const grouped: Record<string, any[]> = { A: [], B: [], C: [], D: [] };
     const grouped: Record<string, any[]> = { A: [], B: [], C: [], D: [] };
     products.forEach(p => {
     products.forEach(p => {
-        console.log('Processing item level:', p.level);
-        if (grouped[p.level]) {
-            grouped[p.level].push(p);
-        } else {
-            console.log('Unknown level:', p.level);
+        const level = p.level || 'D';
+        if (grouped[level]) {
+            grouped[level].push(p);
         }
         }
     });
     });
 
 
     const result = [
     const result = [
-        { level: 'A', list: grouped.A, ...LEVEL_MAP.A },
-        { level: 'B', list: grouped.B, ...LEVEL_MAP.B },
-        { level: 'C', list: grouped.C, ...LEVEL_MAP.C },
-        { level: 'D', list: grouped.D, ...LEVEL_MAP.D },
+        { 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);
     ].filter(g => g.list && g.list.length > 0);
 
 
-    console.log('ProductListYfs levels:', result.length);
     return result;
     return result;
   }, [products]);
   }, [products]);
 
 
   const getLeftNum = (item: any) => {
   const getLeftNum = (item: any) => {
-      if (!box || !box.usedStat || !box.usedStat[item.spu.id]) {
-          return item.quantity;
+      // 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 - (box.usedStat[item.spu.id].quantity || 0);
+      return item.quantity;
   };
   };
 
 
   const getProbability = (item: any) => {
   const getProbability = (item: any) => {
-      if (!box || !box.leftQuantity) return '0%';
+      if (!box || !box.leftQuantity) return '0';
       const left = getLeftNum(item);
       const left = getLeftNum(item);
       const prob = (left / box.leftQuantity * 100).toFixed(4);
       const prob = (left / box.leftQuantity * 100).toFixed(4);
-      return parseFloat(prob) === 0 ? '0%' : `${prob}%`;
+      return parseFloat(prob) === 0 ? '0' : prob;
   };
   };
 
 
   const getLevelProbability = (level: string) => {
   const getLevelProbability = (level: string) => {
@@ -66,50 +100,51 @@ export default function ProductListYfs({ products = [], poolId, box, onProductCl
 
 
   return (
   return (
     <View style={styles.container}>
     <View style={styles.container}>
-      {levels.map((levelItem) => (
-        <View key={levelItem.level} style={styles.levelGroup}>
-             {/* Header with Title and Probability */}
-             <View style={styles.levelHeader}>
-                 <Text style={[styles.levelTitle, { color: levelItem.color }]}>{levelItem.title}</Text>
-                 <Text style={styles.levelProb}>{getLevelProbability(levelItem.level)}</Text>
-                 
-                 {/* The legacy code used borders and backgrounds for titles, but for now text color + shadow mimics the look well enough or we can add ImageBackground if needed. 
-                     Legacy: :style="{ color: LEVEL_MAP[levelItem.level].color }" and centered. 
-                 */}
-             </View>
-             
-             {/* List Container - Legacy uses levelBoxBg for EACH item container? 
-                 Legacy: 
-                 <view class="levelListScroll"> ... <scroll-view ...>
-                 <view ... class="item" :style="{ backgroundImage: 'url(' + ossurl.box.detail.levelBoxBg + ')' }">
-             */}
-             <View style={styles.listWrapper}>
+      {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}>
                 <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.scrollContent}>
                     {levelItem.list.map((item, index) => (
                     {levelItem.list.map((item, index) => (
                         <TouchableOpacity 
                         <TouchableOpacity 
                             key={index} 
                             key={index} 
                             style={styles.itemContainer}
                             style={styles.itemContainer}
                             onPress={() => onProductClick && onProductClick(item)}
                             onPress={() => onProductClick && onProductClick(item)}
+                            activeOpacity={0.8}
                         >
                         >
-                            {/* Item Background (levelBoxBg) */}
                             <ImageBackground 
                             <ImageBackground 
                                 source={{ uri: Images.box.detail.levelBoxBg }} 
                                 source={{ uri: Images.box.detail.levelBoxBg }} 
                                 style={styles.itemBg}
                                 style={styles.itemBg}
                                 resizeMode="stretch"
                                 resizeMode="stretch"
                             >
                             >
+                                {/* Cover Image */}
                                 <Image source={{ uri: item.spu.cover }} style={styles.itemImage} contentFit="contain" />
                                 <Image source={{ uri: item.spu.cover }} style={styles.itemImage} contentFit="contain" />
                                 
                                 
-                                {/* Info Box (productItemA/B/C/D) */}
+                                {/* Bottom Banner (Probability + Quantity Badge) */}
                                 <ImageBackground 
                                 <ImageBackground 
-                                    source={{ uri: levelItem.productItem }} 
+                                    source={{ uri: config.productItem }} 
                                     style={styles.textBox}
                                     style={styles.textBox}
                                     resizeMode="stretch"
                                     resizeMode="stretch"
                                 >
                                 >
+                                    {/* Probability centered */}
                                     <View style={styles.probTag}>
                                     <View style={styles.probTag}>
-                                        <Text style={[styles.probLabel, { color: levelItem.color }]}>概率:</Text>
-                                        <Text style={styles.probValue}>{getProbability(item)}</Text>
+                                        <Text style={styles.probLabel}>概率:</Text>
+                                        <Text style={styles.probValue}>{getProbability(item)}%</Text>
                                     </View>
                                     </View>
-                                    <View style={[styles.countTag, { backgroundColor: levelItem.color }]}>
+
+                                    {/* 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>
                                         <Text style={styles.countText}>{getLeftNum(item)}/{item.quantity}</Text>
                                     </View>
                                     </View>
                                 </ImageBackground>
                                 </ImageBackground>
@@ -117,28 +152,30 @@ export default function ProductListYfs({ products = [], poolId, box, onProductCl
                         </TouchableOpacity>
                         </TouchableOpacity>
                     ))}
                     ))}
                 </ScrollView>
                 </ScrollView>
-             </View>
-        </View>
-      ))}
+            </View>
+        );
+      })}
     </View>
     </View>
   );
   );
 }
 }
 
 
 const styles = StyleSheet.create({
 const styles = StyleSheet.create({
+  // ... (previous styles)
+  // Only changing countTag and related if needed
   container: {
   container: {
     paddingHorizontal: 10,
     paddingHorizontal: 10,
-    paddingVertical: 10,
+    marginTop: 0, 
   },
   },
   levelGroup: {
   levelGroup: {
-    marginBottom: 5,
+    marginBottom: 20,
   },
   },
   levelHeader: {
   levelHeader: {
     flexDirection: 'row',
     flexDirection: 'row',
     justifyContent: 'center',
     justifyContent: 'center',
     alignItems: 'center',
     alignItems: 'center',
-    marginBottom: 5,
+    marginBottom: 10,
     position: 'relative',
     position: 'relative',
-    height: 40,
+    height: 30,
   },
   },
   levelTitle: {
   levelTitle: {
     fontSize: 18,
     fontSize: 18,
@@ -147,71 +184,77 @@ const styles = StyleSheet.create({
     textShadowOffset: { width: 1, height: 1 },
     textShadowOffset: { width: 1, height: 1 },
     textShadowRadius: 1,
     textShadowRadius: 1,
   },
   },
-  levelProb: {
+  levelProportion: {
     position: 'absolute',
     position: 'absolute',
-    right: 15,
+    right: 0,
     bottom: 5,
     bottom: 5,
-    color: '#fff',
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  probabilityLabel: {
     fontSize: 12,
     fontSize: 12,
-    fontFamily: 'System', // Prevent potential font missing issues
+    color: '#ffc901',
   },
   },
-  listWrapper: {
-      paddingBottom: 10,
+  probabilityValue: {
+    fontSize: 12,
+    color: '#fff',
   },
   },
   scrollContent: {
   scrollContent: {
       paddingRight: 10,
       paddingRight: 10,
   },
   },
   itemContainer: {
   itemContainer: {
-    width: 88, // 175rpx / 2
-    height: 110, // 220rpx / 2
-    marginRight: 8,
+    width: 88, 
+    height: 110,
+    marginRight: 10,
   },
   },
   itemBg: {
   itemBg: {
       width: '100%',
       width: '100%',
       height: '100%',
       height: '100%',
-      position: 'relative',
+      alignItems: 'center',
   },
   },
   itemImage: {
   itemImage: {
       width: 88,
       width: 88,
       height: 90,
       height: 90,
-      position: 'absolute',
-      top: 0,
-      left: 0,
-      zIndex: 1,
   },
   },
   textBox: {
   textBox: {
-      width: '100%',
-      height: 53, // 106rpx / 2
-      position: 'absolute',
-      bottom: 0, // Adjusted from top: -30rpx legacy logic which was weird, simplified for RN
-      zIndex: 2,
-      justifyContent: 'flex-start',
-      paddingTop: 18, // Push content down below the curve
+      width: 88,
+      height: 53, 
+      marginTop: -18,
+      justifyContent: 'center',
+      paddingTop: 12,
       alignItems: 'center',
       alignItems: 'center',
+      position: 'relative', // Ensure absolute children position relative to this
   },
   },
   probTag: {
   probTag: {
       flexDirection: 'row',
       flexDirection: 'row',
       alignItems: 'center',
       alignItems: 'center',
+      justifyContent: 'center',
   },
   },
   probLabel: {
   probLabel: {
       fontSize: 8,
       fontSize: 8,
+      color: '#fff',
       marginRight: 2,
       marginRight: 2,
   },
   },
   probValue: {
   probValue: {
-      fontSize: 8,
+      fontSize: 9,
       fontWeight: 'bold',
       fontWeight: 'bold',
-      color: '#333'
+      color: '#fff',
   },
   },
   countTag: {
   countTag: {
       position: 'absolute',
       position: 'absolute',
-      top: 0,
+      top: -6, // Shift up to overlap image (~ -15rpx)
       right: 0,
       right: 0,
       paddingHorizontal: 4,
       paddingHorizontal: 4,
+      borderTopLeftRadius: 4, 
+      borderBottomRightRadius: 4, // Maybe just border radius? Original had border-radius: 5rpx (2.5px)
       borderRadius: 2,
       borderRadius: 2,
-      zIndex: 3,
+      zIndex: 10,
+      minWidth: 30,
+      alignItems: 'center',
   },
   },
   countText: {
   countText: {
       color: '#fff',
       color: '#fff',
-      fontSize: 9, 
+      fontSize: 8, 
+      fontWeight: 'bold',
   }
   }
 });
 });