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

Merge branch 'master' into master-已接入支付宝

# Conflicts:
#	app/award-detail/components/CheckoutModal.tsx
#	package.json
#	services/base.ts
zbb 2 месяцев назад
Родитель
Сommit
cea5b4383e
60 измененных файлов с 5786 добавлено и 3497 удалено
  1. 146 119
      app/(tabs)/box.tsx
  2. 98 40
      app/(tabs)/index.tsx
  3. 47 46
      app/(tabs)/mine.tsx
  4. 12 6
      app/(tabs)/welfare.tsx
  5. 5 0
      app/_layout.tsx
  6. 36 31
      app/address/edit.tsx
  7. 7 5
      app/address/index.tsx
  8. 0 420
      app/award-detail-yfs/components/BoxChooseModal.tsx
  9. 0 547
      app/award-detail-yfs/components/NumChooseModal.tsx
  10. 0 227
      app/award-detail-yfs/components/ProductListYfs.tsx
  11. 365 634
      app/award-detail-yfs/index.tsx
  12. 149 10
      app/award-detail/components/LotteryResultModal.tsx
  13. 55 49
      app/award-detail/components/ProductList.tsx
  14. 289 187
      app/award-detail/components/RecordModal.tsx
  15. 36 25
      app/award-detail/components/RuleModal.tsx
  16. 8 1
      app/award-detail/index.tsx
  17. 87 2
      app/award-detail/swipe.tsx
  18. 77 12
      app/boxInBox/components/BoxPopup.tsx
  19. 10 9
      app/boxInBox/index.tsx
  20. 46 43
      app/coupon/index.tsx
  21. 0 9
      app/integral/_layout.tsx
  22. 0 682
      app/integral/index.tsx
  23. 7 0
      app/lottery/_layout.tsx
  24. 5 5
      app/lottery/components/LotteryReel.tsx
  25. 96 18
      app/lottery/index.tsx
  26. 2 1
      app/lottery/result.tsx
  27. 276 126
      app/orders/index.tsx
  28. 524 0
      app/orders/shop.tsx
  29. 5 2
      app/product/[id].tsx
  30. 18 3
      app/profile/index.tsx
  31. 41 4
      app/store/components/CheckoutModal.tsx
  32. 275 96
      app/store/index.tsx
  33. 23 24
      app/wallet/recharge.tsx
  34. 73 41
      app/weal/components/WishRuleModal.tsx
  35. 27 4
      app/weal/detail.tsx
  36. 45 14
      app/weal/room.tsx
  37. 37 1
      app/weal/store_choose.tsx
  38. 28 21
      app/weal/wish.tsx
  39. 175 0
      components/Barrage.tsx
  40. 31 9
      components/CustomTabBar.tsx
  41. 280 0
      components/RegionPicker.tsx
  42. 428 0
      components/award-detail-yfs/BoxSelectionModal.tsx
  43. 213 0
      components/award-detail-yfs/FirstLast.tsx
  44. 472 0
      components/award-detail-yfs/NumSelectionModal.tsx
  45. 260 0
      components/award-detail-yfs/ProductListYfs.tsx
  46. 222 0
      components/award-detail-yfs/ProductSwiper.tsx
  47. 109 0
      components/award-detail-yfs/PurchaseBar.tsx
  48. 296 0
      components/award-detail-yfs/RecordModal.tsx
  49. 128 0
      components/award-detail-yfs/RuleModal.tsx
  50. 20 10
      components/home/GoodsCard.tsx
  51. 109 0
      components/mine/KefuPopup.tsx
  52. 32 0
      constants/config.ts
  53. 9 1
      constants/images.ts
  54. 6 1
      contexts/AuthContext.tsx
  55. 5 3
      services/address.ts
  56. 12 0
      services/award.ts
  57. 6 9
      services/mall.ts
  58. 10 0
      services/user.ts
  59. 7 0
      services/wallet.ts
  60. 1 0
      supermart-app

+ 146 - 119
app/(tabs)/box.tsx

@@ -1,17 +1,19 @@
+import { Barrage } from '@/components/Barrage';
 import { Image } from 'expo-image';
 import { useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import {
-  ActivityIndicator,
-  FlatList,
-  ImageBackground,
-  RefreshControl,
-  StatusBar,
-  StyleSheet,
-  Text,
-  TextInput,
-  TouchableOpacity,
-  View,
+    ActivityIndicator,
+    FlatList,
+    ImageBackground,
+    RefreshControl,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TextInput,
+    TouchableOpacity,
+    View
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
@@ -30,8 +32,96 @@ interface BarrageItem {
   id: string;
   content: string;
   nickname?: string;
+  avatar: string;
+  poolName?: string;
+  type?: string;
+  text?: string;
+  poolId?: string;
 }
 
+// Static Header Component - Memoized to prevent re-renders
+const StaticHeader = React.memo(({ barrageList }: { barrageList: BarrageItem[] }) => (
+  <View>
+    {/* 占位空间 - 给顶部搜索栏留出空间 */}
+    <View style={{ height: 53 }} />
+
+    {/* 顶部主图 - 绝对定位,叠在背景上 */}
+    <View style={styles.mainImageContainer}>
+      <Image
+        source={{ uri: Images.box.awardMainImg }}
+        style={styles.mainImage}
+        contentFit="fill"
+      />
+    </View>
+
+    {/* 占位空间 - 主图高度 */}
+    <View style={{ height: 360 }} />
+
+    {/* 弹幕区域 */}
+    {barrageList && barrageList.length > 0 && (
+      <View style={styles.barrageSection}>
+        <Barrage data={barrageList.slice(0, Math.ceil(barrageList.length / 2))} />
+        <View style={{ height: 6 }} />
+        <Barrage 
+          data={barrageList.slice(Math.ceil(barrageList.length / 2))} 
+          speed={35} 
+        />
+      </View>
+    )}
+  </View>
+));
+
+// Type Selector Component
+const TypeSelector = React.memo(({ 
+    typeIndex, 
+    priceSort, 
+    onTypeChange, 
+    onSortChange 
+}: { 
+    typeIndex: number;
+    priceSort: number; 
+    onTypeChange: (index: number) => void;
+    onSortChange: () => void;
+}) => (
+    <View style={styles.typeSection}>
+      <View style={styles.typeListContainer}>
+        <ScrollView 
+          horizontal 
+          showsHorizontalScrollIndicator={false} 
+          contentContainerStyle={styles.typeListContent}
+        >
+          {typeList.map((item, index) => (
+            <TouchableOpacity
+              key={index}
+              style={styles.typeItem}
+              onPress={() => onTypeChange(index)}
+              activeOpacity={0.7}
+            >
+              <Image
+                source={{ uri: typeIndex === index ? item.imgOn : item.img }}
+                style={styles.typeImage}
+                contentFit="fill"
+              />
+            </TouchableOpacity>
+          ))}
+        </ScrollView>
+      </View>
+      <TouchableOpacity style={styles.sortBtn} onPress={onSortChange} activeOpacity={0.7}>
+        <Image
+          source={{
+            uri: priceSort === 0
+              ? Images.box.sortAmount
+              : priceSort === 1
+              ? Images.box.sortAmountOnT
+              : Images.box.sortAmountOnB,
+          }}
+          style={styles.sortIcon}
+          contentFit="contain"
+        />
+      </TouchableOpacity>
+    </View>
+));
+
 export default function BoxScreen() {
   const router = useRouter();
   const insets = useSafeAreaInsets();
@@ -118,18 +208,18 @@ export default function BoxScreen() {
     }
   };
 
-  const handleTypeChange = (index: number) => {
+  const handleTypeChange = useCallback((index: number) => {
     setTypeIndex(index);
     setList([]);
     setCurrent(1);
     setHasMore(true);
-  };
+  }, []);
 
-  const handlePriceSort = () => {
+  const handlePriceSort = useCallback(() => {
     setPriceSort((prev) => (prev + 1) % 3);
-  };
+  }, []);
 
-  const handleItemPress = (item: PoolItem) => {
+  const handleItemPress = useCallback((item: PoolItem) => {
     // 检查商品状态
     if (item.status !== undefined && item.status !== 1) return;
     
@@ -149,9 +239,9 @@ export default function BoxScreen() {
       // 其他商品
       router.push(`/product/${item.id}` as any);
     }
-  };
+  }, [router]);
 
-  const renderItem = ({ item }: { item: PoolItem }) => (
+  const renderItem = useCallback(({ item }: { item: PoolItem }) => (
     <TouchableOpacity
       style={styles.itemContainer}
       onPress={() => handleItemPress(item)}
@@ -165,7 +255,7 @@ export default function BoxScreen() {
         <Image
           source={{ uri: item.cover }}
           style={styles.itemImage}
-          contentFit="cover"
+          contentFit="cover" 
         />
         <View style={styles.itemInfo}>
           <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
@@ -176,83 +266,21 @@ export default function BoxScreen() {
         </View>
       </ImageBackground>
     </TouchableOpacity>
-  );
-
-  const renderHeader = () => (
-    <View>
-      {/* 占位空间 - 给顶部搜索栏留出空间 */}
-      <View style={{ height: 53 }} />
-
-      {/* 顶部主图 - 绝对定位,叠在背景上 */}
-      <View style={styles.mainImageContainer}>
-        <Image
-          source={{ uri: Images.box.awardMainImg }}
-          style={styles.mainImage}
-          contentFit="contain"
-        />
-      </View>
-
-      {/* 占位空间 - 主图高度 */}
-      <View style={{ height: 300 }} />
+  ), [handleItemPress]);
 
-      {/* 弹幕区域 */}
-      {barrageList.length > 0 && (
-        <View style={styles.barrageSection}>
-          <View style={styles.barrageRow}>
-            {barrageList.slice(0, 3).map((item, index) => (
-              <View key={item.id || index} style={styles.barrageItem}>
-                <Text style={styles.barrageText} numberOfLines={1}>{item.content}</Text>
-              </View>
-            ))}
-          </View>
-          <View style={[styles.barrageRow, { marginLeft: 25 }]}>
-            {barrageList.slice(3, 6).map((item, index) => (
-              <View key={item.id || index} style={styles.barrageItem}>
-                <Text style={styles.barrageText} numberOfLines={1}>{item.content}</Text>
-              </View>
-            ))}
-          </View>
-        </View>
-      )}
-    </View>
-  );
-
-  // 分类筛选单独渲染
-  const renderTypeSection = () => (
-    <View style={styles.typeSection}>
-      <View style={styles.typeList}>
-        {typeList.map((item, index) => (
-          <TouchableOpacity
-            key={index}
-            style={styles.typeItem}
-            onPress={() => handleTypeChange(index)}
-            activeOpacity={0.7}
-          >
-            <Image
-              source={{ uri: typeIndex === index ? item.imgOn : item.img }}
-              style={styles.typeImage}
-              contentFit="contain"
-            />
-          </TouchableOpacity>
-        ))}
+  const ListHeader = useMemo(() => (
+      <View>
+          <StaticHeader barrageList={barrageList} />
+          <TypeSelector 
+            typeIndex={typeIndex} 
+            priceSort={priceSort}
+            onTypeChange={handleTypeChange} 
+            onSortChange={handlePriceSort} 
+          />
       </View>
-      <TouchableOpacity style={styles.sortBtn} onPress={handlePriceSort} activeOpacity={0.7}>
-        <Image
-          source={{
-            uri: priceSort === 0
-              ? Images.box.sortAmount
-              : priceSort === 1
-              ? Images.box.sortAmountOnT
-              : Images.box.sortAmountOnB,
-          }}
-          style={styles.sortIcon}
-          contentFit="contain"
-        />
-      </TouchableOpacity>
-    </View>
-  );
+  ), [barrageList, typeIndex, priceSort, handleTypeChange, handlePriceSort]);
 
-  const renderFooter = () => {
+  const renderFooter = useCallback(() => {
     if (!loading) return null;
     return (
       <View style={styles.footer}>
@@ -260,16 +288,16 @@ export default function BoxScreen() {
         <Text style={styles.footerText}>加载中...</Text>
       </View>
     );
-  };
+  }, [loading]);
 
-  const renderEmpty = () => {
+  const renderEmpty = useCallback(() => {
     if (loading) return null;
     return (
       <View style={styles.empty}>
         <Text style={styles.emptyText}>暂无数据</Text>
       </View>
     );
-  };
+  }, [loading]);
 
   return (
     <View style={styles.container}>
@@ -314,12 +342,7 @@ export default function BoxScreen() {
           data={list}
           renderItem={renderItem}
           keyExtractor={(item) => item.id}
-          ListHeaderComponent={() => (
-            <>
-              {renderHeader()}
-              {renderTypeSection()}
-            </>
-          )}
+          ListHeaderComponent={ListHeader}
           ListFooterComponent={renderFooter}
           ListEmptyComponent={renderEmpty}
           contentContainerStyle={styles.listContent}
@@ -409,29 +432,31 @@ const styles = StyleSheet.create({
     zIndex: 10,
     backgroundColor: 'transparent',
   },
-  typeList: {
-    flexDirection: 'row',
-    justifyContent: 'flex-start',
+  typeListContainer: {
     flex: 1,
+    marginRight: 10,
+  },
+  typeListContent: {
+    paddingRight: 10,
   },
   typeItem: {
-    width: 60,
-    height: 30,
-    marginRight: 5,
+    width: 73,
+    height: 34,
+    marginRight: 4,
   },
   typeImage: {
     width: '100%',
     height: '100%',
   },
   sortBtn: {
-    width: 40,
-    height: 30,
+    width: 38,
+    height: 38,
     alignItems: 'center',
     justifyContent: 'center',
   },
   sortIcon: {
-    width: 30,
-    height: 30,
+    width: 38,
+    height: 38,
   },
   listContent: {
     paddingHorizontal: 10,
@@ -442,20 +467,22 @@ const styles = StyleSheet.create({
   },
   itemBg: {
     width: '100%',
-    height: 210,
-    padding: 8,
+    height: 230, // Increased total height slightly for safety
+    // Remove padding to control children individually
   },
   itemImage: {
-    width: '100%',
-    height: 142,
-    borderRadius: 0,
+    width: '95%', // Very tight fit (leaving ~2.5% gap on sides = small gap)
+    height: 162, // expand height downwards
+    borderRadius: 6,
+    alignSelf: 'center',
+    marginTop: 13, // Tighter top fit (approx 1px-2px visual gap)
   },
   itemInfo: {
     flexDirection: 'row',
     justifyContent: 'space-between',
     alignItems: 'center',
-    paddingHorizontal: 15,
-    paddingTop: 15,
+    paddingHorizontal: 20, // Visual padding for text
+    paddingTop: 18, // Move text downwards
   },
   itemName: {
     flex: 1,

+ 98 - 40
app/(tabs)/index.tsx

@@ -1,19 +1,19 @@
 import { useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
 import {
-  ActivityIndicator,
-  ImageBackground,
-  RefreshControl,
-  ScrollView,
-  StatusBar,
-  StyleSheet,
-  Text,
-  View,
+    ActivityIndicator,
+    FlatList,
+    ImageBackground,
+    RefreshControl,
+    StatusBar,
+    StyleSheet,
+    Text,
+    View,
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
 import { Banner } from '@/components/home/Banner';
-import { GoodsList } from '@/components/home/GoodsList';
+import { GoodsCard } from '@/components/home/GoodsCard';
 import { IPFilter } from '@/components/home/IPFilter';
 import { QuickEntry } from '@/components/home/QuickEntry';
 import { SearchBar } from '@/components/home/SearchBar';
@@ -29,6 +29,7 @@ export default function HomeScreen() {
   const router = useRouter();
   const [refreshing, setRefreshing] = useState(false);
   const [loading, setLoading] = useState(true);
+  const [listLoading, setListLoading] = useState(false);
 
   // 数据状态
   const [goods, setGoods] = useState<GoodsItem[]>([]);
@@ -48,6 +49,7 @@ export default function HomeScreen() {
   // 加载商品列表
   const loadGoods = useCallback(async (params: GoodsListParams, append = false) => {
     try {
+      if (!append) setListLoading(true);
       const data = await getGoodsList(params);
       if (append) {
         setGoods((prev) => [...prev, ...data]);
@@ -56,6 +58,8 @@ export default function HomeScreen() {
       }
     } catch (error) {
       console.error('加载商品失败:', error);
+    } finally {
+      if (!append) setListLoading(false);
     }
   }, []);
 
@@ -125,11 +129,17 @@ export default function HomeScreen() {
   };
 
   // IP 筛选
-  const handleIPSelect = async (item: IPItem, index: number) => {
+  const handleIPSelect = (item: IPItem, index: number) => {
+    // 立即更新 UI,不等待网络请求
     setIpIndex(index);
-    const newParams = { ...searchParams, worksId: item.id, current: 1 };
-    setSearchParams(newParams);
-    await loadGoods(newParams);
+    setGoods([]); // 清空列表,给予用户切换反馈 (或者可以保留旧数据直到新数据到来,取决于需求,清空通常感觉更"快"因为有反馈)
+    
+    // 异步加载数据
+    requestAnimationFrame(async () => {
+        const newParams = { ...searchParams, worksId: item.id, current: 1 };
+        setSearchParams(newParams);
+        await loadGoods(newParams);
+    });
   };
 
   // 商品点击
@@ -137,6 +147,52 @@ export default function HomeScreen() {
     router.push(`/product/${item.id}` as any);
   };
 
+  // 列表头部组件
+  const ListHeader = useMemo(() => {
+    return (
+      <>
+        {/* 搜索栏 */}
+        <SearchBar onSearch={handleSearch} />
+
+        {/* Banner 轮播 */}
+        {banners.length > 0 && <Banner data={banners} onPress={handleBannerPress} />}
+
+        {/* 功能入口 */}
+        <View style={styles.section}>
+          {tabs.length > 0 && <QuickEntry data={tabs} onPress={handleQuickEntryPress} />}
+
+          {/* IP 分类筛选 */}
+          {ipList.length > 0 && (
+            <IPFilter data={ipList} activeIndex={ipIndex} onSelect={handleIPSelect} />
+          )}
+        </View>
+      </>
+    );
+  }, [banners, tabs, ipList, ipIndex]); // 依赖项
+
+  const renderItem = useCallback(({ item }: { item: GoodsItem }) => {
+    return <GoodsCard data={item} onPress={handleGoodsPress} />;
+  }, []);
+
+  const ListEmptyComponent = useMemo(() => {
+    if (listLoading) {
+      return (
+        <View style={styles.loadingListContainer}>
+          <ActivityIndicator size="small" color="#aaa" />
+          <Text style={styles.loadingText}>加载商品中...</Text>
+        </View>
+      );
+    }
+    if (!loading && goods.length === 0) {
+        return (
+            <View style={styles.emptyContainer}>
+                <Text style={styles.emptyText}>暂无商品</Text>
+            </View>
+        )
+    }
+    return null;
+  }, [listLoading, loading, goods.length]);
+
   if (loading) {
     return (
       <View style={styles.loadingContainer}>
@@ -154,33 +210,22 @@ export default function HomeScreen() {
         style={styles.background}
         resizeMode="cover"
       >
-        <ScrollView
-          style={styles.scrollView}
-          contentContainerStyle={{ paddingTop: insets.top + 10 }}
+        <FlatList
+          data={goods}
+          renderItem={renderItem}
+          keyExtractor={(item) => item.id}
+          ListHeaderComponent={ListHeader}
+          numColumns={2}
+          columnWrapperStyle={styles.columnWrapper}
+          contentContainerStyle={{ paddingTop: insets.top + 10, paddingBottom: 20 }}
           showsVerticalScrollIndicator={false}
           refreshControl={
             <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#fff" />
           }
-        >
-          {/* 搜索栏 */}
-          <SearchBar onSearch={handleSearch} />
-
-          {/* Banner 轮播 */}
-          {banners.length > 0 && <Banner data={banners} onPress={handleBannerPress} />}
-
-          {/* 功能入口 */}
-          <View style={styles.section}>
-            {tabs.length > 0 && <QuickEntry data={tabs} onPress={handleQuickEntryPress} />}
-
-            {/* IP 分类筛选 */}
-            {ipList.length > 0 && (
-              <IPFilter data={ipList} activeIndex={ipIndex} onSelect={handleIPSelect} />
-            )}
-          </View>
-
-          {/* 商品列表 */}
-          <GoodsList data={goods} onItemPress={handleGoodsPress} />
-        </ScrollView>
+          ListEmptyComponent={ListEmptyComponent}
+          onEndReachedThreshold={0.5}
+          // onEndReached={() => { /* Implement pagination if needed */ }}
+        />
       </ImageBackground>
     </View>
   );
@@ -194,9 +239,6 @@ const styles = StyleSheet.create({
   background: {
     flex: 1,
   },
-  scrollView: {
-    flex: 1,
-  },
   section: {
     paddingHorizontal: 10,
   },
@@ -206,9 +248,25 @@ const styles = StyleSheet.create({
     justifyContent: 'center',
     alignItems: 'center',
   },
+  loadingListContainer: {
+    padding: 20,
+    alignItems: 'center',
+  },
   loadingText: {
     color: '#fff',
     marginTop: 10,
     fontSize: 14,
   },
+  columnWrapper: {
+    justifyContent: 'space-between',
+    paddingHorizontal: 10,
+  },
+  emptyContainer: {
+    padding: 50,
+    alignItems: 'center',
+  },
+  emptyText: {
+    color: '#999',
+    fontSize: 14,
+  },
 });

+ 47 - 46
app/(tabs)/mine.tsx

@@ -1,7 +1,12 @@
+import { KefuPopup, KefuPopupRef } from '@/components/mine/KefuPopup';
+import { MenuCell } from '@/components/mine/MenuCell';
+import { Images } from '@/constants/images';
+import { getMagicIndex } from '@/services/award';
+import { getParamConfig, getUserInfo, UserInfo } from '@/services/user';
 import * as Clipboard from 'expo-clipboard';
 import { Image } from 'expo-image';
 import { useFocusEffect, useRouter } from 'expo-router';
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
 import {
     Alert,
     ImageBackground,
@@ -14,11 +19,6 @@ import {
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
-import { MenuCell } from '@/components/mine/MenuCell';
-import { Images } from '@/constants/images';
-import { getMagicIndex } from '@/services/award';
-import { getParamConfig, getUserInfo, UserInfo } from '@/services/user';
-
 interface IndexData {
   couponCount?: number;
   inventoryCount?: number;
@@ -32,9 +32,9 @@ export default function MineScreen() {
   const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
   const [indexData, setIndexData] = useState<IndexData | null>(null);
   const [inviteShow, setInviteShow] = useState(false);
-  const [showCredit, setShowCredit] = useState(false);
   const [filingInfo, setFilingInfo] = useState<{ state: number; data: string } | null>(null);
   const [showWallet, setShowWallet] = useState(false);
+  const kefuRef = useRef<KefuPopupRef>(null);
 
   const loadData = useCallback(async () => {
     try {
@@ -49,10 +49,6 @@ export default function MineScreen() {
       const inviteConfig = await getParamConfig('invite_config');
       setInviteShow(inviteConfig?.state !== 0);
 
-      // 获取积分显示配置
-      const creditConfig = await getParamConfig('credit_show');
-      setShowCredit(creditConfig?.state !== 0);
-
       // 获取备案信息
       const filingConfig = await getParamConfig('beian_icp');
       if (filingConfig) {
@@ -111,7 +107,7 @@ export default function MineScreen() {
         router.push('/exchange' as any);
         break;
       case '4_4': // 联系客服
-        Alert.alert('联系客服', '客服时间:10:00 ~ 18:00');
+        kefuRef.current?.open();
         break;
       case '4_3': // 地址
         router.push('/address' as any);
@@ -227,17 +223,15 @@ export default function MineScreen() {
             activeOpacity={0.8}
           >
             <ImageBackground
-              source={{ uri: Images.common.defaultAvatar }}
-              style={styles.avatarBox}
-              resizeMode="cover"
+              source={{ uri: Images.mine.avatarBorderBg }}
+              style={styles.avatarBorder}
+              resizeMode="contain"
             >
-              {userInfo?.avatar && (
-                <Image
-                  source={{ uri: userInfo.avatar }}
-                  style={styles.avatar}
-                  contentFit="cover"
-                />
-              )}
+              <Image
+                source={{ uri: userInfo?.avatar || Images.common.defaultAvatar }}
+                style={styles.avatar}
+                contentFit="cover"
+              />
             </ImageBackground>
             <View style={styles.userInfo}>
               <View style={styles.nicknameRow}>
@@ -258,17 +252,17 @@ export default function MineScreen() {
                 <View style={styles.idRow}>
                   <TouchableOpacity
                     style={styles.idItem}
-                    onPress={() => handleCopy(userInfo.id || '')}
+                    onPress={() => handleCopy(userInfo.username || userInfo.id || '')}
                   >
-                    <Text style={styles.idText}>ID:{userInfo.id}</Text>
+                    <Text style={styles.idText}>ID:{userInfo.username || userInfo.id}</Text>
                     <Image
                       source={{ uri: Images.mine.kaixinUserCopyIcon }}
                       style={styles.copyIcon}
                       contentFit="contain"
                     />
                   </TouchableOpacity>
-                  {userInfo.phone && (
-                    <Text style={styles.phoneText}>手机:{userInfo.phone}</Text>
+                  {(userInfo.mobile || userInfo.phone) && (
+                    <Text style={styles.phoneText}>手机:{userInfo.mobile || userInfo.phone}</Text>
                   )}
                 </View>
               ) : (
@@ -314,12 +308,6 @@ export default function MineScreen() {
                   <Text style={styles.funcText}>邀新有礼</Text>
                 </TouchableOpacity>
               )}
-              {showCredit && (
-                <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/integral')}>
-                  <Image source={{ uri: Images.mine.kaixinintegral }} style={styles.funcIcon} contentFit="contain" />
-                  <Text style={styles.funcText}>签到领积分</Text>
-                </TouchableOpacity>
-              )}
               <TouchableOpacity style={styles.funcItem} onPress={() => handleMenuPress('/message')}>
                 <Image source={{ uri: Images.mine.message }} style={styles.funcIcon} contentFit="contain" />
                 <Text style={styles.funcText}>系统消息</Text>
@@ -333,15 +321,15 @@ export default function MineScreen() {
 
           {/* 订单入口 */}
           <ImageBackground
-            source={{ uri: Images.mine.userSection2Bg }}
+            source={{ uri: Images.welfare.roomBg }}
             style={styles.orderBox}
             resizeMode="stretch"
           >
             <View style={styles.orderList}>
-              <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders?tab=1')}>
+              <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders/shop?active=1')}>
                 <Image source={{ uri: Images.mine.order1 }} style={styles.orderImage} contentFit="contain" />
               </TouchableOpacity>
-              <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders?tab=4')}>
+              <TouchableOpacity style={styles.orderItem} onPress={() => handleMenuPress('/orders/shop?active=4')}>
                 <Image source={{ uri: Images.mine.order2 }} style={styles.orderImage} contentFit="contain" />
               </TouchableOpacity>
             </View>
@@ -363,6 +351,8 @@ export default function MineScreen() {
           <View style={{ height: 120 }} />
         </ScrollView>
       </ImageBackground>
+      {/* 客服弹窗 */}
+      <KefuPopup ref={kefuRef} />
     </View>
   );
 }
@@ -403,16 +393,24 @@ const styles = StyleSheet.create({
     paddingVertical: 8,
     marginHorizontal: 8,
   },
-  avatarBox: {
+  avatarBorder: {
     width: 64,
     height: 64,
-    borderRadius: 32,
-    overflow: 'hidden',
     marginRight: 21,
+    padding: 7.5,  // 原项目 15rpx
+    justifyContent: 'center',
+    alignItems: 'center',
   },
-  avatar: {
+  avatarBox: {
     width: '100%',
     height: '100%',
+    borderRadius: 4,
+    overflow: 'hidden',
+  },
+  avatar: {
+    width: 49,
+    height: 49,
+    borderRadius: 2,
   },
   userInfo: {
     flex: 1,
@@ -448,9 +446,9 @@ const styles = StyleSheet.create({
     fontWeight: 'bold',
   },
   copyIcon: {
-    width: 11,
-    height: 11,
-    marginLeft: 5,
+    width: 14,
+    height: 14,
+    marginLeft: 6,
   },
   phoneText: {
     color: '#fff',
@@ -486,7 +484,7 @@ const styles = StyleSheet.create({
     fontSize: 12,
   },
   funcBox: {
-    height: 115,
+    height: 115, // 原项目 230rpx
     marginHorizontal: 0,
     paddingTop: 20,
     paddingHorizontal: 10,
@@ -496,15 +494,18 @@ const styles = StyleSheet.create({
     justifyContent: 'space-around',
   },
   funcItem: {
+    flex: 1,
     alignItems: 'center',
   },
   funcIcon: {
-    width: 59,
-    height: 57,
+    width: 59, // 原项目 118rpx
+    height: 57, // 原项目 114rpx
   },
   funcText: {
     color: '#000',
-    fontSize: 12,
+    fontSize: 12, // 原项目 24rpx
+    fontWeight: '400',
+    marginTop: 4,
   },
   orderBox: {
     marginHorizontal: 8,

+ 12 - 6
app/(tabs)/welfare.tsx

@@ -2,6 +2,7 @@ import { Image } from 'expo-image';
 import { useRouter } from 'expo-router';
 import React, { useCallback, useEffect, useState } from 'react';
 import {
+    Dimensions,
     ImageBackground,
     ScrollView,
     StatusBar,
@@ -85,7 +86,7 @@ export default function WelfareScreen() {
               <Image
                 source={{ uri: Images.welfare.indexItem1  }}
                 style={styles.roomImage}
-                contentFit="contain"
+                contentFit="fill"
               />
             </TouchableOpacity>
 
@@ -97,7 +98,7 @@ export default function WelfareScreen() {
               <Image
                 source={{ uri: Images.welfare.indexItem2  }}
                 style={styles.roomImage}
-                contentFit="contain"
+                contentFit="fill"
               />
             </TouchableOpacity>
 
@@ -109,7 +110,7 @@ export default function WelfareScreen() {
               <Image
                 source={{ uri: Images.welfare.indexItem3  }}
                 style={styles.roomImage}
-                contentFit="contain"
+                contentFit="fill"
               />
             </TouchableOpacity>
           </ScrollView>
@@ -122,6 +123,8 @@ export default function WelfareScreen() {
   );
 }
 
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+
 const styles = StyleSheet.create({
   container: {
     flex: 1,
@@ -176,10 +179,13 @@ const styles = StyleSheet.create({
     alignItems: 'center',
   },
   roomItem: {
-    marginBottom: 15,
+    marginBottom: 6,
+    width: SCREEN_WIDTH - 12, // Minimal margins (6px each side)
+    alignSelf: 'center',
   },
   roomImage: {
-    width: 375,
-    height: 130,
+    width: '100%',
+    height: undefined,
+    aspectRatio: 3, // Taller (345/115) to reduce horizontal stretch
   },
 });

+ 5 - 0
app/_layout.tsx

@@ -5,8 +5,12 @@ import 'react-native-reanimated';
 
 import { AuthProvider } from '@/contexts/AuthContext';
 import { useColorScheme } from '@/hooks/use-color-scheme';
+import { LogBox } from 'react-native';
 import { PaperProvider } from 'react-native-paper';
 
+// Disable all yellow box warnings
+LogBox.ignoreAllLogs(true);
+
 export const unstable_settings = {
   anchor: '(tabs)',
 };
@@ -39,6 +43,7 @@ export default function RootLayout() {
             <Stack.Screen name="agreement" options={{ headerShown: false }} />
             <Stack.Screen name="profile" options={{ headerShown: false }} />
             <Stack.Screen name="test" options={{ headerShown: false }} />
+            <Stack.Screen name="lottery" options={{ headerShown: false }} />
             <Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
           </Stack>
           <StatusBar style="auto" />

+ 36 - 31
app/address/edit.tsx

@@ -15,6 +15,7 @@ import {
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
+import { RegionPicker } from '@/components/RegionPicker';
 import { Images } from '@/constants/images';
 import { addAddress, Address, getAddressList, updateAddress } from '@/services/address';
 
@@ -33,13 +34,20 @@ export default function AddressEditScreen() {
   const [district, setDistrict] = useState('');
   const [address, setAddress] = useState('');
   const [isDefault, setIsDefault] = useState(false);
+  const [regionVisible, setRegionVisible] = useState(false);
+
+  const handleRegionSelect = (p: string, c: string, d: string) => {
+    setProvince(p);
+    setCity(c);
+    setDistrict(d);
+  };
 
   const loadData = useCallback(async () => {
     if (!id) return;
     setLoading(true);
     try {
       const list = await getAddressList();
-      const item = list.find((a: Address) => a.id === id);
+      const item = list.find((a: Address) => String(a.id) === String(id));
       if (item) {
         setContactName(item.contactName);
         setContactNo(item.contactNo);
@@ -168,36 +176,15 @@ export default function AddressEditScreen() {
               </View>
 
               <View style={styles.formItem}>
-                <Text style={styles.label}>省份</Text>
-                <TextInput
-                  style={styles.input}
-                  value={province}
-                  onChangeText={setProvince}
-                  placeholder="请输入省份"
-                  placeholderTextColor="#999"
-                />
-              </View>
-
-              <View style={styles.formItem}>
-                <Text style={styles.label}>城市</Text>
-                <TextInput
-                  style={styles.input}
-                  value={city}
-                  onChangeText={setCity}
-                  placeholder="请输入城市"
-                  placeholderTextColor="#999"
-                />
-              </View>
-
-              <View style={styles.formItem}>
-                <Text style={styles.label}>区/县</Text>
-                <TextInput
-                  style={styles.input}
-                  value={district}
-                  onChangeText={setDistrict}
-                  placeholder="请输入区/县(选填)"
-                  placeholderTextColor="#999"
-                />
+                <Text style={styles.label}>所在地区</Text>
+                <TouchableOpacity
+                  style={styles.selector}
+                  onPress={() => setRegionVisible(true)}
+                >
+                  <Text style={[styles.selectorText, !province && styles.placeholderText]}>
+                    {province ? `${province} ${city} ${district}` : '请选择省/市/区'}
+                  </Text>
+                </TouchableOpacity>
               </View>
 
               <View style={styles.formItem}>
@@ -240,6 +227,11 @@ export default function AddressEditScreen() {
           </TouchableOpacity>
         </View>
       </ImageBackground>
+      <RegionPicker
+        visible={regionVisible}
+        onClose={() => setRegionVisible(false)}
+        onSelect={handleRegionSelect}
+      />
     </View>
   );
 }
@@ -309,6 +301,19 @@ const styles = StyleSheet.create({
     color: '#333',
     fontSize: 14,
   },
+  selector: {
+    backgroundColor: 'rgba(255,255,255,0.8)',
+    borderRadius: 8,
+    paddingHorizontal: 12,
+    paddingVertical: 12,
+  },
+  selectorText: {
+    fontSize: 14,
+    color: '#333',
+  },
+  placeholderText: {
+    color: '#999',
+  },
   textArea: {
     height: 80,
     textAlignVertical: 'top',

+ 7 - 5
app/address/index.tsx

@@ -1,5 +1,5 @@
-import { useLocalSearchParams, useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router';
+import React, { useCallback, useState } from 'react';
 import {
     ActivityIndicator,
     Alert,
@@ -35,9 +35,11 @@ export default function AddressListScreen() {
     setLoading(false);
   }, []);
 
-  useEffect(() => {
-    loadData();
-  }, [loadData]);
+  useFocusEffect(
+    useCallback(() => {
+      loadData();
+    }, [loadData])
+  );
 
   // 选择地址
   const selectAddress = (item: Address) => {

+ 0 - 420
app/award-detail-yfs/components/BoxChooseModal.tsx

@@ -1,420 +0,0 @@
-import { Image } from 'expo-image';
-import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
-import {
-    ActivityIndicator,
-    ImageBackground,
-    Modal,
-    ScrollView,
-    StyleSheet,
-    Text,
-    TouchableOpacity,
-    View,
-} from 'react-native';
-
-import { Images } from '@/constants/images';
-import { getBoxList } from '@/services/award';
-
-// 等级筛选标签
-const TABS = [
-  { title: '全部', value: '' },
-  { title: '超神款', value: 'A' },
-  { title: '欧皇款', value: 'B' },
-  { title: '隐藏款', value: 'C' },
-  { title: '普通款', value: 'D' },
-];
-
-interface BoxItem {
-  number: string;
-  quantity: number;
-  leftQuantity: number;
-  quantityA: number;
-  quantityB: number;
-  quantityC: number;
-  quantityD: number;
-  leftQuantityA: number;
-  leftQuantityB: number;
-  leftQuantityC: number;
-  leftQuantityD: number;
-}
-
-interface BoxChooseModalProps {
-  poolId: string;
-  onChoose: (boxNumber: string) => void;
-}
-
-export interface BoxChooseModalRef {
-  show: () => void;
-  close: () => void;
-}
-
-export const BoxChooseModal = forwardRef<BoxChooseModalRef, BoxChooseModalProps>(
-  ({ poolId, onChoose }, ref) => {
-    const [visible, setVisible] = useState(false);
-    const [loading, setLoading] = useState(false);
-    const [currentTab, setCurrentTab] = useState(TABS[0]);
-    const [boxList, setBoxList] = useState<BoxItem[]>([]);
-    const [pageNum, setPageNum] = useState(1);
-    const [hasMore, setHasMore] = useState(true);
-
-    const loadData = useCallback(async (page: number, level?: string) => {
-      if (page === 1) setLoading(true);
-
-      try {
-        const levelValue = level === '' ? undefined : level === 'A' ? 1 : level === 'B' ? 2 : level === 'C' ? 3 : level === 'D' ? 4 : undefined;
-        const res = await getBoxList(poolId, levelValue, page, 20);
-        if (res && res.records) {
-          const newList = res.records.filter((item: BoxItem) => item.leftQuantity > 0);
-          if (page === 1) {
-            setBoxList(newList);
-          } else {
-            setBoxList(prev => [...prev, ...newList]);
-          }
-          setHasMore(res.records.length >= 20);
-          setPageNum(page);
-        }
-      } catch (error) {
-        console.error('加载盒子列表失败:', error);
-      } finally {
-        setLoading(false);
-      }
-    }, [poolId]);
-
-    useImperativeHandle(ref, () => ({
-      show: () => {
-        setVisible(true);
-        setCurrentTab(TABS[0]);
-        setPageNum(1);
-        loadData(1, '');
-      },
-      close: () => {
-        setVisible(false);
-        setBoxList([]);
-      },
-    }));
-
-    const close = () => {
-      setVisible(false);
-    };
-
-    const clickTab = (tab: typeof TABS[0]) => {
-      setCurrentTab(tab);
-      setPageNum(1);
-      loadData(1, tab.value);
-    };
-
-    const loadMore = () => {
-      if (!loading && hasMore) {
-        loadData(pageNum + 1, currentTab.value);
-      }
-    };
-
-    const choose = (item: BoxItem) => {
-      if (item.leftQuantity <= 0) return;
-      onChoose(item.number);
-      close();
-    };
-
-    const handleScroll = (event: any) => {
-      const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
-      const isCloseToBottom = layoutMeasurement.height + contentOffset.y >= contentSize.height - 50;
-      if (isCloseToBottom) {
-        loadMore();
-      }
-    };
-
-    return (
-      <Modal visible={visible} transparent animationType="slide" onRequestClose={close}>
-        <View style={styles.overlay}>
-          <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={close} />
-          <ImageBackground source={{ uri: Images.box.detail.recordBg }} style={styles.container} resizeMode="cover">
-            {/* 标题 */}
-            <View style={styles.titleSection}>
-              <Text style={styles.title}>换盒</Text>
-              <TouchableOpacity style={styles.closeBtn} onPress={close}>
-                <Text style={styles.closeText}>×</Text>
-              </TouchableOpacity>
-            </View>
-
-            {/* 标签页 */}
-            <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.tabsScroll}>
-              <View style={styles.tabs}>
-                {TABS.map((tab) => (
-                  <TouchableOpacity
-                    key={tab.value}
-                    style={[styles.tabItem, currentTab.value === tab.value && styles.tabItemActive]}
-                    onPress={() => clickTab(tab)}
-                  >
-                    <Text style={[styles.tabText, currentTab.value === tab.value && styles.tabTextActive]}>
-                      {tab.title}
-                    </Text>
-                  </TouchableOpacity>
-                ))}
-              </View>
-            </ScrollView>
-
-            {/* 盒子列表 */}
-            <ScrollView
-              style={styles.listScroll}
-              showsVerticalScrollIndicator={false}
-              onScroll={handleScroll}
-              scrollEventThrottle={16}
-            >
-              <View style={styles.listContainer}>
-                {loading && pageNum === 1 ? (
-                  <View style={styles.loadingBox}>
-                    <ActivityIndicator color="#FFC900" size="large" />
-                  </View>
-                ) : boxList.length === 0 ? (
-                  <View style={styles.emptyBox}>
-                    <Text style={styles.emptyText}>暂无可用盒子</Text>
-                  </View>
-                ) : (
-                  boxList.map((item, index) => (
-                    <TouchableOpacity
-                      key={item.number}
-                      style={styles.boxItem}
-                      onPress={() => choose(item)}
-                      activeOpacity={0.8}
-                    >
-                      <View style={styles.itemIndex}>
-                        <Text style={styles.itemIndexText}>{index + 1}</Text>
-                      </View>
-                      <View style={styles.itemContent}>
-                        <View style={styles.leftSection}>
-                          <Image
-                            source={{ uri: Images.box.detail.boxIcon }}
-                            style={styles.boxIcon}
-                            contentFit="contain"
-                          />
-                          <Text style={styles.leftText}>剩{item.leftQuantity}发</Text>
-                        </View>
-                        <View style={styles.divider} />
-                        <View style={styles.levelList}>
-                          <View style={styles.levelBox}>
-                            <Image source={{ uri: Images.box.detail.levelTextA }} style={styles.levelIcon} contentFit="contain" />
-                            <View style={styles.numBox}>
-                              <Text style={styles.currentNum}>{item.leftQuantityA}</Text>
-                              <Text style={styles.totalNum}>/{item.quantityA}</Text>
-                            </View>
-                          </View>
-                          <View style={styles.levelBox}>
-                            <Image source={{ uri: Images.box.detail.levelTextB }} style={styles.levelIcon} contentFit="contain" />
-                            <View style={styles.numBox}>
-                              <Text style={styles.currentNum}>{item.leftQuantityB}</Text>
-                              <Text style={styles.totalNum}>/{item.quantityB}</Text>
-                            </View>
-                          </View>
-                          <View style={styles.levelBox}>
-                            <Image source={{ uri: Images.box.detail.levelTextC }} style={styles.levelIcon} contentFit="contain" />
-                            <View style={styles.numBox}>
-                              <Text style={styles.currentNum}>{item.leftQuantityC}</Text>
-                              <Text style={styles.totalNum}>/{item.quantityC}</Text>
-                            </View>
-                          </View>
-                          <View style={styles.levelBox}>
-                            <Image source={{ uri: Images.box.detail.levelTextD }} style={styles.levelIcon} contentFit="contain" />
-                            <View style={styles.numBox}>
-                              <Text style={styles.currentNum}>{item.leftQuantityD}</Text>
-                              <Text style={styles.totalNum}>/{item.quantityD}</Text>
-                            </View>
-                          </View>
-                        </View>
-                      </View>
-                    </TouchableOpacity>
-                  ))
-                )}
-                {loading && pageNum > 1 && (
-                  <View style={styles.loadMoreBox}>
-                    <ActivityIndicator color="#FFC900" size="small" />
-                  </View>
-                )}
-              </View>
-            </ScrollView>
-          </ImageBackground>
-        </View>
-      </Modal>
-    );
-  }
-);
-
-
-const styles = StyleSheet.create({
-  overlay: {
-    flex: 1,
-    backgroundColor: 'rgba(0,0,0,0.5)',
-    justifyContent: 'flex-end',
-  },
-  mask: { flex: 1 },
-  container: {
-    borderTopLeftRadius: 15,
-    borderTopRightRadius: 15,
-    paddingTop: 15,
-    paddingBottom: 34,
-    maxHeight: '80%',
-  },
-  titleSection: {
-    alignItems: 'center',
-    paddingVertical: 15,
-    position: 'relative',
-  },
-  title: {
-    fontSize: 16,
-    fontWeight: 'bold',
-    color: '#fff',
-    textShadowColor: '#000',
-    textShadowOffset: { width: 1, height: 1 },
-    textShadowRadius: 2,
-  },
-  closeBtn: {
-    position: 'absolute',
-    right: 15,
-    top: 10,
-    width: 24,
-    height: 24,
-    backgroundColor: '#ebebeb',
-    borderRadius: 12,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  closeText: { fontSize: 18, color: '#a2a2a2', marginTop: -2 },
-  tabsScroll: {
-    maxHeight: 40,
-    marginHorizontal: 10,
-    marginBottom: 10,
-  },
-  tabs: {
-    flexDirection: 'row',
-  },
-  tabItem: {
-    paddingHorizontal: 12,
-    paddingVertical: 6,
-    backgroundColor: 'rgba(255,255,255,0.2)',
-    borderRadius: 15,
-    marginRight: 8,
-  },
-  tabItemActive: {
-    backgroundColor: '#FFC900',
-  },
-  tabText: {
-    fontSize: 12,
-    color: '#fff',
-  },
-  tabTextActive: {
-    color: '#000',
-    fontWeight: 'bold',
-  },
-  listScroll: {
-    height: 400,
-    marginHorizontal: 10,
-  },
-  listContainer: {
-    backgroundColor: '#f3f3f3',
-    borderWidth: 2,
-    borderColor: '#000',
-    padding: 15,
-    borderRadius: 4,
-  },
-  loadingBox: {
-    height: 200,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  emptyBox: {
-    height: 200,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  emptyText: {
-    fontSize: 14,
-    color: '#999',
-  },
-  boxItem: {
-    backgroundColor: '#fff',
-    borderWidth: 3,
-    borderColor: '#000',
-    borderRadius: 4,
-    marginBottom: 10,
-    position: 'relative',
-    shadowColor: '#FFC900',
-    shadowOffset: { width: 0, height: 3 },
-    shadowOpacity: 1,
-    shadowRadius: 0,
-    elevation: 3,
-  },
-  itemIndex: {
-    position: 'absolute',
-    left: 0,
-    top: 0,
-    width: 22,
-    height: 22,
-    borderWidth: 1.5,
-    borderColor: '#000',
-    backgroundColor: '#fff',
-    justifyContent: 'center',
-    alignItems: 'center',
-    zIndex: 1,
-  },
-  itemIndexText: {
-    fontSize: 14,
-    fontWeight: 'bold',
-    color: '#000',
-  },
-  itemContent: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    padding: 12,
-  },
-  leftSection: {
-    width: 74,
-    alignItems: 'center',
-  },
-  boxIcon: {
-    width: 24,
-    height: 24,
-  },
-  leftText: {
-    fontSize: 12,
-    color: '#000',
-    marginTop: 4,
-  },
-  divider: {
-    width: 1,
-    height: 40,
-    backgroundColor: '#dcdad3',
-    opacity: 0.5,
-    marginRight: 10,
-  },
-  levelList: {
-    flex: 1,
-    flexDirection: 'row',
-    flexWrap: 'wrap',
-  },
-  levelBox: {
-    width: '50%',
-    flexDirection: 'row',
-    alignItems: 'center',
-    marginBottom: 4,
-  },
-  levelIcon: {
-    width: 45,
-    height: 16,
-    marginRight: 4,
-  },
-  numBox: {
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  currentNum: {
-    fontSize: 13,
-    fontWeight: '500',
-    color: '#000',
-  },
-  totalNum: {
-    fontSize: 11,
-    color: '#666',
-  },
-  loadMoreBox: {
-    paddingVertical: 15,
-    alignItems: 'center',
-  },
-});

+ 0 - 547
app/award-detail-yfs/components/NumChooseModal.tsx

@@ -1,547 +0,0 @@
-import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
-import {
-    ActivityIndicator,
-    Alert,
-    Dimensions,
-    ImageBackground,
-    Modal,
-    ScrollView,
-    StyleSheet,
-    Text,
-    TouchableOpacity,
-    View,
-} from 'react-native';
-
-import { Images } from '@/constants/images';
-import { getUnavailableSeatNumbers, previewOrder } from '@/services/award';
-
-const { width: SCREEN_WIDTH } = Dimensions.get('window');
-const ITEM_WIDTH = Math.floor((SCREEN_WIDTH - 28 - 12 * 4) / 5);
-
-// 等级配置
-const LEVEL_MAP: Record<string, { title: string; color: string }> = {
-  A: { title: '超神款', color: '#FF4444' },
-  B: { title: '欧皇款', color: '#FF9600' },
-  C: { title: '隐藏款', color: '#9B59B6' },
-  D: { title: '普通款', color: '#00CCFF' },
-};
-
-interface BoxData {
-  number: string;
-  quantity: number;
-  lastNumber: number;
-}
-
-interface NumChooseModalProps {
-  poolId: string;
-  onPay: (params: { preview: any; seatNumbers: number[]; boxNumber: string }) => void;
-}
-
-export interface NumChooseModalRef {
-  show: (box: BoxData) => void;
-  close: () => void;
-}
-
-export const NumChooseModal = forwardRef<NumChooseModalRef, NumChooseModalProps>(
-  ({ poolId, onPay }, ref) => {
-    const [visible, setVisible] = useState(false);
-    const [box, setBox] = useState<BoxData | null>(null);
-    const [tabs, setTabs] = useState<{ title: string; value: number; data: number[] }[]>([]);
-    const [currentTab, setCurrentTab] = useState<{ title: string; value: number; data: number[] } | null>(null);
-    const [checkMap, setCheckMap] = useState<Record<number, number>>({});
-    const [useMap, setUseMap] = useState<Record<number, string>>({});
-    const [lockMap, setLockMap] = useState<Record<number, number>>({});
-    const [loading, setLoading] = useState(false);
-
-    // 已选择的号码列表
-    const chooseData = Object.values(checkMap);
-
-    // 初始化标签页
-    const handleTab = useCallback((boxData: BoxData) => {
-      const totalData: number[] = [];
-      for (let i = 1; i <= boxData.quantity; i++) {
-        totalData.push(i);
-      }
-      const newTabs: { title: string; value: number; data: number[] }[] = [];
-      const count = Math.floor(boxData.quantity / 100) + (boxData.quantity % 100 > 0 ? 1 : 0);
-      for (let i = 0; i < count; i++) {
-        let title = `${100 * i + 1}~${100 * i + 100}`;
-        if (100 * (i + 1) > totalData.length) {
-          title = `${100 * i + 1}~${totalData.length}`;
-        }
-        newTabs.push({
-          title,
-          value: i + 1,
-          data: totalData.slice(100 * i, 100 * (i + 1)),
-        });
-      }
-      setTabs(newTabs);
-      setCurrentTab(newTabs[0] || null);
-    }, []);
-
-    // 获取不可用座位号
-    const getData = useCallback(async (boxData: BoxData, tab?: { data: number[] }) => {
-      try {
-        let startSeatNumber = 1;
-        let endSeatNumber = 100;
-        if (tab && tab.data.length > 0) {
-          startSeatNumber = tab.data[0];
-          endSeatNumber = tab.data[tab.data.length - 1];
-        }
-        const res = await getUnavailableSeatNumbers(poolId, boxData.number, startSeatNumber, endSeatNumber);
-        if (res) {
-          const nums: number[] = [];
-          if (res.usedSeatNumbers) {
-            const map: Record<number, string> = {};
-            res.usedSeatNumbers.forEach((item: { seatNumber: number; level: string }) => {
-              map[item.seatNumber] = item.level;
-              nums.push(item.seatNumber);
-            });
-            setUseMap(map);
-          }
-          if (res.applyedSeatNumbers) {
-            const map: Record<number, number> = {};
-            res.applyedSeatNumbers.forEach((item: number) => {
-              map[item] = item;
-              nums.push(item);
-            });
-            setLockMap(map);
-          }
-          // 移除已被占用的选择
-          if (nums.length > 0) {
-            setCheckMap((prev) => {
-              const newMap = { ...prev };
-              nums.forEach((n) => delete newMap[n]);
-              return newMap;
-            });
-          }
-        }
-      } catch (error) {
-        console.error('获取座位号失败:', error);
-      }
-    }, [poolId]);
-
-    useImperativeHandle(ref, () => ({
-      show: (boxData: BoxData) => {
-        setBox(boxData);
-        setCheckMap({});
-        setUseMap({});
-        setLockMap({});
-        setVisible(true);
-        handleTab(boxData);
-        // 延迟获取数据,等待标签页初始化完成
-        setTimeout(() => {
-          const totalData: number[] = [];
-          for (let i = 1; i <= boxData.quantity; i++) {
-            totalData.push(i);
-          }
-          const firstTabData = totalData.slice(0, 100);
-          getData(boxData, { data: firstTabData });
-        }, 100);
-      },
-      close: () => {
-        setVisible(false);
-        setBox(null);
-        setTabs([]);
-        setCurrentTab(null);
-        setCheckMap({});
-        setUseMap({});
-        setLockMap({});
-      },
-    }));
-
-    const close = () => {
-      setVisible(false);
-    };
-
-    // 切换标签页
-    const clickTab = (tab: { title: string; value: number; data: number[] }) => {
-      setCurrentTab(tab);
-      if (box) getData(box, tab);
-    };
-
-    // 选择/取消选择号码
-    const choose = (item: number) => {
-      if (useMap[item] || lockMap[item]) return;
-      setCheckMap((prev) => {
-        const newMap = { ...prev };
-        if (newMap[item]) {
-          delete newMap[item];
-        } else {
-          if (Object.keys(newMap).length >= 50) {
-            Alert.alert('提示', '最多不超过50发');
-            return prev;
-          }
-          newMap[item] = item;
-        }
-        return newMap;
-      });
-    };
-
-    // 删除已选择的号码
-    const deleteChoose = (item: number) => {
-      setCheckMap((prev) => {
-        const newMap = { ...prev };
-        delete newMap[item];
-        return newMap;
-      });
-    };
-
-    // 预览订单
-    const preview = async () => {
-      if (!box) return;
-      if (chooseData.length <= 0) {
-        Alert.alert('提示', '请选择号码');
-        return;
-      }
-      setLoading(true);
-      try {
-        const res = await previewOrder(poolId, chooseData.length, box.number, chooseData);
-        if (res) {
-          if (res.duplicateSeatNumbers && res.duplicateSeatNumbers.length > 0) {
-            Alert.alert('提示', `${res.duplicateSeatNumbers.join(',')}号被占用`);
-            // 移除被占用的号码
-            setCheckMap((prev) => {
-              const newMap = { ...prev };
-              res.duplicateSeatNumbers.forEach((n: number) => delete newMap[n]);
-              return newMap;
-            });
-            getData(box);
-            return;
-          }
-          onPay({ preview: res, seatNumbers: chooseData, boxNumber: box.number });
-          close();
-        }
-      } catch (error: any) {
-        Alert.alert('提示', error?.message || '获取订单信息失败');
-      } finally {
-        setLoading(false);
-      }
-    };
-
-    return (
-      <Modal visible={visible} transparent animationType="slide" onRequestClose={close}>
-        <View style={styles.overlay}>
-          <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={close} />
-          <ImageBackground source={{ uri: Images.box.detail.recordBg }} style={styles.container} resizeMode="cover">
-            {/* 标题 */}
-            <View style={styles.titleSection}>
-              <Text style={styles.title}>换盒</Text>
-              <TouchableOpacity style={styles.closeBtn} onPress={close}>
-                <Text style={styles.closeText}>×</Text>
-              </TouchableOpacity>
-            </View>
-
-            {/* 标签页 */}
-            {tabs.length > 1 && (
-              <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.tabsScroll}>
-                <View style={styles.tabs}>
-                  {tabs.map((tab) => (
-                    <TouchableOpacity
-                      key={tab.value}
-                      style={[styles.tabItem, currentTab?.value === tab.value && styles.tabItemActive]}
-                      onPress={() => clickTab(tab)}
-                    >
-                      <Text style={[styles.tabText, currentTab?.value === tab.value && styles.tabTextActive]}>
-                        {tab.title}
-                      </Text>
-                    </TouchableOpacity>
-                  ))}
-                </View>
-              </ScrollView>
-            )}
-
-            {/* 号码网格 */}
-            <ScrollView style={styles.gridScroll} showsVerticalScrollIndicator={false}>
-              <View style={styles.grid}>
-                {currentTab?.data.map((item) => {
-                  const isChecked = !!checkMap[item];
-                  const isUsed = !!useMap[item];
-                  const isLocked = !!lockMap[item];
-                  return (
-                    <TouchableOpacity
-                      key={item}
-                      style={[
-                        styles.gridItem,
-                        isChecked && styles.gridItemActive,
-                        isUsed && styles.gridItemUsed,
-                        isLocked && styles.gridItemLocked,
-                      ]}
-                      onPress={() => choose(item)}
-                      disabled={isUsed || isLocked}
-                    >
-                      {isUsed ? (
-                        <View style={styles.usedContent}>
-                          <Text style={styles.usedNum}>{item}号</Text>
-                          <Text style={[styles.levelTitle, { color: LEVEL_MAP[useMap[item]]?.color }]}>
-                            {LEVEL_MAP[useMap[item]]?.title}
-                          </Text>
-                        </View>
-                      ) : (
-                        <Text style={[styles.gridItemText, isLocked && styles.gridItemTextLocked]}>
-                          {item}号
-                        </Text>
-                      )}
-                      {isChecked && (
-                        <View style={styles.checkIcon}>
-                          <Text style={styles.checkIconText}>✓</Text>
-                        </View>
-                      )}
-                      {isLocked && (
-                        <View style={styles.lockIcon}>
-                          <Text style={styles.lockIconText}>🔒</Text>
-                        </View>
-                      )}
-                    </TouchableOpacity>
-                  );
-                })}
-              </View>
-            </ScrollView>
-
-            {/* 已选择的号码 */}
-            <View style={styles.selectedSection}>
-              <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.selectedScroll}>
-                {chooseData.map((item) => (
-                  <View key={item} style={styles.selectedItem}>
-                    <Text style={styles.selectedText}>{item}号</Text>
-                    <TouchableOpacity style={styles.selectedClose} onPress={() => deleteChoose(item)}>
-                      <Text style={styles.selectedCloseText}>×</Text>
-                    </TouchableOpacity>
-                  </View>
-                ))}
-              </ScrollView>
-            </View>
-
-            {/* 确认按钮 */}
-            <View style={styles.btnBox}>
-              <TouchableOpacity
-                style={[styles.submitBtn, loading && styles.submitBtnDisabled]}
-                onPress={preview}
-                disabled={loading}
-              >
-                {loading ? (
-                  <ActivityIndicator color="#fff" size="small" />
-                ) : (
-                  <>
-                    <Text style={styles.submitText}>确定选择</Text>
-                    <Text style={styles.submitSubText}>已选择({chooseData.length})发</Text>
-                  </>
-                )}
-              </TouchableOpacity>
-            </View>
-          </ImageBackground>
-        </View>
-      </Modal>
-    );
-  }
-);
-
-const styles = StyleSheet.create({
-  overlay: {
-    flex: 1,
-    backgroundColor: 'rgba(0,0,0,0.5)',
-    justifyContent: 'flex-end',
-  },
-  mask: { flex: 1 },
-  container: {
-    borderTopLeftRadius: 15,
-    borderTopRightRadius: 15,
-    paddingTop: 15,
-    paddingBottom: 34,
-    maxHeight: '80%',
-  },
-  titleSection: {
-    alignItems: 'center',
-    paddingVertical: 15,
-    position: 'relative',
-  },
-  title: {
-    fontSize: 16,
-    fontWeight: 'bold',
-    color: '#fff',
-    textShadowColor: '#000',
-    textShadowOffset: { width: 1, height: 1 },
-    textShadowRadius: 2,
-  },
-  closeBtn: {
-    position: 'absolute',
-    right: 15,
-    top: 10,
-    width: 24,
-    height: 24,
-    backgroundColor: '#ebebeb',
-    borderRadius: 12,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  closeText: { fontSize: 18, color: '#a2a2a2', marginTop: -2 },
-  tabsScroll: {
-    maxHeight: 40,
-    marginHorizontal: 10,
-    marginBottom: 10,
-  },
-  tabs: {
-    flexDirection: 'row',
-  },
-  tabItem: {
-    paddingHorizontal: 12,
-    paddingVertical: 6,
-    backgroundColor: 'rgba(255,255,255,0.2)',
-    borderRadius: 15,
-    marginRight: 8,
-  },
-  tabItemActive: {
-    backgroundColor: '#FFC900',
-  },
-  tabText: {
-    fontSize: 12,
-    color: '#fff',
-  },
-  tabTextActive: {
-    color: '#000',
-    fontWeight: 'bold',
-  },
-  gridScroll: {
-    height: 350,
-    backgroundColor: '#fff',
-    marginHorizontal: 10,
-    borderRadius: 10,
-  },
-  grid: {
-    flexDirection: 'row',
-    flexWrap: 'wrap',
-    padding: 10,
-  },
-  gridItem: {
-    width: ITEM_WIDTH,
-    height: ITEM_WIDTH / 2,
-    backgroundColor: '#FFC900',
-    borderWidth: 3,
-    borderColor: '#000',
-    borderRadius: 4,
-    justifyContent: 'center',
-    alignItems: 'center',
-    margin: 4,
-    position: 'relative',
-  },
-  gridItemActive: {},
-  gridItemUsed: {
-    backgroundColor: '#e8e8e8',
-  },
-  gridItemLocked: {
-    backgroundColor: 'rgba(98, 99, 115, 0.3)',
-    borderWidth: 0,
-  },
-  gridItemText: {
-    fontSize: 12,
-    color: '#000',
-    fontWeight: 'bold',
-  },
-  gridItemTextLocked: {
-    color: 'rgba(255,255,255,0.3)',
-  },
-  usedContent: {
-    alignItems: 'center',
-  },
-  usedNum: {
-    fontSize: 10,
-    color: '#000',
-    opacity: 0.5,
-  },
-  levelTitle: {
-    fontSize: 10,
-    fontWeight: 'bold',
-  },
-  checkIcon: {
-    position: 'absolute',
-    right: 0,
-    bottom: 0,
-    width: 16,
-    height: 14,
-    backgroundColor: '#000',
-    borderTopLeftRadius: 6,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  checkIconText: {
-    fontSize: 10,
-    color: '#FFC900',
-  },
-  lockIcon: {
-    position: 'absolute',
-    right: 0,
-    bottom: 0,
-    width: 16,
-    height: 14,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  lockIconText: {
-    fontSize: 10,
-  },
-  selectedSection: {
-    height: 40,
-    marginHorizontal: 10,
-    marginTop: 10,
-  },
-  selectedScroll: {
-    flex: 1,
-  },
-  selectedItem: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    backgroundColor: '#FFC900',
-    borderWidth: 3,
-    borderColor: '#000',
-    borderRadius: 4,
-    paddingHorizontal: 10,
-    paddingVertical: 4,
-    marginRight: 8,
-    height: 32,
-  },
-  selectedText: {
-    fontSize: 12,
-    color: '#000',
-    fontWeight: 'bold',
-  },
-  selectedClose: {
-    marginLeft: 6,
-    width: 16,
-    height: 16,
-    backgroundColor: 'rgba(255,255,255,0.3)',
-    borderRadius: 8,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  selectedCloseText: {
-    fontSize: 12,
-    color: '#000',
-  },
-  btnBox: {
-    paddingHorizontal: 20,
-    paddingTop: 15,
-  },
-  submitBtn: {
-    backgroundColor: '#ff9600',
-    height: 50,
-    borderRadius: 25,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  submitBtnDisabled: {
-    opacity: 0.6,
-  },
-  submitText: {
-    fontSize: 16,
-    fontWeight: 'bold',
-    color: '#fff',
-    textShadowColor: '#000',
-    textShadowOffset: { width: 1, height: 1 },
-    textShadowRadius: 1,
-  },
-  submitSubText: {
-    fontSize: 10,
-    color: '#fff',
-    marginTop: 2,
-  },
-});

+ 0 - 227
app/award-detail-yfs/components/ProductListYfs.tsx

@@ -1,227 +0,0 @@
-import { Image } from 'expo-image';
-import React from 'react';
-import { ScrollView, StyleSheet, Text, View } from 'react-native';
-
-
-interface ProductItem {
-  id: string;
-  name: string;
-  cover: string;
-  level: string;
-  probability: number;
-  price?: number;
-  quantity?: number;
-  spu?: {
-    id: string;
-    cover: string;
-    name: string;
-  };
-}
-
-interface BoxData {
-  leftQuantity: number;
-  usedStat?: Record<string, { spuId: string; quantity: number }>;
-}
-
-interface ProductListYfsProps {
-  products: ProductItem[];
-  levelList?: any[];
-  poolId: string;
-  price: number;
-  box?: BoxData | null;
-}
-
-// 等级配置
-const LEVEL_CONFIG: Record<string, { title: string; color: string; bgColor: string }> = {
-  A: { title: '超神款', color: '#fff', bgColor: '#FF4444' },
-  B: { title: '欧皇款', color: '#fff', bgColor: '#FF9900' },
-  C: { title: '隐藏款', color: '#fff', bgColor: '#9966FF' },
-  D: { title: '普通款', color: '#fff', bgColor: '#00CCFF' },
-};
-
-// 格式化概率,去除末尾的0
-const formatProbability = (val: string | number) => {
-  const num = parseFloat(String(val));
-  if (isNaN(num)) return '0';
-  // 保留两位小数,去除末尾的0
-  return num.toFixed(2).replace(/\.?0+$/, '');
-};
-
-export const ProductListYfs: React.FC<ProductListYfsProps> = ({ products, levelList, box }) => {
-  // 按等级分组
-  const groupedProducts = products.reduce(
-    (acc, item) => {
-      const level = item.level || 'D';
-      if (!acc[level]) acc[level] = [];
-      acc[level].push(item);
-      return acc;
-    },
-    {} as Record<string, ProductItem[]>
-  );
-
-  // 获取等级概率
-  const getLevelProbability = (level: string) => {
-    const item = levelList?.find((e) => e.level === level);
-    return item ? `${formatProbability(item.probability)}%` : '0%';
-  };
-
-  // 获取商品剩余数量
-  const getLeftNum = (item: ProductItem) => {
-    const spuId = item.spu?.id || item.id;
-    if (!box?.usedStat || !box.usedStat[spuId]?.quantity) {
-      return item.quantity || 1;
-    }
-    return (item.quantity || 1) - box.usedStat[spuId].quantity;
-  };
-
-  // 获取商品概率
-  const getProbability = (item: ProductItem) => {
-    if (!box || box.leftQuantity <= 0) return '0';
-    const leftNum = getLeftNum(item);
-    return ((leftNum / box.leftQuantity) * 100).toFixed(2);
-  };
-
-  const renderLevelSection = (level: string, items: ProductItem[]) => {
-    const config = LEVEL_CONFIG[level] || LEVEL_CONFIG['D'];
-
-    return (
-      <View key={level} style={styles.levelBox}>
-        {/* 等级标题行 */}
-        <View style={styles.levelTitleRow}>
-          <Text style={[styles.levelTitle, { color: config.bgColor }]}>{config.title}</Text>
-          <View style={styles.levelProportion}>
-            <Text style={styles.probabilityLabel}>概率:</Text>
-            <Text style={styles.probabilityValue}>{getLevelProbability(level)}</Text>
-          </View>
-        </View>
-
-        {/* 商品横向滚动列表 */}
-        <ScrollView
-          horizontal
-          showsHorizontalScrollIndicator={false}
-          contentContainerStyle={styles.scrollContent}
-        >
-          {items.map((item, index) => {
-            const cover = item.spu?.cover || item.cover;
-            const leftNum = getLeftNum(item);
-            const prob = formatProbability(getProbability(item));
-            return (
-              <View key={item.id || index} style={styles.productItem}>
-                {/* 商品图片 */}
-                <View style={styles.productImageBox}>
-                  <Image
-                    source={{ uri: cover }}
-                    style={styles.productImage}
-                    contentFit="contain"
-                  />
-                </View>
-                {/* 等级标签 */}
-                <View style={[styles.levelTag, { backgroundColor: config.bgColor }]}>
-                  <Text style={styles.levelTagLabel}>概率</Text>
-                  <Text style={styles.levelTagText}>{prob}%</Text>
-                </View>
-                {/* 剩余数量 */}
-                <Text style={styles.leftNumText}>{leftNum}/{item.quantity || 1}</Text>
-              </View>
-            );
-          })}
-        </ScrollView>
-      </View>
-    );
-  };
-
-  const levelOrder = ['A', 'B', 'C', 'D'];
-
-  return (
-    <View style={styles.container}>
-      {levelOrder.map((level) => {
-        const items = groupedProducts[level];
-        if (!items || items.length === 0) return null;
-        return renderLevelSection(level, items);
-      })}
-    </View>
-  );
-};
-
-const styles = StyleSheet.create({
-  container: {
-    paddingHorizontal: 10,
-  },
-  levelBox: {
-    marginBottom: 20,
-  },
-  levelTitleRow: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    paddingHorizontal: 14,
-    paddingBottom: 10,
-    position: 'relative',
-  },
-  levelTitle: {
-    fontSize: 18,
-    fontWeight: 'bold',
-    textAlign: 'center',
-    textShadowColor: '#000',
-    textShadowOffset: { width: 1, height: 1 },
-    textShadowRadius: 1,
-  },
-  levelProportion: {
-    position: 'absolute',
-    right: 0,
-    bottom: 10,
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  probabilityLabel: {
-    fontSize: 12,
-    color: '#ffc901',
-  },
-  probabilityValue: {
-    fontSize: 12,
-    color: '#fff',
-  },
-  scrollContent: {
-    paddingHorizontal: 5,
-  },
-  productItem: {
-    width: 90,
-    marginRight: 10,
-    alignItems: 'center',
-  },
-  productImageBox: {
-    width: 90,
-    height: 90,
-    backgroundColor: '#fff',
-    borderRadius: 4,
-    overflow: 'hidden',
-  },
-  productImage: {
-    width: 90,
-    height: 90,
-  },
-  levelTag: {
-    width: 80,
-    height: 26,
-    borderRadius: 2,
-    flexDirection: 'row',
-    justifyContent: 'center',
-    alignItems: 'center',
-    marginTop: -8,
-  },
-  levelTagLabel: {
-    fontSize: 10,
-    color: '#fff',
-    marginRight: 2,
-  },
-  levelTagText: {
-    fontSize: 12,
-    color: '#fff',
-    fontWeight: 'bold',
-  },
-  leftNumText: {
-    fontSize: 10,
-    color: '#999',
-    marginTop: 4,
-  },
-});

+ 365 - 634
app/award-detail-yfs/index.tsx

@@ -1,9 +1,8 @@
-import { Image } from 'expo-image';
 import { useLocalSearchParams, useRouter } from 'expo-router';
 import React, { useCallback, useEffect, useRef, useState } from 'react';
 import {
     ActivityIndicator,
-    Animated,
+    Alert,
     Dimensions,
     ImageBackground,
     ScrollView,
@@ -11,707 +10,439 @@ import {
     StyleSheet,
     Text,
     TouchableOpacity,
-    View,
+    View
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
+import { RecordModal, RecordModalRef } from '@/app/award-detail/components/RecordModal';
+import { BoxSelectionModal, BoxSelectionModalRef } from '@/components/award-detail-yfs/BoxSelectionModal';
+import FirstLast from '@/components/award-detail-yfs/FirstLast';
+import { NumSelectionModal, NumSelectionModalRef } from '@/components/award-detail-yfs/NumSelectionModal';
+import ProductListYfs from '@/components/award-detail-yfs/ProductListYfs';
+import ProductSwiper from '@/components/award-detail-yfs/ProductSwiper';
+import PurchaseBar from '@/components/award-detail-yfs/PurchaseBar';
+import { RuleModal, RuleModalRef } from '@/components/award-detail-yfs/RuleModal';
 import { Images } from '@/constants/images';
-import { useAuth } from '@/contexts/AuthContext';
-import {
-    getBoxDetail,
-    getNextBox,
-    getPoolDetail,
-    getPreBox,
-    lockBox,
-    poolIn,
-    poolOut,
-    previewOrder,
-    unlockBox,
-} from '@/services/award';
-
-import { CheckoutModal } from '../award-detail/components/CheckoutModal';
-import { RecordModal } from '../award-detail/components/RecordModal';
-import { RuleModal } from '../award-detail/components/RuleModal';
-import { BoxChooseModal } from './components/BoxChooseModal';
-import { NumChooseModal } from './components/NumChooseModal';
-import { ProductListYfs } from './components/ProductListYfs';
-
-const { width: SCREEN_WIDTH } = Dimensions.get('window');
-
-interface PoolData {
-  id: string;
-  name: string;
-  cover: string;
-  price: number;
-  specialPrice?: number;
-  specialPriceFive?: number;
-  restrictionQuantity?: number;
-  luckGoodsList: ProductItem[];
-  luckGoodsLevelProbabilityList?: any[];
-}
+import { getBoxDetail, getNextBox, getPoolDetail, getPreBox, lockBox, poolIn, poolOut, unlockBox } from '@/services/award';
+import { CheckoutModal, CheckoutModalRef } from '../award-detail/components/CheckoutModal';
 
-interface ProductItem {
-  id: string;
-  name: string;
-  cover: string;
-  level: string;
-  probability: number;
-  price?: number;
-  quantity?: number;
-}
+const { width } = Dimensions.get('window');
 
-interface BoxData {
-  number: string;
-  quantity: number;
-  leftQuantity: number;
-  leftQuantityA: number;
-  leftQuantityB: number;
-  leftQuantityC: number;
-  leftQuantityD: number;
-  lastNumber: number;
-  lock?: { locker: string; leftTime: number };
-  usedStat?: Record<string, { spuId: string; quantity: number }>;
-  prizeList?: any[];
-}
+const styles = StyleSheet.create({
+  container: { flex: 1, backgroundColor: '#1a1a2e' },
+  background: { flex: 1 },
+  loading: { flex: 1, backgroundColor: '#1a1a2e', justifyContent: 'center', alignItems: 'center' },
+  nav: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    paddingHorizontal: 15,
+    height: 90,
+    zIndex: 100,
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    right: 0,
+  },
+  backBtn: { width: 40 },
+  backText: { color: '#fff', fontSize: 24 },
+  navTitle: { color: '#fff', fontSize: 16, fontWeight: 'bold', maxWidth: '60%' },
+  placeholder: { width: 40 },
+  scrollView: { flex: 1 },
+  
+  detailWrapper: {
+      position: 'relative',
+      minHeight: 800,
+      paddingBottom: 100,
+  },
+  mainGoodsSection: {
+      width: '100%',
+      height: 504,
+      position: 'relative',
+      zIndex: 1,
+  },
+  mainGoodsSectionBtext: {
+      width: '100%',
+      height: 74,
+      marginTop: -10,
+      zIndex: 2,
+  },
+  positionBgLeftBg: {
+      position: 'absolute',
+      left: 0,
+      top: 225,
+      width: 32,
+      height: 188,
+      zIndex: 2,
+  },
+  positionBgRightBg: {
+      position: 'absolute',
+      right: 0,
+      top: 225,
+      width: 32,
+      height: 188,
+      zIndex: 2,
+  },
+  positionBut: {
+      position: 'absolute',
+      zIndex: 10,
+      width: 35,
+      height: 34,
+  },
+  btnBg: {
+      width: '100%',
+      height: '100%',
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  slantedL: {
+      color: '#fff',
+      fontSize: 12,
+      fontWeight: 'bold',
+      transform: [{ rotate: '14deg' }],
+      marginTop: -2,
+      textShadowColor: '#000',
+      textShadowOffset: { width: 1, height: 1 },
+      textShadowRadius: 1,
+  },
+  slantedR: {
+      color: '#fff',
+      fontSize: 12,
+      fontWeight: 'bold',
+      transform: [{ rotate: '-16deg' }],
+      marginTop: -2,
+      textShadowColor: '#000',
+      textShadowOffset: { width: 1, height: 1 },
+      textShadowRadius: 1,
+  },
+  positionRule: {
+      top: 256,
+      left: 0,
+  },
+  positionStore: {
+      top: 256,
+      right: 0,
+  },
+  positionRecord: {
+      top: 300,
+      left: 0,
+  },
+  positionRefresh: { 
+      top: 345,
+      right: 0,
+  },
+  bottomBar: {
+      position: 'absolute',
+      bottom: 0,
+      left: 0,
+      right: 0,
+      zIndex: 200,
+  }
+});
 
 export default function AwardDetailYfsScreen() {
   const { poolId } = useLocalSearchParams<{ poolId: string }>();
   const router = useRouter();
   const insets = useSafeAreaInsets();
-  const { user } = useAuth();
-
   const [loading, setLoading] = useState(true);
-  const [data, setData] = useState<PoolData | null>(null);
-  const [products, setProducts] = useState<ProductItem[]>([]);
-  const [box, setBox] = useState<BoxData | null>(null);
-  const [boxNum, setBoxNum] = useState<string>('');
-  const [currentIndex, setCurrentIndex] = useState(0);
-  const [leftTime, setLeftTime] = useState(0);
-  const [probability, setProbability] = useState<any[]>([]);
+  const [data, setData] = useState<any>(null);
   const [scrollTop, setScrollTop] = useState(0);
+  const [currentBox, setCurrentBox] = useState<any>(null);
 
-  const checkoutRef = useRef<any>(null);
-  const recordRef = useRef<any>(null);
-  const ruleRef = useRef<any>(null);
-  const numChooseRef = useRef<any>(null);
-  const boxChooseRef = useRef<any>(null);
-  const floatAnim = useRef(new Animated.Value(0)).current;
-  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
+  const boxRef = useRef<BoxSelectionModalRef>(null);
+  const numRef = useRef<NumSelectionModalRef>(null);
+  const checkoutRef = useRef<CheckoutModalRef>(null);
+  const ruleRef = useRef<RuleModalRef>(null);
+  const recordRef = useRef<RecordModalRef>(null);
 
-  useEffect(() => {
-    Animated.loop(
-      Animated.sequence([
-        Animated.timing(floatAnim, { toValue: 10, duration: 1500, useNativeDriver: true }),
-        Animated.timing(floatAnim, { toValue: -10, duration: 1500, useNativeDriver: true }),
-      ])
-    ).start();
-  }, []);
+  const getSafePoolId = () => Array.isArray(poolId) ? poolId[0] : poolId;
+
+  const loadBox = async (boxNum?: string) => {
+      try {
+          const id = getSafePoolId();
+          console.log(`[DEBUG-ICHIBAN] loadBox called for pool: ${id}, boxNum: ${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);
+      } catch (error) {
+          console.error('[DEBUG-ICHIBAN] Failed to load box', error);
+      }
+  };
 
   const loadData = useCallback(async () => {
     if (!poolId) return;
     setLoading(true);
     try {
-      const detail = await getPoolDetail(poolId);
-      if (detail) {
-        setData(detail);
-        setProducts(detail.luckGoodsList || []);
-      }
+      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);
+      setData(res);
+      console.log(`[DEBUG-ICHIBAN] Pool detail loaded, calling loadBox...`);
+      await loadBox(); // Initial box
     } catch (error) {
-      console.error('加载数据失败:', 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]);
 
-  const loadBox = useCallback(
-    async (num?: string) => {
-      if (!poolId) return;
-      try {
-        const res = await getBoxDetail(poolId, num);
-        if (res) handleBoxResult(res);
-      } catch (error) {
-        console.error('加载盒子失败:', error);
-      }
-    },
-    [poolId]
-  );
-
-  const handleBoxResult = (res: any) => {
-    const map: Record<string, any> = {};
-    if (res.usedStat) {
-      res.usedStat.forEach((item: any) => {
-        map[item.spuId] = item;
-      });
-    }
-    res.usedStat = map;
-    setBox(res);
-    setBoxNum(res.number);
-    lockTimeStart(res);
-
-    if (res.leftQuantity <= 0) {
-      setProbability([
-        { level: 'A', probability: 0 },
-        { level: 'B', probability: 0 },
-        { level: 'C', probability: 0 },
-        { level: 'D', probability: 0 },
-      ]);
-    } else {
-      setProbability([
-        { level: 'A', probability: ((res.leftQuantityA / res.leftQuantity) * 100).toFixed(2) },
-        { level: 'B', probability: ((res.leftQuantityB / res.leftQuantity) * 100).toFixed(2) },
-        { level: 'C', probability: ((res.leftQuantityC / res.leftQuantity) * 100).toFixed(2) },
-        { level: 'D', probability: ((res.leftQuantityD / res.leftQuantity) * 100).toFixed(2) },
-      ]);
-    }
-  };
-
-  const lockTimeStart = (boxData: BoxData) => {
-    lockTimeEnd();
-    if (boxData?.lock) {
-      setLeftTime(boxData.lock.leftTime);
-      timerRef.current = setInterval(() => {
-        setLeftTime((prev) => {
-          if (prev <= 1) {
-            lockTimeEnd();
-            loadBox();
-            return 0;
-          }
-          return prev - 1;
-        });
-      }, 1000);
-    }
-  };
-
-  const lockTimeEnd = () => {
-    if (timerRef.current) {
-      clearInterval(timerRef.current);
-      timerRef.current = null;
-    }
-    setLeftTime(0);
-  };
-
   useEffect(() => {
-    loadData();
-    loadBox();
-    if (poolId) poolIn(poolId);
-    return () => {
-      if (poolId) poolOut(poolId);
-      lockTimeEnd();
-    };
-  }, [poolId]);
-
-  const handlePay = async (num: number) => {
-    if (!poolId || !data || !box) return;
-    try {
-      const preview = await previewOrder(poolId, num, box.number);
-      if (preview) checkoutRef.current?.show(num, preview, box.number);
-    } catch (error) {
-      console.error('预览订单失败:', error);
+    if (poolId) {
+        poolIn(poolId);
+        return () => {
+            poolOut(poolId);
+        };
     }
-  };
-
-  const handleSuccess = () => {
-    setTimeout(() => {
-      loadData();
-      loadBox(boxNum);
-    }, 500);
-  };
-
-  // 处理多盒购买(号码选择后的回调)
-  const handleNumPay = async (params: { preview: any; seatNumbers: number[]; boxNumber: string }) => {
-    checkoutRef.current?.show(params.seatNumbers.length, params.preview, params.boxNumber, params.seatNumbers);
-  };
-
-  // 打开号码选择弹窗
-  const handleShowNumChoose = () => {
-    if (!box) return;
-    numChooseRef.current?.show({
-      number: box.number,
-      quantity: box.quantity || box.leftQuantity,
-      lastNumber: box.lastNumber,
-    });
-  };
-
-  // 打开换盒弹窗
-  const handleShowBoxChoose = () => {
-    boxChooseRef.current?.show();
-  };
+  }, [poolId]);
 
-  // 选择盒子后的回调
-  const handleChooseBox = (boxNumber: string) => {
-    loadBox(boxNumber);
+  const handleBoxSelect = (box: any) => {
+      // Must load full detail to get usedStat
+      loadBox(box.number);
   };
 
-  const handlePreBox = async () => {
-    if (!poolId || !boxNum || parseInt(boxNum) <= 1) return;
-    try {
-      const res = await getPreBox(poolId, boxNum);
-      if (res) handleBoxResult(res);
-    } catch (error) {
-      console.error('获取上一个盒子失败:', error);
-    }
+  const handlePrevBox = async () => {
+      if (!currentBox) return;
+      try {
+          const res = await getPreBox(getSafePoolId(), currentBox.number);
+          if (res) setCurrentBox(res);
+          else Alert.alert('提示', '已经是第一盒了');
+      } catch (e) { Alert.alert('提示', '切换失败'); }
   };
 
   const handleNextBox = async () => {
-    if (!poolId || !box || parseInt(boxNum) >= box.lastNumber) return;
-    try {
-      const res = await getNextBox(poolId, boxNum);
-      if (res) handleBoxResult(res);
-    } catch (error) {
-      console.error('获取下一个盒子失败:', error);
-    }
-  };
-
-  const handleLock = async () => {
-    if (!poolId || !boxNum) return;
-    try {
-      await lockBox(poolId, boxNum);
-      loadBox(boxNum);
-    } catch (error) {
-      console.error('锁定失败:', error);
-    }
-  };
-
-  const handleUnlock = async () => {
-    if (!poolId || !boxNum) return;
-    try {
-      await unlockBox(poolId, boxNum);
-      loadBox(boxNum);
-    } catch (error) {
-      console.error('解锁失败:', error);
-    }
-  };
-
-  const handlePrev = () => {
-    if (currentIndex > 0) setCurrentIndex(currentIndex - 1);
-  };
-  const handleNext = () => {
-    if (currentIndex < products.length - 1) setCurrentIndex(currentIndex + 1);
-  };
-
-  const getLevelName = (level: string) => {
-    const map: Record<string, string> = { A: '超神款', B: '欧皇款', C: '隐藏款', D: '普通款' };
-    return map[level] || level;
+      if (!currentBox) return;
+      try {
+          const res = await getNextBox(getSafePoolId(), currentBox.number);
+          if (res) setCurrentBox(res);
+          else Alert.alert('提示', '已经是最后一盒了');
+      } catch (e) { Alert.alert('提示', '切换失败'); }
   };
 
-  const getLeftNum = (item: ProductItem) => {
-    if (!box?.usedStat || !box.usedStat[item.id]?.quantity) {
-      return item.quantity || 1;
-    }
-    return (item.quantity || 1) - box.usedStat[item.id].quantity;
+  const handlePay = ({ previewRes, chooseNum, boxNum }: any) => {
+      checkoutRef.current?.show(chooseNum.length, previewRes, boxNum, chooseNum);
   };
 
-  const getProbability = (item: ProductItem) => {
-    if (!box || box.leftQuantity <= 0) return '0';
-    const leftNum = getLeftNum(item);
-    return ((leftNum / box.leftQuantity) * 100).toFixed(2);
-  };
+  useEffect(() => {
+    loadData();
+  }, [loadData]);
 
-  const leftNum = box?.lock ? (leftTime / box.lock.leftTime) * 100 : 0;
-  const headerBg = scrollTop > 0 ? '#333' : 'transparent';
+  const headerBg = scrollTop > 50 ? '#333' : 'transparent';
 
   if (loading) {
     return (
-      <View style={styles.loadingContainer}>
-        <ActivityIndicator size="large" color="#fff" />
-      </View>
-    );
-  }
-
-  if (!data) {
-    return (
-      <View style={styles.loadingContainer}>
-        <Text style={styles.errorText}>奖池不存在</Text>
-        <TouchableOpacity style={styles.backBtn2} onPress={() => router.back()}>
-          <Text style={styles.backBtn2Text}>返回</Text>
-        </TouchableOpacity>
+      <View style={styles.loading}>
+        <ActivityIndicator color="#fff" />
       </View>
     );
   }
 
-  const currentProduct = products[currentIndex];
-
   return (
     <View style={styles.container}>
       <StatusBar barStyle="light-content" />
       <ImageBackground source={{ uri: Images.common.indexBg }} style={styles.background} resizeMode="cover">
-        {/* 顶部导航 */}
-        <View style={[styles.header, { paddingTop: insets.top, backgroundColor: headerBg }]}>
-          <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
-            <Text style={styles.backText}>{'<'}</Text>
+        
+        {/* Navigation Bar */}
+        <View style={[styles.nav, { paddingTop: insets.top, backgroundColor: headerBg }]}>
+          <TouchableOpacity onPress={() => router.back()} style={styles.backBtn}>
+             <Text style={styles.backText}>←</Text>
           </TouchableOpacity>
-          <Text style={styles.headerTitle} numberOfLines={1}>
-            {data.name}
-          </Text>
+          <Text style={styles.navTitle} numberOfLines={1}>{data?.name || '详情'}</Text>
           <View style={styles.placeholder} />
         </View>
 
         <ScrollView
           style={styles.scrollView}
-          showsVerticalScrollIndicator={false}
           onScroll={(e) => setScrollTop(e.nativeEvent.contentOffset.y)}
           scrollEventThrottle={16}
+          showsVerticalScrollIndicator={false}
         >
-          {/* 主商品展示区域 */}
-          <ImageBackground source={{ uri: Images.box.detail.mainGoodsSection }} style={styles.mainGoodsSection} resizeMode="cover">
-            <View style={{ height: 72 + insets.top }} />
-
-            {/* 商品轮播区域 */}
-            <View style={styles.mainSwiper}>
-              {currentProduct && (
-                <>
-                  <Animated.View style={[styles.productImageBox, { transform: [{ translateY: floatAnim }] }]}>
-                    <Image source={{ uri: currentProduct.cover }} style={styles.productImage} contentFit="contain" />
-                  </Animated.View>
-
-                  {/* 等级信息 */}
-                  <ImageBackground source={{ uri: Images.box.detail.detailsBut }} style={styles.detailsBut} resizeMode="contain">
-                    <View style={styles.detailsText}>
-                      <Text style={styles.levelText}>{getLevelName(currentProduct.level)}</Text>
-                      <Text style={styles.probabilityText}>({getProbability(currentProduct)}%)</Text>
-                    </View>
-                  </ImageBackground>
-
-                  {/* 商品名称 */}
-                  <ImageBackground source={{ uri: Images.box.detail.nameBg }} style={styles.goodsNameBg} resizeMode="contain">
-                    <Text style={styles.goodsNameText} numberOfLines={6}>
-                      {currentProduct.name}
-                    </Text>
-                  </ImageBackground>
-                </>
-              )}
-
-              {/* 左右切换按钮 */}
-              {currentIndex > 0 && (
-                <TouchableOpacity style={styles.prevBtn} onPress={handlePrev}>
-                  <Image source={{ uri: Images.box.detail.left }} style={styles.arrowImg} contentFit="contain" />
-                </TouchableOpacity>
-              )}
-              {currentIndex < products.length - 1 && (
-                <TouchableOpacity style={styles.nextBtn} onPress={handleNext}>
-                  <Image source={{ uri: Images.box.detail.right }} style={styles.arrowImg} contentFit="contain" />
+            <View style={styles.detailWrapper}>
+                {/* Main Goods Section */}
+                <ImageBackground 
+                    source={{ uri: Images.box.detail.mainGoodsSection }} 
+                    style={styles.mainGoodsSection}
+                    resizeMode="stretch"
+                >
+                    <View style={{ height: 180 }} /> 
+                    
+                    <ProductSwiper 
+                        box={currentBox} // Pass current box
+                        products={data?.luckGoodsList?.map((item: any) => ({
+                                cover: item.spu.cover,
+                                name: item.spu.name,
+                                level: item.level,
+                                quantity: item.quantity,
+                                id: item.id, // Ensure IDs are passed
+                                spu: item.spu
+                        })) || []} 
+                    />
+                    
+                    <ImageBackground 
+                        source={{ uri: Images.box.detail.positionBgleftBg }} 
+                        style={styles.positionBgLeftBg} 
+                        resizeMode="contain"
+                    />
+                     <ImageBackground 
+                        source={{ uri: Images.box.detail.positionBgRightBg }} 
+                        style={styles.positionBgRightBg} 
+                        resizeMode="contain"
+                    />
+                </ImageBackground>
+
+                {/* Bottom Text Graphic */}
+                <ImageBackground
+                    source={{ uri: Images.box.detail.mainGoodsSectionBtext }}
+                    style={styles.mainGoodsSectionBtext}
+                    resizeMode="contain"
+                />
+
+                {/* FirstLast (Box Info & Nav) */}
+                <FirstLast 
+                    data={data}
+                    box={currentBox}
+                    onPrevBox={handlePrevBox}
+                    onNextBox={handleNextBox}
+                    onChangeBox={() => boxRef.current?.show()}
+                    onProductClick={(spu) => console.log(spu)}
+                />
+
+                {/* Product List (A/B/C/D) */}
+                <ProductListYfs 
+                    products={data?.luckGoodsList || []}
+                    poolId={getSafePoolId()}
+                    box={currentBox}
+                    onProductClick={(product) => {
+                        const list = data?.luckGoodsList || [];
+                        const getPId = (p: any) => String(p.id || p.spu?.id || p.spuId || '');
+                        const targetId = getPId(product);
+                        const index = list.findIndex((p: any) => getPId(p) === targetId);
+                        console.log(`[DEBUG-NAV] Clicked ${targetId} (${product.name}). Found index: ${index}`);
+                        if (index !== -1) {
+                            router.push({
+                                pathname: '/award-detail/swipe',
+                                params: { poolId: getSafePoolId(), index }
+                            });
+                        }
+                    }}
+                />
+
+                {/* Floating Buttons */}
+                <TouchableOpacity style={[styles.positionBut, styles.positionRule]} onPress={() => ruleRef.current?.show()}>
+                     <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.btnBg} resizeMode="contain">
+                        <Text style={styles.slantedL}>规则</Text>
+                     </ImageBackground>
                 </TouchableOpacity>
-              )}
-            </View>
 
-            {/* 左侧装饰 */}
-            <Image source={{ uri: Images.box.detail.positionBgleftBg }} style={styles.positionBgleftBg} contentFit="contain" />
-            {/* 右侧装饰 */}
-            <Image source={{ uri: Images.box.detail.positionBgRightBg }} style={styles.positionBgRightBg} contentFit="contain" />
-          </ImageBackground>
-
-          {/* 底部装饰文字 */}
-          <Image source={{ uri: Images.box.detail.mainGoodsSectionBtext }} style={styles.mainGoodsSectionBtext} contentFit="cover" />
-
-          {/* 侧边按钮 - 规则 */}
-          <TouchableOpacity style={[styles.positionBut, styles.positionRule]} onPress={() => ruleRef.current?.show()}>
-            <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
-              <Text style={styles.positionButText}>规则</Text>
-            </ImageBackground>
-          </TouchableOpacity>
-
-          {/* 侧边按钮 - 记录 */}
-          <TouchableOpacity style={[styles.positionBut, styles.positionRecord]} onPress={() => recordRef.current?.show()}>
-            <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
-              <Text style={styles.positionButText}>记录</Text>
-            </ImageBackground>
-          </TouchableOpacity>
-
-          {/* 侧边按钮 - 锁定/解锁 */}
-          {box && !box.lock && (
-            <TouchableOpacity style={[styles.positionBut, styles.positionLock]} onPress={handleLock}>
-              <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
-                <Text style={styles.positionButText}>锁定</Text>
-              </ImageBackground>
-            </TouchableOpacity>
-          )}
-          {box?.lock && user && box.lock.locker === (user.userId || user.id) && (
-            <TouchableOpacity style={[styles.positionBut, styles.positionLock]} onPress={handleUnlock}>
-              <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
-                <Text style={styles.positionButText}>解锁</Text>
-              </ImageBackground>
-            </TouchableOpacity>
-          )}
-
-          {/* 侧边按钮 - 仓库 */}
-          <TouchableOpacity style={[styles.positionBut, styles.positionStore]} onPress={() => router.push('/store' as any)}>
-            <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">
-              <Text style={styles.positionButTextR}>仓库</Text>
-            </ImageBackground>
-          </TouchableOpacity>
+                <TouchableOpacity style={[styles.positionBut, styles.positionRecord]} onPress={() => recordRef.current?.show()}>
+                     <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.btnBg} resizeMode="contain">
+                        <Text style={styles.slantedL}>记录</Text>
+                     </ImageBackground>
+                </TouchableOpacity>
+                
+                 <TouchableOpacity style={[styles.positionBut, styles.positionStore]} onPress={() => router.push('/store')}>
+                     <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.btnBg} resizeMode="contain">
+                        <Text style={styles.slantedR}>仓库</Text>
+                     </ImageBackground>
+                </TouchableOpacity>
+                
+                {/* Lock Button */}
+                 <TouchableOpacity style={[styles.positionBut, { top: 300, right: 0 }]} onPress={() => {
+                     if (!currentBox) return;
+                     const isLocked = currentBox.lockTime && new Date(currentBox.lockTime).getTime() > Date.now();
+                     
+                     Alert.alert('提示', isLocked ? '是否解锁盒子?' : '是否锁定盒子(锁定后他人无法购买)?', [
+                         { text: '取消', style: 'cancel' },
+                         { text: '确定', onPress: async () => {
+                             try {
+                                 const poolId = getSafePoolId();
+                                 let success = false;
+                                 if (isLocked) {
+                                     success = await unlockBox(poolId, currentBox.number);
+                                 } else {
+                                     success = await lockBox(poolId, currentBox.number);
+                                 }
+                                 
+                                 if (success) {
+                                     Alert.alert('成功', isLocked ? '解锁成功' : '锁定成功');
+                                     loadBox(currentBox.number); // Refresh
+                                 }
+                                 // System handles error msg via interceptor, no need for manual alert
+                             } catch (e) {
+                                 // console.error(e); // Silent error
+                             }
+                         }}
+                     ]);
+                 }}>
+                     <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.btnBg} resizeMode="contain">
+                        <Text style={styles.slantedR}>{currentBox?.lockTime && new Date(currentBox.lockTime).getTime() > Date.now() ? '解锁' : '锁盒'}</Text>
+                     </ImageBackground>
+                </TouchableOpacity>
 
-          {/* 侧边按钮 - 刷新 */}
-          <TouchableOpacity style={[styles.positionBut, styles.positionRefresh]} onPress={() => loadBox(boxNum)}>
-            <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">
-              <Text style={styles.positionButTextR}>刷新</Text>
-            </ImageBackground>
-          </TouchableOpacity>
+                <TouchableOpacity style={[styles.positionBut, styles.positionRefresh]} onPress={() => loadBox(currentBox?.number)}>
+                     <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.btnBg} resizeMode="contain">
+                        <Text style={styles.slantedR}>刷新</Text>
+                     </ImageBackground>
+                </TouchableOpacity>
 
-          {/* 锁定倒计时 */}
-          {box?.lock && (
-            <View style={styles.lockTimeBox}>
-              <Text style={styles.lockTimeLabel}>剩余时间:</Text>
-              <View style={styles.lockTimeBarBox}>
-                <View style={styles.lockTimeBar}>
-                  <View style={[styles.processBar, { width: `${leftNum}%` }]} />
-                </View>
-                <Text style={[styles.lockTimeText, { left: `${leftNum}%` }]}>{leftTime}</Text>
-              </View>
             </View>
-          )}
-
-          {/* 标题 */}
-          <View style={styles.productTitleBox}>
-            <Image source={{ uri: Images.box.detail.productTitle }} style={styles.productTitleImg} contentFit="contain" />
-          </View>
-
-          {/* 一番赏盒子信息区域 */}
-          <ImageBackground source={{ uri: Images.box.detail.firstBoxBg }} style={styles.firstLastBox}>
-            {/* 当前盒子剩余数量 */}
-            {box && (
-              <View style={styles.boxSizeRow}>
-                <Text style={styles.boxSizeText}>当前盒子剩余:</Text>
-                <Text style={styles.boxSizeText}>
-                  <Text style={styles.boxSizeNum}>{box.leftQuantity}</Text>
-                  /{box.quantity || '-'}发
-                </Text>
-              </View>
-            )}
-
-            {/* 抢先赏/最终赏/全局赏展示 */}
-            {box?.prizeList && box.prizeList.length > 0 && (
-              <View style={styles.prizeListRow}>
-                {box.prizeList.map((item: any, index: number) => (
-                  <ImageBackground key={index} source={{ uri: Images.box.detail.firstItemBg }} style={styles.prizeItem}>
-                    <Image source={{ uri: item.cover }} style={styles.prizeImage} contentFit="contain" />
-                    <View style={[styles.prizeLevelTag, {
-                      backgroundColor: item.level === 'FIRST' ? 'rgba(91, 189, 208, 0.8)' :
-                        item.level === 'LAST' ? 'rgba(246, 44, 113, 0.8)' : 'rgba(44, 246, 74, 0.8)'
-                    }]}>
-                      <Text style={styles.prizeLevelText}>
-                        {item.level === 'FIRST' ? '抢先赏' : item.level === 'LAST' ? '最终赏' : '全局赏'}
-                      </Text>
-                    </View>
-                  </ImageBackground>
-                ))}
-              </View>
-            )}
-
-            {/* 换盒控制区 */}
-            <ImageBackground source={{ uri: Images.box.detail.funBoxBg }} style={styles.funBox}>
-              <TouchableOpacity style={styles.preBoxBtn} onPress={handlePreBox} disabled={!boxNum || parseInt(boxNum) <= 1}>
-                <Text style={[styles.funBoxText, (!boxNum || parseInt(boxNum) <= 1) && styles.disabled]}>上一盒</Text>
-              </TouchableOpacity>
-              <TouchableOpacity style={styles.changeBoxBtn} onPress={handleShowBoxChoose}>
-                <Text style={styles.changeBoxText}>换盒({boxNum || '-'}/{box?.lastNumber || '-'})</Text>
-              </TouchableOpacity>
-              <TouchableOpacity style={styles.nextBoxBtn} onPress={handleNextBox} disabled={!box || parseInt(boxNum) >= box.lastNumber}>
-                <Text style={[styles.funBoxText, (!box || parseInt(boxNum) >= box.lastNumber) && styles.disabled]}>下一盒</Text>
-              </TouchableOpacity>
-            </ImageBackground>
-          </ImageBackground>
-
-          {/* 商品列表 */}
-          <ProductListYfs products={products} levelList={probability} poolId={poolId!} price={data.price} box={box} />
-
-          <View style={{ height: 150 }} />
+
+             <View style={{ height: 100 }} />
         </ScrollView>
 
-        {/* 底部购买栏 */}
-        <ImageBackground source={{ uri: Images.box.detail.boxDetailBott }} style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]} resizeMode="cover">
-          <View style={styles.bottomBtns}>
-            <TouchableOpacity style={styles.btnItem} onPress={() => handlePay(1)} activeOpacity={0.8}>
-              <ImageBackground source={{ uri: Images.common.butBgV }} style={styles.btnBg} resizeMode="contain">
-                <Text style={styles.btnText}>购买一盒</Text>
-                <Text style={styles.btnPrice}>(¥{data.specialPrice || data.price})</Text>
-              </ImageBackground>
-            </TouchableOpacity>
-            <TouchableOpacity style={styles.btnItem} onPress={() => handlePay(5)} activeOpacity={0.8}>
-              <ImageBackground source={{ uri: Images.common.butBgL }} style={styles.btnBg} resizeMode="contain">
-                <Text style={styles.btnText}>购买五盒</Text>
-                <Text style={styles.btnPrice}>(¥{data.specialPriceFive || data.price * 5})</Text>
-              </ImageBackground>
-            </TouchableOpacity>
-            <TouchableOpacity style={styles.btnItem} onPress={handleShowNumChoose} activeOpacity={0.8}>
-              <ImageBackground source={{ uri: Images.common.butBgH }} style={styles.btnBg} resizeMode="contain">
-                <Text style={styles.btnText}>购买多盒</Text>
-              </ImageBackground>
-            </TouchableOpacity>
-          </View>
-        </ImageBackground>
-      </ImageBackground>
+        <View style={styles.bottomBar}>
+            <PurchaseBar 
+                price={data?.price || 0}
+                specialPrice={data?.specialPrice}
+                specialPriceFive={data?.specialPriceFive}
+                onBuy={(count) => {
+                    if (!currentBox) { 
+                        boxRef.current?.show();
+                    } else {
+                        numRef.current?.show(currentBox);
+                    }
+                }} 
+                onBuyMany={() => {
+                   if (!currentBox) { 
+                        boxRef.current?.show();
+                    } else {
+                        numRef.current?.show(currentBox);
+                    }
+                }}
+            />
+        </View>
 
-      <CheckoutModal ref={checkoutRef} data={data} poolId={poolId!} boxNumber={boxNum} onSuccess={handleSuccess} />
-      <RecordModal ref={recordRef} poolId={poolId!} />
-      <RuleModal ref={ruleRef} />
-      <NumChooseModal ref={numChooseRef} poolId={poolId!} onPay={handleNumPay} />
-      <BoxChooseModal ref={boxChooseRef} poolId={poolId!} onChoose={handleChooseBox} />
+        {/* Modals */}
+        <BoxSelectionModal 
+            ref={boxRef} 
+            poolId={getSafePoolId()} 
+            onSelect={handleBoxSelect} 
+        />
+        <NumSelectionModal 
+            ref={numRef} 
+            poolId={getSafePoolId()} 
+            onPay={handlePay} 
+        />
+        <CheckoutModal 
+            ref={checkoutRef} 
+            poolId={getSafePoolId()} 
+            data={data}
+            onSuccess={(res) => {
+                console.log('Success', res);
+                loadData(); // Refresh
+            }} 
+        />
+        <RuleModal ref={ruleRef} />
+        <RecordModal ref={recordRef} poolId={getSafePoolId()} />
+      </ImageBackground>
     </View>
   );
 }
 
-const styles = StyleSheet.create({
-  container: { flex: 1, backgroundColor: '#1a1a2e' },
-  background: { flex: 1 },
-  loadingContainer: { flex: 1, backgroundColor: '#1a1a2e', justifyContent: 'center', alignItems: 'center' },
-  errorText: { color: '#999', fontSize: 16 },
-  backBtn2: { marginTop: 20, backgroundColor: '#ff6600', paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8 },
-  backBtn2Text: { color: '#fff', fontSize: 14 },
 
-  header: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'space-between',
-    paddingHorizontal: 10,
-    paddingBottom: 10,
-    position: 'absolute',
-    top: 0,
-    left: 0,
-    right: 0,
-    zIndex: 100,
-  },
-  backBtn: { width: 40, height: 40, justifyContent: 'center', alignItems: 'center' },
-  backText: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
-  headerTitle: { color: '#fff', fontSize: 15, fontWeight: 'bold', flex: 1, textAlign: 'center', width: 250 },
-  placeholder: { width: 40 },
 
-  scrollView: { flex: 1 },
 
-  mainGoodsSection: { width: SCREEN_WIDTH, height: 504, position: 'relative' },
-  mainSwiper: { position: 'relative', width: '100%', height: 375, alignItems: 'center', justifyContent: 'center', marginTop: -50 },
-  productImageBox: { width: 200, height: 300, justifyContent: 'center', alignItems: 'center' },
-  productImage: { width: 200, height: 300 },
-  detailsBut: { width: 120, height: 45, justifyContent: 'center', alignItems: 'center', marginTop: -30 },
-  detailsText: { flexDirection: 'row', alignItems: 'center' },
-  levelText: { fontSize: 14, color: '#FBC400', fontWeight: 'bold' },
-  probabilityText: { fontSize: 10, color: '#FBC400', marginLeft: 3 },
-  goodsNameBg: { position: 'absolute', left: 47, top: 53, width: 43, height: 100, paddingTop: 8, justifyContent: 'flex-start', alignItems: 'center' },
-  goodsNameText: { fontSize: 12, fontWeight: 'bold', color: '#000', width: 20, textAlign: 'center' },
-  prevBtn: { position: 'absolute', left: 35, top: '40%' },
-  nextBtn: { position: 'absolute', right: 35, top: '40%' },
-  arrowImg: { width: 33, height: 38 },
-
-  positionBgleftBg: { position: 'absolute', left: 0, top: 225, width: 32, height: 188 },
-  positionBgRightBg: { position: 'absolute', right: 0, top: 225, width: 32, height: 188 },
-  mainGoodsSectionBtext: { width: SCREEN_WIDTH, height: 74, marginTop: -10 },
-
-  positionBut: { position: 'absolute', zIndex: 10, width: 35, height: 34 },
-  positionButBg: { width: 35, height: 34, justifyContent: 'center', alignItems: 'center' },
-  positionButText: { fontSize: 12, fontWeight: 'bold', color: '#fff', transform: [{ rotate: '14deg' }], textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
-  positionButTextR: { fontSize: 12, fontWeight: 'bold', color: '#fff', transform: [{ rotate: '-16deg' }], textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 1 },
-  positionRule: { top: 256, left: 0 },
-  positionRecord: { top: 300, left: 0 },
-  positionLock: { top: 345, left: 0 },
-  positionStore: { top: 256, right: 0 },
-  positionRefresh: { top: 300, right: 0 },
-
-  lockTimeBox: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#71ccff', padding: 10, marginHorizontal: 10, borderRadius: 8, marginTop: -60 },
-  lockTimeLabel: { color: '#000', fontSize: 12 },
-  lockTimeBarBox: { flex: 1, height: 30, position: 'relative', justifyContent: 'center' },
-  lockTimeBar: { height: 8, backgroundColor: 'rgba(255,255,255,0.6)', borderRadius: 4, overflow: 'hidden' },
-  processBar: { height: '100%', backgroundColor: '#209ae5', borderRadius: 4 },
-  lockTimeText: { position: 'absolute', top: -5, fontSize: 10, backgroundColor: '#000', color: '#fff', paddingHorizontal: 4, borderRadius: 2, marginLeft: -13 },
-
-  productTitleBox: { alignItems: 'center', marginTop: -20, marginBottom: 10 },
-  productTitleImg: { width: 121, height: 29 },
-
-  // 一番赏盒子信息区域
-  firstLastBox: {
-    marginHorizontal: 10,
-    height: 193,
-    marginBottom: 10,
-    paddingTop: 20,
-  },
-  boxSizeRow: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingLeft: 10,
-    paddingTop: 12,
-  },
-  boxSizeText: {
-    fontSize: 12,
-    color: '#fff',
-    textShadowColor: '#000',
-    textShadowOffset: { width: 1, height: 1 },
-    textShadowRadius: 2,
-  },
-  boxSizeNum: {
-    fontSize: 16,
-    fontWeight: 'bold',
-  },
-  prizeListRow: {
-    flexDirection: 'row',
-    justifyContent: 'center',
-    alignItems: 'center',
-    paddingTop: 20,
-    minHeight: 66,
-  },
-  prizeItem: {
-    width: 63,
-    height: 65,
-    marginHorizontal: 5,
-    position: 'relative',
-  },
-  prizeImage: {
-    width: 60,
-    height: 50,
-  },
-  prizeLevelTag: {
-    position: 'absolute',
-    left: 0,
-    bottom: -8,
-    width: '100%',
-    height: 22,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  prizeLevelText: {
-    fontSize: 10,
-    color: '#fff',
-    fontWeight: 'bold',
-  },
-  funBox: {
-    width: 270,
-    height: 38,
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    alignSelf: 'center',
-    marginTop: 15,
-  },
-  preBoxBtn: {
-    width: 90,
-    height: 50,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  nextBoxBtn: {
-    width: 90,
-    height: 50,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  changeBoxBtn: {
-    width: 110,
-    height: 60,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  funBoxText: {
-    fontSize: 12,
-    color: '#fff',
-    fontWeight: '500',
-  },
-  changeBoxText: {
-    fontSize: 12,
-    color: '#000',
-    fontWeight: '500',
-  },
-  disabled: { opacity: 0.3 },
-
-  bottomBar: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 69, paddingHorizontal: 5 },
-  bottomBtns: { flexDirection: 'row', height: 64, alignItems: 'center', justifyContent: 'space-around' },
-  btnItem: { flex: 1, marginHorizontal: 6 },
-  btnBg: { width: '100%', height: 54, justifyContent: 'center', alignItems: 'center' },
-  btnText: { fontSize: 14, fontWeight: 'bold', color: '#fff' },
-  btnPrice: { fontSize: 9, color: '#fff' },
-});
+

+ 149 - 10
app/award-detail/components/LotteryResultModal.tsx

@@ -1,7 +1,9 @@
+import { AVPlaybackStatus, ResizeMode, Video } from 'expo-av';
 import { Image } from 'expo-image';
 import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
 import {
     ActivityIndicator,
+    Alert,
     Animated,
     Dimensions,
     ImageBackground,
@@ -18,7 +20,7 @@ import { convertApply, getApplyResult } from '@/services/award';
 
 const { width: SCREEN_WIDTH } = Dimensions.get('window');
 const CARD_WIDTH = (SCREEN_WIDTH - 60) / 3;
-const CARD_HEIGHT = CARD_WIDTH * 1.5;
+const CARD_HEIGHT = CARD_WIDTH * 1.78;
 
 const CDN_BASE = 'https://cdn.acetoys.cn';
 const imgUrl = `${CDN_BASE}/kai_xin_ma_te/supermart`;
@@ -59,6 +61,45 @@ const LotteryImages = {
   lotteryBg: `${imgUrlSupermart}/supermart/box/sequence/sequence0.jpg`,
   cardBack: `${imgUrl}/box/back1.png`,
   halo: `${imgUrlSupermart}/supermart/box/halo.gif`,
+  levelA_bg: `${imgUrlSupermart}/supermart/box/levelD.png`, // Using levelD as placeholder if specific not found, or specific bg
+  levelA_title: `${imgUrlSupermart}/supermart/box/detail/levelTextA.png`,
+  close: `${imgUrlSupermart}/supermart/box/qiji_close.png`,
+};
+
+const DEFAULT_JACKPOT_VIDEO = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart/box/lottery/jackpot.mp4';
+
+const KingModal = ({ visible, data, onClose }: { visible: boolean; data: LotteryItem | null; onClose: () => void }) => {
+  if (!visible || !data) return null;
+
+  return (
+    <Modal visible={visible} transparent animationType="fade" onRequestClose={onClose}>
+      <View style={styles.kingContainer}>
+        <View style={styles.kingMask}>
+          <TouchableOpacity style={StyleSheet.absoluteFill} onPress={onClose} />
+        </View>
+        <View style={styles.kingWrapper}>
+           <Image 
+            source={{ uri: LEVEL_MAP[data.level]?.resultBg || LEVEL_MAP.A.resultBg }} 
+            style={styles.kingBg} 
+            contentFit="fill"
+          />
+          <Animated.Image 
+            source={{ uri: data.cover }} 
+            style={styles.kingProduct} 
+            resizeMode="contain"
+          />
+           <Image 
+            source={{ uri: LotteryImages.levelA_title }} 
+            style={styles.kingTitle} 
+            contentFit="contain"
+          />
+        </View>
+        <TouchableOpacity style={styles.kingCloseBtn} onPress={onClose}>
+          <Image source={{ uri: LotteryImages.close }} style={styles.kingCloseIcon} />
+        </TouchableOpacity>
+      </View>
+    </Modal>
+  );
 };
 
 interface LotteryItem {
@@ -96,7 +137,24 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
     const [isSkip, setIsSkip] = useState(true);
     const [tradeNo, setTradeNo] = useState('');
 
+    const [kingVisible, setKingVisible] = useState(false);
+    const [kingData, setKingData] = useState<LotteryItem | null>(null);
+    const [videoVisible, setVideoVisible] = useState(false);
+    const [videoUrl, setVideoUrl] = useState('');
+
+    const videoRef = useRef<Video>(null);
     const flipAnims = useRef<Animated.Value[]>([]);
+
+    const startFlow = (data: LotteryItem[]) => {
+        setVideoVisible(false);
+        const levelAItem = data.find((item: LotteryItem) => ['A', 'a'].includes(item.level));
+        if (levelAItem) {
+            setKingData(levelAItem);
+            setKingVisible(true);
+        } else {
+            setTimeout(() => flipCards(data), 500);
+        }
+    };
     const dataLoadedRef = useRef(false);
 
     useImperativeHandle(ref, () => ({
@@ -111,6 +169,10 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
         setHaloShow(false);
         setRebateAmount(0);
         setIsSkip(true);
+        setKingVisible(false);
+        setKingData(null);
+        setVideoVisible(false);
+        setVideoUrl('');
         dataLoadedRef.current = false;
         flipAnims.current = [];
       },
@@ -172,16 +234,35 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
             setTableData(array);
             setLoading(false);
             
-            // 直接开始翻牌动画
-            setTimeout(() => flipCards(array), 500);
+            setLoading(false);
+            
+            // Determine playing video
+            let playVideoUrl = '';
+            if (res.video) {
+                playVideoUrl = res.video;
+            } else {
+                // Check if any Level A item exists (Robust check)
+                const hasLevelA = array.some((item: LotteryItem) => ['A', 'a'].includes(item.level));
+                if (hasLevelA) {
+                    playVideoUrl = DEFAULT_JACKPOT_VIDEO;
+                }
+            }
+
+            if (playVideoUrl) {
+                setVideoUrl(playVideoUrl);
+                setVideoVisible(true);
+            } else {
+                startFlow(array);
+            }
           } else if (attempts < maxAttempts) {
             attempts++;
             timeoutId = setTimeout(fetchData, 400);
           } else {
             setLoading(false);
-            window.alert('获取结果超时,请在仓库中查看');
+            Alert.alert('提示', '获取结果超时,请在仓库中查看');
           }
         } catch (error) {
+          console.error(error);
           if (attempts < maxAttempts) {
             attempts++;
             timeoutId = setTimeout(fetchData, 400);
@@ -214,10 +295,11 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
         if (res) {
           setTableData((prev) => prev.map((item) => ({ ...item, magicAmount: 0 })));
           setTotal(0);
-          window.alert('兑换成功');
+          setTotal(0);
+          Alert.alert('提示', '兑换成功');
         }
       } catch {
-        window.alert('兑换失败,请重试');
+        Alert.alert('提示', '兑换失败,请重试');
       }
     };
 
@@ -263,9 +345,9 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
             </Animated.View>
             {/* 正面 - 商品信息 */}
             <Animated.View style={[styles.cardFront, { transform: [{ rotateY: frontRotate }], opacity: frontOpacity }]}>
-              <ImageBackground source={{ uri: levelConfig.resultBg }} style={styles.cardFrontBg} resizeMode="cover">
+              <ImageBackground source={{ uri: levelConfig.resultBg }} style={styles.cardFrontBg} resizeMode="stretch">
                 <Image source={{ uri: item.cover }} style={styles.productImage} contentFit="contain" />
-                <Image source={{ uri: levelConfig.borderImg }} style={styles.borderImage} contentFit="cover" />
+                <Image source={{ uri: levelConfig.borderImg }} style={styles.borderImage} contentFit="fill" />
                 <View style={styles.cardInfo}>
                   <View style={styles.infoRow}>
                     <Text style={styles.levelText}>{levelConfig.title}</Text>
@@ -286,9 +368,9 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
       return (
         <View key={item.id || index} style={styles.cardWrapper}>
           <View style={styles.cardFrontStatic}>
-            <ImageBackground source={{ uri: levelConfig.resultBg }} style={styles.cardFrontBg} resizeMode="cover">
+            <ImageBackground source={{ uri: levelConfig.resultBg }} style={styles.cardFrontBg} resizeMode="stretch">
               <Image source={{ uri: item.cover }} style={styles.productImage} contentFit="contain" />
-              <Image source={{ uri: levelConfig.borderImg }} style={styles.borderImage} contentFit="cover" />
+              <Image source={{ uri: levelConfig.borderImg }} style={styles.borderImage} contentFit="fill" />
               <View style={styles.cardInfo}>
                 <View style={styles.infoRow}>
                   <Text style={styles.levelText}>{levelConfig.title}</Text>
@@ -377,6 +459,52 @@ export const LotteryResultModal = forwardRef<LotteryResultModalRef, LotteryResul
             )}
           </ImageBackground>
         </View>
+        <KingModal 
+            visible={kingVisible} 
+            data={kingData} 
+            onClose={() => {
+                setKingVisible(false);
+                setTimeout(() => flipCards(tableData), 300);
+            }} 
+        />
+        {videoVisible && videoUrl ? (
+            <View style={styles.videoContainer}>
+                <Video
+                    key={videoUrl}
+                    ref={videoRef}
+                    source={{ uri: videoUrl }}
+                    style={styles.video}
+                    resizeMode={ResizeMode.COVER}
+                    shouldPlay
+                    isLooping={false}
+                    useNativeControls={false}
+                    onLoad={() => {
+                        videoRef.current?.playAsync();
+                    }}
+                    onError={(error) => {
+                        console.error('Video playback error:', error);
+                        setVideoVisible(false);
+                        startFlow(tableData);
+                    }}
+                    onPlaybackStatusUpdate={(status: AVPlaybackStatus) => {
+                        if (!status.isLoaded) return;
+                        if (status.didJustFinish) {
+                             setVideoVisible(false);
+                             startFlow(tableData);
+                        }
+                    }}
+                />
+                 <TouchableOpacity 
+                    style={styles.skipVideoBtn} 
+                    onPress={() => {
+                        setVideoVisible(false);
+                        startFlow(tableData);
+                    }}
+                >
+                    <Text style={styles.skipText}>跳过</Text>
+                </TouchableOpacity>
+            </View>
+        ) : null}
       </Modal>
     );
   }
@@ -508,4 +636,15 @@ const styles = StyleSheet.create({
     borderRadius: 15 
   },
   skipText: { fontSize: 14, color: '#fff' },
+  kingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'transparent' },
+  kingMask: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.7)' },
+  kingWrapper: { width: 353 * 1.2, height: 545 * 1.2, alignItems: 'center', justifyContent: 'center' },
+  kingBg: { position: 'absolute', width: '100%', height: '100%' },
+  kingProduct: { width: 293 * 1.2, height: 370 * 1.2, marginTop: 33, borderRadius: 8 },
+  kingTitle: { position: 'absolute', bottom: 40, width: 230 * 1.2, height: 102 * 1.2 },
+  kingCloseBtn: { marginTop: 30 },
+  kingCloseIcon: { width: 60, height: 60 },
+  videoContainer: { ...StyleSheet.absoluteFillObject, zIndex: 10000, backgroundColor: 'black' },
+  video: { width: '100%', height: '100%' },
+  skipVideoBtn: { position: 'absolute', top: 60, right: 20, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 15, paddingVertical: 8, borderRadius: 20 },
 });

+ 55 - 49
app/award-detail/components/ProductList.tsx

@@ -1,7 +1,7 @@
 import { Image } from 'expo-image';
 import { useRouter } from 'expo-router';
 import React from 'react';
-import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+import { ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
 
 import { Images } from '@/constants/images';
 
@@ -28,22 +28,22 @@ interface ProductListProps {
 }
 
 // 等级配置 - 对应小程序的 LEVEL_MAP
-const LEVEL_CONFIG: Record<string, { title: string; color: string; bgColor: string }> = {
-  A: { title: '超神款', color: '#fff', bgColor: '#FF4444' },
-  B: { title: '欧皇款', color: '#fff', bgColor: '#FF9900' },
-  C: { title: '隐藏款', color: '#fff', bgColor: '#9966FF' },
-  D: { title: '普通款', color: '#fff', bgColor: '#00CCFF' },
+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 },
 };
 
 const ignoreRatio0 = (val: number) => {
-  // 将小数转换为百分比,保留两位小数
-  const percent = val * 100;
-  // 如果是整数则不显示小数点
-  if (percent === Math.floor(percent)) {
-    return String(Math.floor(percent));
+  // Revert to original logic: Do NOT multiply. Assume API sends WYSIWYG value (e.g. 99.05 or 0.001)
+  // Just format string to remove trailing zeros.
+  let str = String(val);
+  // Match original logic: strip trailing zeros if decimal
+  if (str.indexOf('.') > -1) {
+      str = str.replace(/0+$/, '').replace(/\.$/, '');
   }
-  // 保留两位小数,去除末尾的0
-  return percent.toFixed(2).replace(/\.?0+$/, '');
+  return str;
 };
 
 export const ProductList: React.FC<ProductListProps> = ({ products, levelList, poolId }) => {
@@ -68,8 +68,8 @@ export const ProductList: React.FC<ProductListProps> = ({ products, levelList, p
 
   // 点击产品跳转到详情页
   const handleProductPress = (item: ProductItem) => {
-    // 找到该产品在原始列表中的索引
-    const index = products.findIndex((p) => p.id === item.id);
+    // Look up by object reference to handle duplicate IDs correctly
+    const index = products.indexOf(item);
     router.push({
       pathname: '/award-detail/swipe' as any,
       params: { poolId, index: index >= 0 ? index : 0 },
@@ -101,23 +101,33 @@ export const ProductList: React.FC<ProductListProps> = ({ products, levelList, p
             return (
               <TouchableOpacity 
                 key={item.id || index} 
-                style={styles.productItem}
                 onPress={() => handleProductPress(item)}
                 activeOpacity={0.8}
               >
-                {/* 商品图片 */}
-                <View style={styles.productImageBox}>
-                  <Image
-                    source={{ uri: cover }}
-                    style={styles.productImage}
-                    contentFit="contain"
-                  />
-                </View>
-                {/* 等级标签 - 显示概率和等级名称 */}
-                <View style={[styles.levelTag, { backgroundColor: config.bgColor }]}>
-                  <Text style={styles.levelTagLabel}>概率</Text>
-                  <Text style={styles.levelTagText}>{ignoreRatio0(item.probability)}%</Text>
-                </View>
+                <ImageBackground 
+                    source={{ uri: Images.box.detail.levelBoxBg }}
+                    style={styles.productItem}
+                    resizeMode="stretch"
+                >
+                    {/* 商品图片 */}
+                    <Image
+                        source={{ uri: cover }}
+                        style={styles.productImage}
+                        contentFit="contain"
+                    />
+                    
+                    {/* 概率标签背景 */}
+                    <ImageBackground
+                        source={{ uri: config.productItem }}
+                        style={styles.levelTagBg}
+                        resizeMode="stretch"
+                    >
+                         <View style={styles.levelTagContent}>
+                            <Text style={styles.levelTagLabel}>概率:</Text>
+                            <Text style={styles.levelTagText}>{ignoreRatio0(item.probability)}%</Text>
+                         </View>
+                    </ImageBackground>
+                </ImageBackground>
               </TouchableOpacity>
             );
           })}
@@ -195,37 +205,33 @@ const styles = StyleSheet.create({
     paddingHorizontal: 5,
   },
   productItem: {
-    width: 90,
+    width: 88, 
+    height: 110,
     marginRight: 10,
     alignItems: 'center',
   },
-  productImageBox: {
-    width: 90,
-    height: 90,
-    backgroundColor: '#fff',
-    borderRadius: 4,
-    overflow: 'hidden',
-  },
   productImage: {
-    width: 90,
+    width: 88,
     height: 90,
   },
-  levelTag: {
-    width: 80,
-    height: 26,
-    borderRadius: 2,
-    flexDirection: 'row',
+  levelTagBg: {
+    width: 88,
+    height: 53,
+    marginTop: -18, 
     justifyContent: 'center',
-    alignItems: 'center',
-    marginTop: -8,
+    paddingTop: 12, 
+  },
+  levelTagContent: {
+      flexDirection: 'row',
+      justifyContent: 'center',
+      alignItems: 'center',
   },
   levelTagLabel: {
-    fontSize: 10,
-    color: '#fff',
-    marginRight: 2,
+      fontSize: 8,
+      color: '#fff',
   },
   levelTagText: {
-    fontSize: 12,
+    fontSize: 9,
     color: '#fff',
     fontWeight: 'bold',
   },

+ 289 - 187
app/award-detail/components/RecordModal.tsx

@@ -1,214 +1,316 @@
-import { Image } from 'expo-image';
-import React, { forwardRef, useImperativeHandle, useState } from 'react';
-import {
-    ActivityIndicator,
-    FlatList,
-    Modal,
-    StyleSheet,
-    Text,
-    TouchableOpacity,
-    View,
-} from 'react-native';
-
-import { getBuyRecord } from '@/services/award';
-
-interface RecordItem {
-  id: string;
-  nickname: string;
-  avatar: string;
-  goodsName: string;
-  cover: string;  // API 返回的是 cover 字段
-  level: string;
-  createTime: string;
-}
+import { LEVEL_MAP } from '@/constants/config';
+import { Images } from '@/constants/images';
+import { countRecordsAfterLastLevel, getBuyRecord } from '@/services/award';
+import { Image, ImageBackground } from 'expo-image';
+import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
+import { ActivityIndicator, Dimensions, FlatList, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+const { width, height } = Dimensions.get('window');
+
+const TABS = [
+    { title: '全部', value: '' },
+    { title: '超神款', value: 'A' },
+    { title: '欧皇款', value: 'B' },
+    { title: '隐藏款', value: 'C' },
+    { title: '普通款', value: 'D' }
+];
 
 interface RecordModalProps {
-  poolId: string;
+    poolId: string;
 }
 
 export interface RecordModalRef {
-  show: () => void;
-  close: () => void;
+    show: () => void;
+    close: () => void;
 }
 
-const LEVEL_MAP: Record<string, string> = {
-  A: '超神款',
-  B: '欧皇款',
-  C: '隐藏款',
-  D: '普通款',
-};
-
-export const RecordModal = forwardRef<RecordModalRef, RecordModalProps>(
-  ({ poolId }, ref) => {
+export const RecordModal = forwardRef<RecordModalRef, RecordModalProps>(({ poolId }, ref) => {
     const [visible, setVisible] = useState(false);
+    const [data, setData] = useState<any[]>([]);
+    const [activeTab, setActiveTab] = useState(TABS[0]);
     const [loading, setLoading] = useState(false);
-    const [records, setRecords] = useState<RecordItem[]>([]);
-    const [levelFilter, setLevelFilter] = useState<number | undefined>();
+    const [refreshing, setRefreshing] = useState(false);
+    const [lastId, setLastId] = useState<string | undefined>(undefined);
+    const [taggingA, setTaggingA] = useState(0);
+    const [taggingB, setTaggingB] = useState(0);
+
+    const [hasMore, setHasMore] = useState(true);
 
     useImperativeHandle(ref, () => ({
-      show: () => {
-        setVisible(true);
-        loadRecords();
-      },
-      close: () => setVisible(false),
+        show: () => {
+            setVisible(true);
+            refresh();
+        },
+        close: () => setVisible(false)
     }));
 
-    const loadRecords = async (level?: number) => {
-      setLoading(true);
-      try {
-        const res = await getBuyRecord(poolId, undefined, level);
-        setRecords(res || []);
-      } catch (error) {
-        console.error('加载记录失败:', error);
-      }
-      setLoading(false);
+    const refresh = () => {
+        setLastId(undefined);
+        setData([]);
+        setHasMore(true);
+        loadData(true);
+        loadStats();
     };
 
-    const handleLevelFilter = (level?: number) => {
-      setLevelFilter(level);
-      loadRecords(level);
+    const loadStats = async () => {
+        try {
+            const resA = await countRecordsAfterLastLevel({ levelEnumList: ['A'], poolId });
+            setTaggingA(resA.data || 0); // Check API structure: {data: number}
+            const resB = await countRecordsAfterLastLevel({ levelEnumList: ['B'], poolId });
+            setTaggingB(resB.data || 0);
+        } catch (e) {
+            console.error('Failed to load stats', e);
+        }
     };
 
-    const renderItem = ({ item }: { item: RecordItem }) => (
-      <View style={styles.recordItem}>
-        <Image
-          source={{ uri: item.avatar }}
-          style={styles.avatar}
-          contentFit="cover"
-        />
-        <View style={styles.recordInfo}>
-          <Text style={styles.nickname} numberOfLines={1}>{item.nickname}</Text>
-          <Text style={styles.time}>{item.createTime}</Text>
-        </View>
-        <View style={styles.goodsInfo}>
-          <Image
-            source={{ uri: item.cover }}
-            style={styles.goodsImage}
-            contentFit="cover"
-          />
-          <View style={styles.goodsDetail}>
-            <Text style={styles.goodsName} numberOfLines={1}>{item.goodsName}</Text>
-            <Text style={styles.levelText}>{LEVEL_MAP[item.level] || item.level}</Text>
-          </View>
-        </View>
-      </View>
-    );
+    const loadData = async (isRefresh = false) => {
+        if (loading || (!isRefresh && !hasMore)) return;
+        setLoading(true);
+        try {
+           const currentLastId = isRefresh ? undefined : lastId;
+           const res = await getBuyRecord(poolId, currentLastId, activeTab.value as any);
+           if (res && res.length > 0) {
+               setData(prev => {
+                   if (isRefresh) return res;
+                   // Deduplicate based on ID
+                   const existingIds = new Set(prev.map(item => item.id));
+                   const newItems = res.filter(item => !existingIds.has(item.id));
+                   return [...prev, ...newItems];
+               });
+               setLastId(res[res.length - 1].id);
+               // Assuming page size is 10 or 20.
+           } else {
+               setHasMore(false);
+           }
+           if (res && res.length < 10) {
+               setHasMore(false);
+           }
+        } catch (e) {
+            console.error('Failed to load records', e);
+        }
+        setLoading(false);
+        setRefreshing(false);
+    };
 
-    const levels = [
-      { label: '全部', value: undefined },
-      { label: '超神款', value: 1 },
-      { label: '欧皇款', value: 2 },
-      { label: '隐藏款', value: 3 },
-      { label: '普通款', value: 4 },
-    ];
+    const handleTabChange = (tab: any) => {
+        setActiveTab(tab);
+        // Reset and reload
+        setLastId(undefined);
+        setData([]);
+        setHasMore(true);
+    };
 
-    return (
-      <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
-        <View style={styles.overlay}>
-          <View style={styles.container}>
-            <View style={styles.header}>
-              <Text style={styles.title}>购买记录</Text>
-              <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
-                <Text style={styles.closeText}>×</Text>
-              </TouchableOpacity>
-            </View>
+    useEffect(() => {
+        if (visible) {
+             setLastId(undefined);
+             setData([]);
+             setHasMore(true);
+             loadData(true);
+        }
+    }, [activeTab]);
 
-            <View style={styles.filterRow}>
-              {levels.map((item) => (
-                <TouchableOpacity
-                  key={item.label}
-                  style={[styles.filterBtn, levelFilter === item.value && styles.filterBtnActive]}
-                  onPress={() => handleLevelFilter(item.value)}
-                >
-                  <Text style={[styles.filterText, levelFilter === item.value && styles.filterTextActive]}>
-                    {item.label}
-                  </Text>
-                </TouchableOpacity>
-              ))}
+    const renderItem = ({ item }: { item: any }) => (
+        <View style={styles.item}>
+            <Image source={{ uri: item.avatar || Images.common.defaultAvatar }} style={styles.avatar} />
+            <Text style={styles.nickname} numberOfLines={1}>{item.nickname}</Text>
+            
+            {/* Level Icon */}
+            <View style={[styles.levelTag, { borderColor: LEVEL_MAP[item.level]?.color || '#000' }]}>
+                 <Text style={[styles.levelText, { color: LEVEL_MAP[item.level]?.color || '#000' }]}>
+                     {LEVEL_MAP[item.level]?.title}
+                 </Text>
             </View>
 
-            {loading ? (
-              <View style={styles.loadingBox}>
-                <ActivityIndicator size="large" color="#ff6600" />
-              </View>
-            ) : (
-              <FlatList
-                data={records}
-                renderItem={renderItem}
-                keyExtractor={(item, index) => item.id || index.toString()}
-                contentContainerStyle={styles.listContent}
-                ListEmptyComponent={
-                  <View style={styles.emptyBox}>
-                    <Text style={styles.emptyText}>暂无记录</Text>
-                  </View>
-                }
-              />
-            )}
-          </View>
+            <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
+            <Text style={styles.countText}>×{item.lastCount}</Text>
+            <Image source={{ uri: item.cover }} style={styles.itemImage} />
         </View>
-      </Modal>
     );
-  }
-);
+
+    return (
+        <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
+             <View style={styles.overlay}>
+                 <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={() => setVisible(false)} />
+                 <ImageBackground 
+                    source={{ uri: Images.box.detail.recordBg }} 
+                    style={styles.container}
+                    resizeMode="stretch"
+                 >
+                     {/* Header */}
+                     <View style={styles.header}>
+                         <Text style={styles.title}>购买记录</Text>
+                         <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
+                             {/* Close Icon or Text */}
+                             <Text style={{ fontSize: 20 }}>×</Text>
+                         </TouchableOpacity>
+                     </View>
+
+                     {/* Stats Banner */}
+                     <ImageBackground 
+                        source={{ uri: Images.box.detail.taggingBg }} 
+                        style={styles.statsBanner}
+                        resizeMode="stretch"
+                     >
+                         <View style={styles.statItem}>
+                             <Text style={styles.statNum}>{taggingA}</Text>
+                             <Text style={styles.statLabel}>发未出</Text>
+                             <Image source={{ uri: LEVEL_MAP.A.titleText }} style={{ width: 45, height: 16 }} contentFit="contain" />
+                         </View>
+                         <View style={styles.statItem}>
+                             <Text style={styles.statNum}>{taggingB}</Text>
+                             <Text style={styles.statLabel}>发未出</Text>
+                             <Image source={{ uri: LEVEL_MAP.B.titleText }} style={{ width: 45, height: 16 }} contentFit="contain" />
+                         </View>
+                     </ImageBackground>
+
+                     {/* Tabs */}
+                     <View style={styles.tabs}>
+                         {TABS.map(tab => (
+                             <TouchableOpacity 
+                                key={tab.value} 
+                                style={[styles.tab, activeTab.value === tab.value && styles.activeTab]}
+                                onPress={() => handleTabChange(tab)}
+                             >
+                                 <Text style={[styles.tabText, activeTab.value === tab.value && styles.activeTabText]}>
+                                     {tab.title}
+                                 </Text>
+                             </TouchableOpacity>
+                         ))}
+                     </View>
+
+                     {/* List */}
+                     <FlatList
+                        data={data}
+                        renderItem={renderItem}
+                        keyExtractor={(item, index) => (item.id ? `${item.id}_${index}` : String(index))}
+                        style={styles.list}
+                        contentContainerStyle={{ paddingBottom: 20 }}
+                        onEndReached={() => loadData(false)}
+                        onEndReachedThreshold={0.5}
+                        ListEmptyComponent={
+                            !loading ? <Text style={styles.emptyText}>暂无记录</Text> : null
+                        }
+                        ListFooterComponent={loading ? <ActivityIndicator color="#000" /> : null}
+                     />
+
+                 </ImageBackground>
+             </View>
+        </Modal>
+    );
+});
 
 const styles = StyleSheet.create({
-  overlay: {
-    flex: 1,
-    backgroundColor: 'rgba(0,0,0,0.5)',
-    justifyContent: 'flex-end',
-  },
-  container: {
-    backgroundColor: '#fff',
-    borderTopLeftRadius: 20,
-    borderTopRightRadius: 20,
-    height: '70%',
-  },
-  header: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
-    padding: 15,
-    borderBottomWidth: 1,
-    borderBottomColor: '#eee',
-  },
-  title: { fontSize: 16, fontWeight: '600', color: '#333' },
-  closeBtn: { position: 'absolute', right: 15, top: 10 },
-  closeText: { fontSize: 24, color: '#999' },
-  filterRow: {
-    flexDirection: 'row',
-    paddingHorizontal: 10,
-    paddingVertical: 10,
-    borderBottomWidth: 1,
-    borderBottomColor: '#eee',
-  },
-  filterBtn: {
-    paddingHorizontal: 12,
-    paddingVertical: 6,
-    borderRadius: 15,
-    marginRight: 8,
-    backgroundColor: '#f5f5f5',
-  },
-  filterBtnActive: { backgroundColor: '#ff6600' },
-  filterText: { fontSize: 12, color: '#666' },
-  filterTextActive: { color: '#fff' },
-  listContent: { padding: 15 },
-  recordItem: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    paddingVertical: 12,
-    borderBottomWidth: 1,
-    borderBottomColor: '#f5f5f5',
-  },
-  avatar: { width: 40, height: 40, borderRadius: 20 },
-  recordInfo: { flex: 1, marginLeft: 10 },
-  nickname: { fontSize: 14, color: '#333' },
-  time: { fontSize: 11, color: '#999', marginTop: 2 },
-  goodsInfo: { flexDirection: 'row', alignItems: 'center' },
-  goodsImage: { width: 50, height: 50, borderRadius: 5 },
-  goodsDetail: { marginLeft: 8, alignItems: 'flex-end' },
-  goodsName: { fontSize: 12, color: '#333', maxWidth: 80 },
-  levelText: { fontSize: 11, color: '#ff6600', marginTop: 2 },
-  loadingBox: { flex: 1, justifyContent: 'center', alignItems: 'center' },
-  emptyBox: { alignItems: 'center', paddingVertical: 50 },
-  emptyText: { color: '#999', fontSize: 14 },
+    overlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.5)',
+        justifyContent: 'flex-end',
+    },
+    mask: {
+        flex: 1,
+    },
+    container: {
+        height: height * 0.7,
+        paddingTop: 0,
+        backgroundColor: '#fff', 
+        borderTopLeftRadius: 15,
+        borderTopRightRadius: 15,
+        overflow: 'hidden',
+    },
+    header: {
+        height: 50,
+        justifyContent: 'center',
+        alignItems: 'center',
+        marginTop: 10,
+    },
+    title: {
+        fontSize: 18,
+        fontWeight: 'bold',
+    },
+    closeBtn: {
+        position: 'absolute',
+        right: 20,
+        top: 15,
+        width: 30,
+        height: 30,
+        alignItems: 'center',
+        justifyContent: 'center', 
+        backgroundColor: '#eee',
+        borderRadius: 15,
+    },
+    statsBanner: {
+        width: '90%',
+        alignSelf: 'center',
+        height: 50,
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+        alignItems: 'center',
+        marginBottom: 10,
+    },
+    statItem: {
+        flexDirection: 'row',
+        alignItems: 'center',
+    },
+    statNum: { 
+        fontSize: 24, 
+        fontWeight: '900', 
+        color: '#000', 
+        textShadowColor: '#fff', 
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    },
+    statLabel: { 
+        fontSize: 12, 
+        color: '#928D81', 
+        fontWeight: '300', 
+        marginHorizontal: 8 
+    },
+    statLevel: { fontSize: 12, fontWeight: 'bold' },
+    
+    tabs: {
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+        paddingHorizontal: 10,
+        marginBottom: 10,
+        backgroundColor: 'rgba(255,255,255,0.1)',
+        paddingVertical: 5,
+    },
+    tab: {
+        paddingVertical: 5,
+        paddingHorizontal: 10,
+        borderRadius: 5,
+        backgroundColor: '#eee',
+    },
+    activeTab: {
+        backgroundColor: '#FEC433',
+    },
+    tabText: { fontSize: 12, color: '#666' },
+    activeTabText: { color: '#000', fontWeight: 'bold' },
+
+    list: {
+        flex: 1,
+        paddingHorizontal: 15,
+    },
+    item: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        backgroundColor: '#FFF7E3',
+        marginBottom: 10,
+        padding: 5,
+        borderRadius: 8,
+        height: 60,
+    },
+    avatar: { width: 40, height: 40, borderRadius: 20 },
+    nickname: { flex: 1, marginLeft: 10, fontSize: 12, color: '#333' },
+    levelTag: { 
+        borderWidth: 1, 
+        paddingHorizontal: 4, 
+        borderRadius: 4, 
+        marginHorizontal: 5
+    },
+    levelText: { fontSize: 10, fontWeight: 'bold' },
+    itemName: { width: 80, fontSize: 12, color: '#FEC433', marginHorizontal: 5 },
+    countText: { fontSize: 12, color: '#000', fontWeight: 'bold', marginRight: 5 },
+    itemImage: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#fff' },
+    emptyText: { textAlign: 'center', marginTop: 20, color: '#999' }
+
 });

+ 36 - 25
app/award-detail/components/RuleModal.tsx

@@ -1,6 +1,6 @@
-import { Image } from 'expo-image';
 import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
 import {
+    Dimensions,
     ImageBackground,
     Modal,
     ScrollView,
@@ -13,6 +13,8 @@ import {
 import { Images } from '@/constants/images';
 import { getParamConfig } from '@/services/user';
 
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+
 export interface RuleModalRef {
   show: () => void;
   close: () => void;
@@ -32,9 +34,16 @@ export const RuleModal = forwardRef<RuleModalRef>((_, ref) => {
 
   const loadRules = async () => {
     try {
-      const res = await getParamConfig('show_rule');
-      // 去除HTML标签
-      const text = res?.data?.replace(/<[^>]+>/g, '') || '平台发货零门槛,全国统一运费15元/单,满五件享快递包邮服务,默认5个工作日内完成发货。';
+      const res = await getParamConfig('baoxiang_rule');
+      // 增强清洗:移除 style/script,去除 HTML 标签,并压缩多余空行
+      const rawData = res?.data || '';
+      const text = rawData
+        .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
+        .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
+        .replace(/<[^>]+>/g, '')
+        .replace(/&nbsp;/g, ' ')
+        .replace(/\n\s*\n/g, '\n') // 将多个空行压缩为一个
+        .trim() || '平台发货零门槛,全国统一运费15元/单,满五件享快递包邮服务,默认5个工作日内完成发货。';
       setRules(text);
     } catch (error) {
       console.error('加载规则失败:', error);
@@ -50,11 +59,6 @@ export const RuleModal = forwardRef<RuleModalRef>((_, ref) => {
     <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
       <View style={styles.overlay}>
         <View style={styles.container}>
-          {/* 关闭按钮 */}
-          <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
-            <Image source={{ uri: Images.common.closeBut }} style={styles.closeIcon} contentFit="contain" />
-          </TouchableOpacity>
-
           {/* 内容区域 */}
           <ImageBackground
             source={{ uri: Images.mine.dialogContentBg }}
@@ -66,6 +70,11 @@ export const RuleModal = forwardRef<RuleModalRef>((_, ref) => {
               <Text style={styles.ruleText}>{rules}</Text>
             </ScrollView>
           </ImageBackground>
+
+          {/* 关闭按钮 - 移到下方 */}
+          <TouchableOpacity onPress={() => setVisible(false)} style={styles.closeBtn}>
+            <ImageBackground source={{ uri: Images.common.closeBut }} style={styles.closeIcon} resizeMode="contain" />
+          </TouchableOpacity>
         </View>
       </View>
     </Modal>
@@ -80,27 +89,29 @@ const styles = StyleSheet.create({
     alignItems: 'center',
   },
   container: {
-    width: '90%',
+    width: '100%',
     alignItems: 'center',
+    justifyContent: 'center',
+  },
+  contentBg: {
+    width: SCREEN_WIDTH * 0.85,
+    height: SCREEN_WIDTH * 0.85 * 1.25,
+    paddingTop: 85,
+    paddingHorizontal: 35,
+    paddingBottom: 50,
+    justifyContent: 'flex-start',
+    marginBottom: 30, // 给下方按钮留出空间
   },
   closeBtn: {
-    position: 'absolute',
-    right: 15,
-    top: 20,
+    width: 45,
+    height: 45,
+    justifyContent: 'center',
+    alignItems: 'center',
     zIndex: 10,
-    width: 35,
-    height: 35,
   },
   closeIcon: {
-    width: 35,
-    height: 35,
-  },
-  contentBg: {
     width: '100%',
-    height: 400,
-    paddingTop: 75,
-    paddingHorizontal: 30,
-    paddingBottom: 30,
+    height: '100%',
   },
   title: {
     fontSize: 18,
@@ -114,7 +125,7 @@ const styles = StyleSheet.create({
   },
   ruleText: {
     fontSize: 14,
-    color: '#666',
-    lineHeight: 24,
+    color: '#333',
+    lineHeight: 20, // 降低行间距
   },
 });

+ 8 - 1
app/award-detail/index.tsx

@@ -207,7 +207,7 @@ export default function AwardDetailScreen() {
                     <View style={styles.detailsText}>
                       <Text style={styles.levelText}>{getLevelName(currentProduct.level)}</Text>
                       <Text style={styles.probabilityText}>
-                        ({ignoreRatio0(currentProduct.probability * 100)}%)
+                        ({ignoreRatio0(currentProduct.probability)}%)
                       </Text>
                     </View>
                   </ImageBackground>
@@ -255,6 +255,13 @@ export default function AwardDetailScreen() {
             </ImageBackground>
           </TouchableOpacity>
 
+          {/* 侧边按钮 - 客服 */}
+          <TouchableOpacity style={[styles.positionBut, styles.positionService]} onPress={() => kefuRef.current?.open()}>
+            <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">
+              <Text style={styles.positionButTextR}>客服</Text>
+            </ImageBackground>
+          </TouchableOpacity>
+
           {/* 侧边按钮 - 仓库 */}
           <TouchableOpacity style={[styles.positionBut, styles.positionStore]} onPress={() => router.push('/store' as any)}>
             <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">

+ 87 - 2
app/award-detail/swipe.tsx

@@ -1,9 +1,10 @@
 import { Image } from 'expo-image';
 import { useLocalSearchParams, useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
 import {
     ActivityIndicator,
     Dimensions,
+    ImageBackground,
     ScrollView,
     StatusBar,
     StyleSheet,
@@ -13,6 +14,9 @@ import {
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
+import { RecordModal, RecordModalRef } from '@/app/award-detail/components/RecordModal';
+import { RuleModal, RuleModalRef } from '@/app/award-detail/components/RuleModal';
+import { Images } from '@/constants/images';
 import { getPoolDetail } from '@/services/award';
 
 const { width: SCREEN_WIDTH } = Dimensions.get('window');
@@ -53,6 +57,11 @@ export default function AwardDetailSwipeScreen() {
   const [data, setData] = useState<PoolData | null>(null);
   const [products, setProducts] = useState<ProductItem[]>([]);
   const [currentIndex, setCurrentIndex] = useState(parseInt(index || '0', 10));
+  
+  const ruleRef = useRef<RuleModalRef>(null);
+  const recordRef = useRef<RecordModalRef>(null);
+
+  console.log(`[DEBUG-SWIPE] Init. PoolId: ${poolId}, ParamIndex: ${index}, StateIndex: ${currentIndex}`);
 
   const loadData = useCallback(async () => {
     if (!poolId) return;
@@ -127,6 +136,27 @@ export default function AwardDetailSwipeScreen() {
       </View>
 
       <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
+        <View style={styles.detailWrapper}>
+        {/* 侧边按钮 - 规则 */}
+        <TouchableOpacity style={[styles.positionBut, styles.positionRule]} onPress={() => ruleRef.current?.show()}>
+          <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
+            <Text style={styles.positionButText}>规则</Text>
+          </ImageBackground>
+        </TouchableOpacity>
+
+        {/* 侧边按钮 - 记录 */}
+        <TouchableOpacity style={[styles.positionBut, styles.positionRecord]} onPress={() => recordRef.current?.show()}>
+          <ImageBackground source={{ uri: Images.box.detail.positionBgLeft }} style={styles.positionButBg} resizeMode="contain">
+            <Text style={styles.positionButText}>记录</Text>
+          </ImageBackground>
+        </TouchableOpacity>
+
+        {/* 侧边按钮 - 仓库 */}
+        <TouchableOpacity style={[styles.positionBut, styles.positionStore]} onPress={() => router.push('/store' as any)}>
+          <ImageBackground source={{ uri: Images.box.detail.positionBgRight }} style={styles.positionButBg} resizeMode="contain">
+            <Text style={styles.positionButTextR}>仓库</Text>
+          </ImageBackground>
+        </TouchableOpacity>
         {/* 商品图片区域 */}
         <View style={styles.imageSection}>
           <Image
@@ -242,8 +272,12 @@ export default function AwardDetailSwipeScreen() {
           )}
         </View>
 
-        <View style={{ height: 50 }} />
+    </View>
       </ScrollView>
+
+      {/* 弹窗组件 */}
+      <RuleModal ref={ruleRef} />
+      <RecordModal ref={recordRef} poolId={poolId as string} />
     </View>
   );
 }
@@ -309,6 +343,57 @@ const styles = StyleSheet.create({
   },
   arrowText: { color: '#fff', fontSize: 18, fontWeight: 'bold' },
 
+  detailWrapper: {
+    position: 'relative',
+  },
+
+  // 侧边悬浮按钮
+  positionBut: {
+    position: 'absolute',
+    left: 0,
+    zIndex: 100,
+    width: 35,
+    height: 35,
+  },
+  positionRule: {
+    top: 256,
+    left: 0,
+  },
+  positionRecord: {
+    top: 300,
+    left: 0,
+  },
+  positionStore: {
+    top: 256,
+    right: 0,
+  },
+  positionButBg: {
+    width: 35,
+    height: 34,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  positionButText: {
+    color: '#fff',
+    fontSize: 12,
+    fontWeight: 'bold',
+    transform: [{ rotate: '14deg' }],
+    marginTop: -2,
+    textShadowColor: 'rgba(0,0,0,0.5)',
+    textShadowOffset: { width: 1, height: 1 },
+    textShadowRadius: 1,
+  },
+  positionButTextR: {
+    color: '#fff',
+    fontSize: 12,
+    fontWeight: 'bold',
+    transform: [{ rotate: '-16deg' }],
+    marginTop: -2,
+    textShadowColor: 'rgba(0,0,0,0.5)',
+    textShadowOffset: { width: 1, height: 1 },
+    textShadowRadius: 1,
+  },
+
   // 价格区域
   priceSection: {
     backgroundColor: '#fff',

+ 77 - 12
app/boxInBox/components/BoxPopup.tsx

@@ -21,12 +21,15 @@ export interface BoxPopupRef {
 }
 
 export const BoxPopup = forwardRef<BoxPopupRef, BoxPopupProps>(({ onSelect }, ref) => {
-  const [visible, setVisible] = useState(false);
+  const [activeBucket, setActiveBucket] = useState(0);
   const [list, setList] = useState<BoxItem[]>([]);
+  const [visible, setVisible] = useState(false);
 
   useImperativeHandle(ref, () => ({
     open: (data: BoxItem[]) => {
-      setList(data);
+      console.log('BoxPopup open with data:', data?.length);
+      setList(data || []);
+      setActiveBucket(0); // Reset to first bucket
       setVisible(true);
     },
     close: () => setVisible(false),
@@ -37,6 +40,11 @@ export const BoxPopup = forwardRef<BoxPopupRef, BoxPopupProps>(({ onSelect }, re
     setVisible(false);
   };
 
+  // Bucket logic
+  const BUCKET_SIZE = 100;
+  const buckets = Math.ceil(list.length / BUCKET_SIZE);
+  const displayList = list.slice(activeBucket * BUCKET_SIZE, (activeBucket + 1) * BUCKET_SIZE);
+
   return (
     <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
       <View style={styles.overlay}>
@@ -51,12 +59,36 @@ export const BoxPopup = forwardRef<BoxPopupRef, BoxPopupProps>(({ onSelect }, re
             </TouchableOpacity>
           </View>
 
+          {/* 区间选择器 (仅当数量超过100时显示) */}
+          {buckets > 1 && (
+            <View style={styles.bucketContainer}>
+              <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.bucketScroll}>
+                {Array.from({ length: buckets }).map((_, index) => {
+                  const start = index * BUCKET_SIZE + 1;
+                  const end = Math.min((index + 1) * BUCKET_SIZE, list.length);
+                  const isActive = activeBucket === index;
+                  return (
+                    <TouchableOpacity 
+                      key={index} 
+                      style={[styles.bucketItem, isActive && styles.bucketItemActive]} 
+                      onPress={() => setActiveBucket(index)}
+                    >
+                      <Text style={[styles.bucketText, isActive && styles.bucketTextActive]}>
+                        {start}~{end}
+                      </Text>
+                    </TouchableOpacity>
+                  );
+                })}
+              </ScrollView>
+            </View>
+          )}
+
           {/* 盒子列表 */}
           <View style={styles.content}>
             <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
               <View style={styles.grid}>
-                {list.map((item, index) => (
-                  <TouchableOpacity key={index} style={styles.item} onPress={() => handleSelect(item)}>
+                {displayList.map((item, index) => (
+                  <TouchableOpacity key={item.boxNumber} style={styles.item} onPress={() => handleSelect(item)}>
                     <View style={styles.imgBox}>
                       <Image
                         source={{ uri: item.isCompleted ? Images.box.detail.packagingBox2 : Images.box.detail.packagingBox1 }}
@@ -91,7 +123,9 @@ const styles = StyleSheet.create({
     backgroundColor: '#ffc901',
     borderTopLeftRadius: 10,
     borderTopRightRadius: 10,
-    maxHeight: '80%',
+    maxHeight: '85%', // Increased slightly
+    minHeight: 400, // Ensure popup has height even if list is empty
+    paddingBottom: 20,
   },
   titleSection: {
     position: 'relative',
@@ -123,14 +157,44 @@ const styles = StyleSheet.create({
     color: '#333',
     fontWeight: 'bold',
   },
+  // Bucket Styles
+  bucketContainer: {
+    marginBottom: 10,
+    height: 40,
+  },
+  bucketScroll: {
+    paddingHorizontal: 10,
+  },
+  bucketItem: {
+    paddingHorizontal: 15,
+    paddingVertical: 6,
+    borderRadius: 15,
+    backgroundColor: '#fff',
+    marginRight: 10,
+    borderWidth: 1,
+    borderColor: '#eee',
+    justifyContent: 'center',
+  },
+  bucketItemActive: {
+    backgroundColor: '#ff8c16',
+    borderColor: '#ff8c16',
+  },
+  bucketText: {
+    fontSize: 12,
+    color: '#333',
+    fontWeight: '500',
+  },
+  bucketTextActive: {
+    color: '#fff',
+    fontWeight: 'bold',
+  },
   content: {
     backgroundColor: '#fff',
     marginHorizontal: 10,
-    marginBottom: 20,
     borderRadius: 13,
     borderWidth: 1,
     borderColor: '#000',
-    maxHeight: 470,
+    flex: 1, // Let it take remaining space
   },
   scrollView: {
     padding: 10,
@@ -138,6 +202,7 @@ const styles = StyleSheet.create({
   grid: {
     flexDirection: 'row',
     flexWrap: 'wrap',
+    paddingBottom: 20,
   },
   item: {
     width: '25%',
@@ -145,8 +210,8 @@ const styles = StyleSheet.create({
     marginBottom: 15,
   },
   imgBox: {
-    width: 54,
-    height: 54,
+    width: 50, // Slightly smaller to fit better
+    height: 50,
   },
   boxImg: {
     width: '100%',
@@ -155,17 +220,17 @@ const styles = StyleSheet.create({
   numBadge: {
     backgroundColor: '#959595',
     borderRadius: 15,
-    paddingHorizontal: 10,
+    paddingHorizontal: 8,
     paddingVertical: 2,
     marginTop: 5,
   },
   numText: {
     color: '#fff',
-    fontSize: 12,
+    fontSize: 10, // Smaller font
     fontWeight: '500',
   },
   remaining: {
-    fontSize: 12,
+    fontSize: 10,
     color: '#999',
     fontWeight: 'bold',
     marginTop: 3,

+ 10 - 9
app/boxInBox/index.tsx

@@ -36,6 +36,8 @@ interface PoolData {
   price: number;
   specialPrice?: number;
   bigBoxPrizes?: ProductItem[];
+  mediumBoxPrizes?: ProductItem[];
+  smallBoxPrizes?: ProductItem[];
   activityGoods?: any[];
 }
 
@@ -186,14 +188,13 @@ export default function BoxInBoxScreen() {
 
   // 打开换盒弹窗
   const openBoxPopup = useCallback(async () => {
-    if (!poolId) return;
-    try {
-      const res = await getBoxHistory(poolId);
-      if (res && res.length > 0) {
-        boxPopupRef.current?.open(res);
-      }
-    } catch {}
-  }, [poolId]);
+    console.log('Opening Box Popup, history len:', boxHistory?.length);
+    if (!boxHistory || boxHistory.length === 0) {
+      Alert.alert('提示', '暂无盒子数据');
+      return;
+    }
+    boxPopupRef.current?.open(boxHistory);
+  }, [boxHistory]);
 
   // 选择盒子
   const handleSelectBox = useCallback((item: any) => {
@@ -601,7 +602,7 @@ const styles = StyleSheet.create({
   positionStore: { top: 256, right: 0 },
   positionRefresh: { top: 300, right: 0 },
 
-  emptyRunsBox: { alignItems: 'center', marginTop: -60, marginBottom: 10 },
+  emptyRunsBox: { alignItems: 'flex-start', marginLeft: 10, marginTop: -40, marginBottom: 10, zIndex: 20 },
   emptyRunsText: { color: '#fff', fontSize: 12, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 15, paddingVertical: 5, borderRadius: 10 },
 
   lockTimeBox: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#71ccff', padding: 10, marginHorizontal: 10, borderRadius: 8, marginBottom: 10 },

+ 46 - 43
app/coupon/index.tsx

@@ -53,15 +53,13 @@ export default function CouponScreen() {
     };
 
     const handleUse = (item: CouponItem) => {
-        if (item.scene === 'LUCK') {
-            // Go to Box/Award
-            router.push('/box' as any); // Adapt route as needed
-        } else if (item.scene === 'MALL') {
-            // Go to Mall/VIP usually
-            router.push('/(tabs)' as any);
+        // Original behavior: goAward() -> /pages/award/index (Box Screen)
+        // We replicate this 1:1, or maintain specific routing if mall requires it.
+        // Given request for 1:1, we prioritize Box screen.
+        if (item.scene === 'MALL') {
+             router.push('/(tabs)/store' as any);
         } else {
-            // Default
-            router.push('/(tabs)' as any);
+             router.push('/(tabs)/box' as any);
         }
     };
 
@@ -90,40 +88,45 @@ export default function CouponScreen() {
                     contentContainerStyle={{ paddingTop: insets.top + 50, paddingBottom: 50, paddingHorizontal: 10 }}
                 >
                     {list.length > 0 ? (
-                        list.map((item, index) => (
-                            <ImageBackground
-                                key={index}
-                                source={{ uri: Images.mine.couponBg }}
-                                style={[styles.couponItem, item.status !== 1 && styles.grayscale]}
-                                resizeMode="stretch"
-                            >
-                                <View style={styles.left}>
-                                    <View style={styles.amountBox}>
-                                        <Text style={styles.symbol}>¥</Text>
-                                        <Text style={styles.amount}>{item.amount}</Text>
-                                    </View>
-                                    <Text style={styles.fullAmount}>满{item.fullAmount}可用</Text>
-                                </View>
-
-                                <View style={styles.divider} />
-
-                                <View style={styles.right}>
-                                    <View style={styles.info}>
-                                        <Text style={styles.name}>{item.name}</Text>
-                                        <Text style={styles.time}>{formatTime(item.endTime)}过期</Text>
-                                    </View>
-                                    <TouchableOpacity onPress={() => handleUse(item)}>
-                                        <ImageBackground
-                                            source={{ uri: Images.mine.couponItemButBg }}
-                                            style={styles.useBtn}
-                                            resizeMode="contain"
-                                        >
-                                            <Text style={styles.useText}>立即使用</Text>
-                                        </ImageBackground>
-                                    </TouchableOpacity>
-                                </View>
-                            </ImageBackground>
-                        ))
+                            list.map((item, index) => (
+                                <TouchableOpacity 
+                                    key={index}
+                                    activeOpacity={0.8}
+                                    onPress={() => handleUse(item)}
+                                >
+                                    <ImageBackground
+                                        source={{ uri: Images.mine.couponBg }}
+                                        style={[styles.couponItem, item.status !== 1 && styles.grayscale]}
+                                        resizeMode="stretch"
+                                    >
+                                        <View style={styles.left}>
+                                            <View style={styles.amountBox}>
+                                                <Text style={styles.symbol}>¥</Text>
+                                                <Text style={styles.amount}>{item.amount}</Text>
+                                            </View>
+                                            <Text style={styles.fullAmount}>满{item.fullAmount}可用</Text>
+                                        </View>
+ 
+                                        <View style={styles.divider} />
+ 
+                                        <View style={styles.right}>
+                                            <View style={styles.info}>
+                                                <Text style={styles.name}>{item.name}</Text>
+                                                <Text style={styles.time}>{formatTime(item.endTime)}过期</Text>
+                                            </View>
+                                            <View>
+                                                <ImageBackground
+                                                    source={{ uri: Images.mine.couponItemButBg }}
+                                                    style={styles.useBtn}
+                                                    resizeMode="contain"
+                                                >
+                                                    <Text style={styles.useText}>立即使用</Text>
+                                                </ImageBackground>
+                                            </View>
+                                        </View>
+                                    </ImageBackground>
+                                </TouchableOpacity>
+                            ))
                     ) : (
                         <View style={styles.emptyBox}>
                             <Text style={styles.emptyText}>暂无优惠券</Text>
@@ -209,7 +212,7 @@ const styles = StyleSheet.create({
         height: 24,
         backgroundColor: '#C3B3DF',
         marginHorizontal: 16,
-        opacity: 0, // Hidden in layout if image has it, just spacing
+        // opacity: 0, // Removed to match original design
     },
     right: {
         flex: 1,

+ 0 - 9
app/integral/_layout.tsx

@@ -1,9 +0,0 @@
-import { Stack } from 'expo-router';
-
-export default function IntegralLayout() {
-  return (
-    <Stack screenOptions={{ headerShown: false }}>
-      <Stack.Screen name="index" />
-    </Stack>
-  );
-}

+ 0 - 682
app/integral/index.tsx

@@ -1,682 +0,0 @@
-import { Image } from 'expo-image';
-import { useRouter } from 'expo-router';
-import React, { useCallback, useEffect, useState } from 'react';
-import {
-    Alert,
-    ImageBackground,
-    ScrollView,
-    StatusBar,
-    StyleSheet,
-    Text,
-    TouchableOpacity,
-    View,
-} from 'react-native';
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
-
-import { Images } from '@/constants/images';
-import services from '@/services/api';
-
-interface DayInfo {
-  text: string;
-  date: string;
-  isFutureDate: boolean;
-  istoday: boolean;
-  isSignIn: boolean;
-}
-
-interface RecordItem {
-  id: string;
-  credit: number;
-  createTime: string;
-}
-
-export default function IntegralScreen() {
-  const router = useRouter();
-  const insets = useSafeAreaInsets();
-  
-  const [integral, setIntegral] = useState(0);
-  const [daysIntegral, setDaysIntegral] = useState(0);
-  const [istodaySignIn, setIstodaySignIn] = useState(false);
-  const [record, setRecord] = useState<RecordItem[]>([]);
-  const [dataInfo, setDataInfo] = useState<DayInfo[]>([]);
-  const [todayIntegral, setTodayIntegral] = useState(1);
-  const [tomorrowIntegral, setTomorrowIntegral] = useState(1);
-  const [isTooltip, setIsTooltip] = useState(false);
-
-  // 格式化数字为两位
-  const padWithZeros = (number: number, length: number) => {
-    return String(number).padStart(length, '0');
-  };
-
-  // 设置日期数据
-  const setDate = useCallback(() => {
-    const now = new Date();
-    const dataInfoArr: DayInfo[] = [];
-
-    // 前4天
-    for (let i = -4; i <= 0; i++) {
-      const date = new Date(now);
-      date.setDate(date.getDate() + i);
-      const m = date.getMonth() + 1;
-      const d = date.getDate();
-      const y = date.getFullYear();
-      
-      dataInfoArr.push({
-        text: `${m}.${d}`,
-        date: `${y}-${padWithZeros(m, 2)}-${padWithZeros(d, 2)}`,
-        isFutureDate: false,
-        istoday: i === 0,
-        isSignIn: false,
-      });
-    }
-
-    // 明天
-    const tomorrow = new Date(now);
-    tomorrow.setDate(tomorrow.getDate() + 1);
-    const tm = tomorrow.getMonth() + 1;
-    const td = tomorrow.getDate();
-    const ty = tomorrow.getFullYear();
-    
-    dataInfoArr.push({
-      text: `${tm}.${td}`,
-      date: `${ty}-${padWithZeros(tm, 2)}-${padWithZeros(td, 2)}`,
-      isFutureDate: true,
-      istoday: false,
-      isSignIn: false,
-    });
-
-    setDataInfo(dataInfoArr);
-    return dataInfoArr;
-  }, []);
-
-  // 获取积分信息
-  const getInfo = useCallback(async () => {
-    try {
-      // Use wallet service info instead of user service getWalletInfo
-      const info = await services.wallet.info('USER_CREDIT');
-      setIntegral(info?.balance || 0);
-    } catch (error) {
-      console.error('获取积分失败:', error);
-    }
-  }, []);
-
-  // 获取积分规则
-  const showRule = async () => {
-    try {
-      const res = await services.user.getParamConfig('credit_sign');
-      if (res && res.data) {
-        Alert.alert('积分规则', res.data.replace(/<[^>]+>/g, '')); // Strip HTML tags for Alert
-      }
-    } catch (error) {
-       console.error('获取规则失败:', error);
-    }
-  };
-
-  // 获取签到记录
-  const getData = useCallback(async (dateInfo: DayInfo[]) => {
-    try {
-      const fourDaysAgo = new Date();
-      fourDaysAgo.setDate(fourDaysAgo.getDate() - 4);
-      const m = fourDaysAgo.getMonth() + 1;
-      const d = fourDaysAgo.getDate();
-      const y = fourDaysAgo.getFullYear();
-      
-      const param = {
-        createTime: `${y}-${padWithZeros(m, 2)}-${padWithZeros(d, 2)}`,
-      };
-
-      const res = await services.user.getCreditRecord(param);
-      const recordData = res?.data || [];
-      setRecord(recordData);
-
-      // 今天日期
-      const now = new Date();
-      const todayStr = `${now.getFullYear()}-${padWithZeros(now.getMonth() + 1, 2)}-${padWithZeros(now.getDate(), 2)}`;
-
-      // 更新签到状态
-      const updatedDataInfo = dateInfo.map(item => {
-        const newItem = { ...item };
-        
-        // 检查是否已签到
-        for (const rec of recordData) {
-          const createTime = rec.createTime?.slice(0, 10);
-          if (createTime === item.date) {
-            newItem.isSignIn = true;
-            if (item.date === todayStr) {
-              setIstodaySignIn(true);
-            }
-          }
-        }
-        
-        return newItem;
-      });
-
-      setDataInfo(updatedDataInfo);
-
-      // 计算签到积分
-      let total = 0;
-      for (const rec of recordData) {
-        if (rec.credit > 0) {
-          total += rec.credit;
-        }
-      }
-      setDaysIntegral(total);
-
-      // 计算今天和明天的积分
-      if (recordData.length > 0) {
-        const lastCredit = recordData[0]?.credit || 1;
-        const yesterday = new Date();
-        yesterday.setDate(yesterday.getDate() - 1);
-        const yesterdayStr = `${yesterday.getFullYear()}-${padWithZeros(yesterday.getMonth() + 1, 2)}-${padWithZeros(yesterday.getDate(), 2)}`;
-        const lastRecordDate = recordData[0]?.createTime?.slice(0, 10);
-
-        if (yesterdayStr === lastRecordDate) {
-          setTodayIntegral(lastCredit + 1);
-          setTomorrowIntegral(lastCredit + 2);
-        } else {
-          setTodayIntegral(lastCredit);
-          setTomorrowIntegral(Math.min(lastCredit + 1, 7));
-        }
-      }
-    } catch (error) {
-      console.error('获取签到记录失败:', error);
-    }
-  }, []);
-
-  // 初始化
-  useEffect(() => {
-    const dateInfo = setDate();
-    getInfo();
-    getData(dateInfo);
-  }, [setDate, getInfo, getData]);
-
-  // 签到
-  const handleSignIn = async () => {
-    if (istodaySignIn) {
-      Alert.alert('提示', '今天已签到');
-      return;
-    }
-
-    try {
-      const res = await services.user.signIn();
-      if (res?.code === 0) {
-        Alert.alert('提示', '签到成功');
-        setIstodaySignIn(true);
-        
-        // Optimistic update for UI
-        const optimisticDateInfo = dataInfo.map(item => {
-             if (item.istoday) {
-                 return { ...item, isSignIn: true };
-             }
-             return item;
-        });
-        setDataInfo(optimisticDateInfo);
-
-        // Delay fetch to ensure backend data consistency
-        setTimeout(() => {
-            const dateInfo = setDate();
-            getInfo();
-            getData(dateInfo);
-        }, 500);
-      } else {
-        Alert.alert('提示', res?.msg || '签到失败');
-      }
-    } catch (error) {
-      console.error('签到失败:', error);
-      Alert.alert('提示', '签到失败');
-    }
-  };
-
-  const handleBack = () => {
-    router.back();
-  };
-
-  return (
-    <View style={styles.container}>
-      <StatusBar barStyle="dark-content" />
-      
-      {/* 顶部导航 */}
-      <View style={[styles.header, { paddingTop: insets.top }]}>
-        <TouchableOpacity style={styles.backBtn} onPress={handleBack}>
-          <Text style={styles.backIcon}>‹</Text>
-        </TouchableOpacity>
-        <Text style={styles.title}>我的积分</Text>
-        <View style={styles.placeholder} />
-      </View>
-
-      <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
-        {/* 头部积分展示 */}
-        <ImageBackground
-          source={{ uri: Images.integral?.head || Images.common.commonBg }}
-          style={styles.headerBg}
-          resizeMode="cover"
-        >
-          <Text style={styles.integralNum}>{integral}</Text>
-          <TouchableOpacity 
-            style={styles.allBox}
-            onPress={() => setIsTooltip(!isTooltip)}
-          >
-            <Text style={styles.allText}>所有积分</Text>
-            <Image
-              source={{ uri: Images.integral?.greetings }}
-              style={styles.infoIcon}
-              contentFit="contain"
-            />
-            {isTooltip && (
-                <ImageBackground 
-                    source={{uri: Images.integral?.tooltip }} 
-                    style={styles.tooltip}
-                    resizeMode="stretch"
-                >
-                    <Text style={styles.tooltipText}>消费积分与签到积分总和</Text>
-                </ImageBackground>
-            )}
-          </TouchableOpacity>
-          <View style={styles.todayBox}>
-            <Text style={styles.todayText}>
-              今日已获得<Text style={styles.todayNum}>{istodaySignIn ? todayIntegral : 0}</Text>积分
-            </Text>
-          </View>
-          
-          <TouchableOpacity style={styles.ruleBtn} onPress={showRule}>
-             <Text style={styles.ruleText}>积分规则</Text>
-          </TouchableOpacity>
-        </ImageBackground>
-
-        <View style={styles.content}>
-          {/* 签到面板 */}
-          <View style={styles.panel}>
-            <View style={styles.panelTitle}>
-              <View style={styles.panelTitleLeft}>
-                <Image
-                  source={{ uri: Images.integral?.goldCoins }}
-                  style={styles.goldIcon}
-                  contentFit="contain"
-                />
-                <Text style={styles.panelTitleText}>签到领积分</Text>
-              </View>
-              <Text style={[styles.panelTitleRight, istodaySignIn && styles.signedText]}>
-                {istodaySignIn ? '今日已签到' : '今日未签到'}
-              </Text>
-            </View>
-
-            <Text style={styles.explain}>
-              已签到{record.filter(r => r.credit > 0).length}天,共获得<Text style={styles.highlightText}>{daysIntegral}</Text>积分
-            </Text>
-
-            {/* 签到进度 */}
-            <View style={styles.progressBox}>
-              <View style={styles.lineBox}>
-                {[1, 2, 3, 4, 5].map((_, index) => (
-                  <View key={index} style={styles.lineSection}>
-                    <View style={[styles.line, styles.lineOn]} />
-                  </View>
-                ))}
-              </View>
-
-              <View style={styles.daysBox}>
-                {dataInfo.map((item, index) => (
-                  <View key={index} style={styles.dayItem}>
-                    <TouchableOpacity
-                      style={styles.dayCircleBox}
-                      onPress={item.istoday && !istodaySignIn ? handleSignIn : undefined}
-                      activeOpacity={item.istoday && !istodaySignIn ? 0.7 : 1}
-                    >
-                      {item.istoday && !istodaySignIn ? (
-                        <ImageBackground
-                          source={{ uri: Images.integral?.basisBg }}
-                          style={styles.dayCircleBg}
-                          resizeMode="contain"
-                        >
-                          <View style={[styles.dayCircle, styles.todayCircle]}>
-                            <Text style={styles.todayCircleText}>+{todayIntegral}</Text>
-                          </View>
-                        </ImageBackground>
-                      ) : item.isFutureDate ? (
-                        <View style={[styles.dayCircle, styles.futureCircle]}>
-                          <Text style={styles.futureText}>+{tomorrowIntegral}</Text>
-                        </View>
-                      ) : item.isSignIn ? (
-                        <View style={[styles.dayCircle, styles.signedCircle]}>
-                          <Text style={styles.checkIcon}>✓</Text>
-                        </View>
-                      ) : (
-                        <View style={[styles.dayCircle, styles.missedCircle]}>
-                          <Text style={styles.missedIcon}>✗</Text>
-                        </View>
-                      )}
-                    </TouchableOpacity>
-                    <Text style={[styles.dayText, item.istoday && styles.todayDayText]}>
-                      {item.istoday ? '今天' : item.text}
-                    </Text>
-                  </View>
-                ))}
-              </View>
-            </View>
-          </View>
-
-          {/* 积分明细 */}
-          <View style={styles.listSection}>
-            <Text style={styles.listTitle}>积分明细</Text>
-            {record.map((item, index) => (
-              <View key={item.id || index} style={styles.listItem}>
-                <View style={styles.listItemLeft}>
-                  <Text style={styles.listItemTitle}>
-                    {item.credit > 0 ? '积分签到获得' : '大转盘消费'}
-                  </Text>
-                  <Text style={styles.listItemTime}>{item.createTime}</Text>
-                </View>
-                <Text style={[
-                  styles.listItemCredit,
-                  item.credit > 0 ? styles.creditPositive : styles.creditNegative
-                ]}>
-                  {item.credit > 0 ? '+' : ''}{item.credit}
-                </Text>
-              </View>
-            ))}
-            {record.length === 0 && (
-              <View style={styles.emptyBox}>
-                <Text style={styles.emptyText}>暂无积分记录</Text>
-              </View>
-            )}
-          </View>
-        </View>
-      </ScrollView>
-    </View>
-  );
-}
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    backgroundColor: '#F8FAFB',
-  },
-  header: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'space-between',
-    paddingHorizontal: 10,
-    height: 80,
-    backgroundColor: 'transparent',
-    position: 'absolute',
-    top: 0,
-    left: 0,
-    right: 0,
-    zIndex: 100,
-  },
-  backBtn: {
-    width: 40,
-    height: 40,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  backIcon: {
-    fontSize: 32,
-    color: '#000',
-    fontWeight: 'bold',
-  },
-  title: {
-    fontSize: 15,
-    fontWeight: 'bold',
-    color: '#000',
-  },
-  placeholder: {
-    width: 40,
-  },
-  scrollView: {
-    flex: 1,
-  },
-  headerBg: {
-    width: '100%',
-    height: 283,
-    paddingTop: 100,
-    alignItems: 'center',
-    position: 'relative',
-  },
-  integralNum: {
-    fontSize: 48,
-    fontWeight: '400',
-    color: '#5B460F',
-    textAlign: 'center',
-  },
-  allBox: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    marginTop: 7,
-    marginBottom: 12,
-    position: 'relative',
-  },
-  allText: {
-    fontSize: 12,
-    color: '#C8B177',
-  },
-  infoIcon: {
-    width: 16,
-    height: 16,
-    marginLeft: 4,
-  },
-  tooltip: {
-      position: 'absolute',
-      width: 150,
-      height: 27,
-      top: 15,
-      left: 30, // Adjust execution based on design
-      justifyContent: 'center',
-      alignItems: 'center',
-      zIndex: 999,
-      paddingHorizontal: 5,
-  },
-  tooltipText: {
-      color: '#fff',
-      fontSize: 10,
-  },
-  todayBox: {
-    backgroundColor: 'rgba(0,0,0,0.08)',
-    borderRadius: 217,
-    paddingHorizontal: 15,
-    paddingVertical: 5,
-  },
-  todayText: {
-    fontSize: 12,
-    color: '#8A794F',
-  },
-  todayNum: {
-    fontWeight: '800',
-    color: '#5B460F',
-  },
-  ruleBtn: {
-    position: 'absolute',
-    right: 0,
-    top: 130, // Half of 260rpx approx
-    backgroundColor: '#fff',
-    borderTopLeftRadius: 20,
-    borderBottomLeftRadius: 20,
-    paddingHorizontal: 10,
-    paddingVertical: 6,
-    zIndex: 10,
-  },
-  ruleText: {
-    fontSize: 12,
-    color: '#333',
-  },
-  content: {
-    paddingHorizontal: 10,
-    marginTop: -50,
-  },
-  panel: {
-    backgroundColor: '#fff',
-    borderRadius: 15,
-    padding: 12,
-  },
-  panelTitle: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    paddingHorizontal: 10,
-  },
-  panelTitleLeft: {
-    flexDirection: 'row',
-    alignItems: 'center',
-  },
-  goldIcon: {
-    width: 24,
-    height: 24,
-    marginRight: 6,
-  },
-  panelTitleText: {
-    fontSize: 16,
-    fontWeight: '700',
-    color: '#3D3D3D',
-  },
-  panelTitleRight: {
-    fontSize: 12,
-    color: '#FC7D2E',
-  },
-  signedText: {
-    color: '#999',
-  },
-  explain: {
-    fontSize: 12,
-    color: '#999',
-    paddingHorizontal: 10,
-    marginTop: 7,
-    marginBottom: 11,
-  },
-  highlightText: {
-    color: '#FC7D2E',
-  },
-  progressBox: {
-    paddingTop: 20,
-  },
-  lineBox: {
-    flexDirection: 'row',
-    paddingHorizontal: 10,
-  },
-  lineSection: {
-    flex: 1,
-  },
-  line: {
-    height: 1,
-    backgroundColor: '#9E9E9E',
-  },
-  lineOn: {
-    backgroundColor: '#FC7D2E',
-  },
-  daysBox: {
-    flexDirection: 'row',
-    marginTop: -25,
-  },
-  dayItem: {
-    flex: 1,
-    alignItems: 'center',
-  },
-  dayCircleBox: {
-    width: 46,
-    height: 46,
-    justifyContent: 'center',
-    alignItems: 'center',
-    marginBottom: 3,
-  },
-  dayCircleBg: {
-    width: 46,
-    height: 46,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  dayCircle: {
-    width: 32,
-    height: 32,
-    borderRadius: 16,
-    justifyContent: 'center',
-    alignItems: 'center',
-  },
-  todayCircle: {
-    backgroundColor: '#FC7D2E',
-  },
-  todayCircleText: {
-    color: '#fff',
-    fontSize: 12,
-    fontWeight: 'bold',
-  },
-  futureCircle: {
-    backgroundColor: '#F4F6F8',
-    borderWidth: 1,
-    borderColor: 'rgba(226,226,226,0.5)',
-  },
-  futureText: {
-    color: '#505050',
-    fontSize: 12,
-  },
-  signedCircle: {
-    backgroundColor: '#FFE7C4',
-  },
-  checkIcon: {
-    color: '#FC7D2E',
-    fontSize: 16,
-    fontWeight: 'bold',
-  },
-  missedCircle: {
-    backgroundColor: '#F4F6F8',
-  },
-  missedIcon: {
-    color: '#999',
-    fontSize: 16,
-  },
-  dayText: {
-    fontSize: 12,
-    color: '#9E9E9E',
-  },
-  todayDayText: {
-    color: '#FC7D2E',
-  },
-  listSection: {
-    backgroundColor: '#fff',
-    borderRadius: 15,
-    padding: 12,
-    marginTop: 10,
-    marginBottom: 30,
-  },
-  listTitle: {
-    fontSize: 16,
-    fontWeight: '700',
-    color: '#3D3D3D',
-    marginBottom: 10,
-  },
-  listItem: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    paddingVertical: 14,
-    borderBottomWidth: 1,
-    borderBottomColor: 'rgba(0,0,0,0.05)',
-  },
-  listItemLeft: {},
-  listItemTitle: {
-    fontSize: 14,
-    color: '#333',
-    marginBottom: 3,
-  },
-  listItemTime: {
-    fontSize: 12,
-    color: '#999',
-  },
-  listItemCredit: {
-    fontSize: 18,
-    fontWeight: '700',
-  },
-  creditPositive: {
-    color: '#588CFF',
-  },
-  creditNegative: {
-    color: '#FC7D2E',
-  },
-  emptyBox: {
-    paddingVertical: 50,
-    alignItems: 'center',
-  },
-  emptyText: {
-    fontSize: 14,
-    color: '#999',
-  },
-});
-

+ 7 - 0
app/lottery/_layout.tsx

@@ -0,0 +1,7 @@
+import { Stack } from 'expo-router';
+
+export default function Layout() {
+  return (
+    <Stack screenOptions={{ headerShown: false }} />
+  );
+}

+ 5 - 5
app/lottery/components/LotteryReel.tsx

@@ -107,11 +107,11 @@ export default function LotteryReel({
            <View key={item.uniqueKey} style={[styles.itemWrapper, { width: itemWidth }]}>
              <TransCard 
                 item={item} 
-                width={itemWidth === 94 ? 90 : itemWidth - 4} 
-                height={itemWidth === 94 ? 121 : 90}
-                fill={itemWidth === 94 ? 2 : 2}
-                imageWidth={itemWidth === 94 ? 74 : 55}
-                imageHeight={itemWidth === 94 ? 94 : 70}
+                width={itemWidth > 100 ? itemWidth - 10 : (itemWidth === 94 ? 90 : itemWidth - 4)} 
+                height={itemWidth > 100 ? (itemWidth - 10) * 1.35 : (itemWidth === 94 ? 121 : 90)}
+                fill={2}
+                imageWidth={itemWidth > 100 ? (itemWidth - 10) * 0.82 : (itemWidth === 94 ? 74 : 55)}
+                imageHeight={itemWidth > 100 ? ((itemWidth - 10) * 1.35) * 0.77 : (itemWidth === 94 ? 94 : 70)}
              />
            </View>
         ))}

+ 96 - 18
app/lottery/index.tsx

@@ -1,4 +1,4 @@
-import { Audio } from 'expo-av';
+import { Audio, AVPlaybackStatus, ResizeMode, Video } from 'expo-av';
 import { Image } from 'expo-image';
 import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
 import React, { useEffect, useRef, useState } from 'react';
@@ -15,6 +15,7 @@ const { width: SCREEN_WIDTH } = Dimensions.get('window');
 
 // Sound URL from Uniapp config
 const SOUND_URL = 'https://cdn.acetoys.cn/kai_xin_ma_te/resource/magic/lottery.mp3';
+const DEFAULT_JACKPOT_VIDEO = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart/box/lottery/jackpot.mp4';
 
 export default function LotteryScreen() {
   const router = useRouter();
@@ -33,15 +34,22 @@ export default function LotteryScreen() {
   const [sound, setSound] = useState<Audio.Sound>();
   const [animationFinished, setAnimationFinished] = useState(false);
   
+  // Video state
+  const [videoVisible, setVideoVisible] = useState(false);
+  const [videoUrl, setVideoUrl] = useState('');
+  const videoRef = useRef<Video>(null);
+  
   // Timer Ref for cleanup
   const timerRef = useRef<any>(null);
 
   // Layout calculations
-  const itemWidth = (num === 1) ? 94 : 72; // Adjusted to 72 to match mask calculation exactly (was 71)
-  // Uniapp width logic:
-  // width() { return (screenWidth - (this.num == 1 ? 94 : 72)) / 2 }
-  const maskWidth = (SCREEN_WIDTH - ((num === 1) ? 94 : 72)) / 2; 
+  const singleItemWidth = 240;
+  const itemWidth = (num === 1) ? singleItemWidth : 72; 
+  const maskWidth = (SCREEN_WIDTH - ((num === 1) ? singleItemWidth : 72)) / 2; 
   const padding = SCREEN_WIDTH > 375 ? 32 : 0; 
+  
+  // Dynamic styles
+  const singleReelHeight = singleItemWidth * 1.35 + 20; // 324 + 20
 
   useEffect(() => {
     init();
@@ -50,11 +58,17 @@ export default function LotteryScreen() {
       if (timerRef.current) clearTimeout(timerRef.current);
     };
   }, []);
+  
+  // ... (rest of the file)
+  
+  // Update render styles inline or via StyleSheet
+  // I will update the stylesheet usage in return and the StyleSheet definition below
+
 
   const init = async () => {
       try {
           console.log('LotteryScreen Init: num', num, 'tradeNo', tradeNo, 'poolId', poolId);
-          await Promise.all([loadData(), playSound()]);
+          await Promise.all([loadData() /*, playSound()*/]);
       } catch (e) {
           console.error('LotteryScreen Init Error', e);
       } finally {
@@ -93,11 +107,23 @@ export default function LotteryScreen() {
              // Auto show result after animation
              if (timerRef.current) clearTimeout(timerRef.current);
              
-             if (!isGrid) {
-                // Reel mode uses timer
-                timerRef.current = setTimeout(() => {
-                    handleFinish(list);
-                }, 2800); 
+             // Check for Level A video
+             const hasLevelA = list.some((item: any) => ['A', 'a'].includes(item.level));
+             // Also check res.video if available
+             const playVideoUrl = res.video || (hasLevelA ? DEFAULT_JACKPOT_VIDEO : '');
+
+             if (playVideoUrl) {
+                 console.log('Found Level A, preparing video:', playVideoUrl);
+                 setVideoUrl(playVideoUrl);
+                 setVideoVisible(true);
+                 // Do NOT set timer here, wait for video to finish
+             } else {
+                 if (!isGrid) {
+                    // Reel mode uses timer
+                    timerRef.current = setTimeout(() => {
+                        handleFinish(list);
+                    }, 2800); 
+                 }
              }
              // Grid mode handles finish via callback
          } catch (error) {
@@ -134,6 +160,12 @@ export default function LotteryScreen() {
   };
   
   const handleSkip = () => {
+      if (videoVisible) {
+          setVideoVisible(false);
+          handleFinish();
+          return;
+      }
+
       if (isGrid) {
           if (animationFinished) {
               // User clicked "Claim Prize", exit
@@ -177,7 +209,13 @@ export default function LotteryScreen() {
                 {/* Reels */}
                 <View style={num === 1 ? styles.height1 : styles.height5}>
                     {results.map((item, index) => (
-                        <View key={index} style={styles.reelRow}> 
+                        <View 
+                            key={index} 
+                            style={[
+                                styles.reelRow, 
+                                num === 1 && { height: singleReelHeight, marginBottom: 0 }
+                            ]}
+                        > 
                             <LotteryReel 
                                 key={`reel-${index}-${pool.length}-${results.length}`} 
                                 pool={pool.length > 0 ? pool : results} 
@@ -201,8 +239,8 @@ export default function LotteryScreen() {
                     style={[
                         styles.middleImage, 
                         { 
-                            height: num === 1 ? 200 : 540, 
-                            top: num === 1 ? -35 : -10,
+                            height: num === 1 ? 360 : 540, 
+                            top: num === 1 ? -20 : -10,
                         }
                     ]}
                     contentFit="contain"
@@ -211,13 +249,43 @@ export default function LotteryScreen() {
          )}
       </View>
 
+
+      {/* Video Overlay */}
+      {videoVisible && videoUrl ? (
+        <View style={styles.videoContainer}>
+            <Video
+                ref={videoRef}
+                key={videoUrl}
+                source={{ uri: videoUrl }}
+                style={styles.video}
+                resizeMode={ResizeMode.COVER}
+                shouldPlay
+                isLooping={false}
+                useNativeControls={false}
+                onLoad={() => {
+                    videoRef.current?.playAsync();
+                }}
+                onPlaybackStatusUpdate={(status: AVPlaybackStatus) => {
+                    if (status.isLoaded && status.didJustFinish) {
+                         setVideoVisible(false);
+                         handleFinish();
+                    }
+                }}
+                onError={() => {
+                    setVideoVisible(false);
+                    handleFinish();
+                }}
+            />
+        </View>
+      ) : null}
+
       <View style={styles.bottom}>
         <TouchableOpacity 
             style={styles.skipBtn} 
             onPress={handleSkip}
         >
             <Text style={styles.skipText}>
-                {isGrid && animationFinished ? '收下奖品' : '跳过动画'}
+                {videoVisible ? '跳过动画' : (isGrid && animationFinished ? '收下奖品' : '跳过动画')}
             </Text>
         </TouchableOpacity>
       </View>
@@ -245,7 +313,8 @@ const styles = StyleSheet.create({
     flex: 1,
     // justifyContent: 'center', 
     alignItems: 'center',
-    marginTop: 100, // Adjust based on visual
+    marginTop: 0, // Remove fixed margin, let flex center it or use justify center if needed
+    justifyContent: 'center', // Center vertically
   },
   reelsContainer: {
     position: 'relative',
@@ -262,7 +331,7 @@ const styles = StyleSheet.create({
       height: 94, // Height of one row (card height + tiny padding)
   },
   height1: {
-      height: 130, // 1 row + gaps
+      height: 360, // Accommodate larger card (approx 325px height)
       justifyContent: 'center',
   },
   height5: {
@@ -299,7 +368,7 @@ const styles = StyleSheet.create({
     bottom: 50,
      width: '100%',
      alignItems: 'center',
-     zIndex: 30,
+     zIndex: 60,
   },
   skipBtn: {
       backgroundColor: 'rgba(255,255,255,0.2)',
@@ -313,4 +382,13 @@ const styles = StyleSheet.create({
       color: '#fff',
       fontSize: 14,
   },
+  videoContainer: {
+      ...StyleSheet.absoluteFillObject,
+      backgroundColor: '#000',
+      zIndex: 50,
+  },
+  video: {
+      width: '100%',
+      height: '100%',
+  },
 });

+ 2 - 1
app/lottery/result.tsx

@@ -137,7 +137,8 @@ const styles = StyleSheet.create({
       textAlign: 'center',
   },
   button: {
-      marginTop: 20,
+      position: 'absolute',
+      bottom: 30,
       backgroundColor: '#F62C71',
       paddingHorizontal: 40,
       paddingVertical: 10,

+ 276 - 126
app/orders/index.tsx

@@ -1,5 +1,5 @@
 import { Image } from 'expo-image';
-import { useRouter } from 'expo-router';
+import { useLocalSearchParams, useRouter } from 'expo-router';
 import React, { useCallback, useEffect, useState } from 'react';
 import {
     ActivityIndicator,
@@ -15,29 +15,57 @@ import {
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
 import { Images } from '@/constants/images';
-import { getOrders, OrderItem } from '@/services/mall';
+import { getAwardOrders } from '@/services/award';
+
+const LEVEL_MAP: Record<string, { title: string; color: string }> = {
+  A: { title: '超神', color: '#ff0000' },
+  B: { title: '欧皇', color: '#ffae00' },
+  C: { title: '隐藏', color: '#9745e6' },
+  D: { title: '普通款', color: '#666666' },
+};
 
 const tabs = [
-  { label: '全部', value: 'all' },
-  { label: '待付款', value: 'to_pay' },
-  { label: '待补款', value: 'to_payLeft' },
-  { label: '待发货', value: 'to_delivery' },
-  { label: '待收货', value: 'to_receive' },
   { label: '已完成', value: 'complete' },
   { label: '未完成', value: 'uncomplete' },
 ];
 
+interface AwardOrderItem {
+  tradeNo: string;
+  createTime: string;
+  status: number;
+  luckPool: {
+    name: string;
+    cover: string;
+  };
+  price: number;
+  quantity: number;
+  couponAmount: number;
+  magicAmount: number;
+  paymentAmount: number;
+  paymentTimeoutTime?: string;
+  itemList?: Array<{
+    spu: { cover: string };
+    level: string;
+  }>;
+}
+
 export default function OrdersScreen() {
   const router = useRouter();
   const insets = useSafeAreaInsets();
+  const params = useLocalSearchParams();
 
   const [loading, setLoading] = useState(true);
   const [refreshing, setRefreshing] = useState(false);
-  const [activeTab, setActiveTab] = useState(0);
-  const [orders, setOrders] = useState<OrderItem[]>([]);
+  const [activeTab, setActiveTab] = useState(() => {
+    if (params.active) return parseInt(params.active as string, 10);
+    if (params.tab === '4') return 1;
+    return 0;
+  });
+  const [orders, setOrders] = useState<AwardOrderItem[]>([]);
   const [page, setPage] = useState(1);
+  const [hasMore, setHasMore] = useState(true);
 
-  const loadData = useCallback(async (tab?: number, refresh = false) => {
+  const loadData = useCallback(async (tabValue?: string, refresh = false) => {
     if (refresh) {
       setRefreshing(true);
       setPage(1);
@@ -45,9 +73,23 @@ export default function OrdersScreen() {
       setLoading(true);
     }
     try {
-      const data = await getOrders(refresh ? 1 : page, 10, tab);
-      if (data?.records) {
-        setOrders(refresh ? data.records : [...orders, ...data.records]);
+      const data = await getAwardOrders(refresh ? 1 : page, 10, tabValue);
+      console.log('奖池订单数据:', tabValue, data);
+      
+      let records: AwardOrderItem[] = [];
+      if (Array.isArray(data)) {
+        records = data;
+      } else if (data?.records) {
+        records = data.records;
+      }
+
+      if (records.length > 0) {
+        setOrders(refresh ? records : [...orders, ...records]);
+        setPage((prev) => (refresh ? 2 : prev + 1));
+        setHasMore(records.length === 10);
+      } else {
+        if (refresh) setOrders([]);
+        setHasMore(false);
       }
     } catch (error) {
       console.error('加载订单失败:', error);
@@ -71,14 +113,22 @@ export default function OrdersScreen() {
     }
   };
 
-  const goToDetail = (tradeNo: string) => {
-    router.push(`/orders/${tradeNo}` as any);
-  };
-
   const goBack = () => {
     router.back();
   };
 
+  const goToShopOrders = () => {
+    router.push('/orders/shop' as any);
+  };
+
+  const getStatusText = (status: number) => {
+    if (status === 99) return { text: '已完成', color: '#20D7EF' };
+    if (status === 0) return { text: '待支付', color: '#F62C71' };
+    if (status === 10) return { text: '用户取消', color: '#999' };
+    if (status === 11) return { text: '超时取消', color: '#999' };
+    return { text: '未知', color: '#999' };
+  };
+
   return (
     <View style={styles.container}>
       <StatusBar barStyle="light-content" />
@@ -90,9 +140,9 @@ export default function OrdersScreen() {
         {/* 顶部导航 */}
         <View style={[styles.header, { paddingTop: insets.top }]}>
           <TouchableOpacity style={styles.backBtn} onPress={goBack}>
-            <Text style={styles.backText}></Text>
+            <Text style={styles.backText}></Text>
           </TouchableOpacity>
-          <Text style={styles.headerTitle}>我的订单</Text>
+          <Text style={styles.headerTitle}>奖池订单</Text>
           <View style={styles.placeholder} />
         </View>
 
@@ -101,12 +151,18 @@ export default function OrdersScreen() {
           {tabs.map((tab, index) => (
             <TouchableOpacity
               key={index}
-              style={[styles.tabItem, activeTab === index && styles.tabItemActive]}
               onPress={() => switchTab(index)}
+              activeOpacity={0.8}
             >
-              <Text style={[styles.tabText, activeTab === index && styles.tabTextActive]}>
-                {tab.label}
-              </Text>
+              <ImageBackground
+                source={{ uri: activeTab === index ? Images.home.typeBgOn : Images.home.typeBg }}
+                style={styles.tabItem}
+                resizeMode="stretch"
+              >
+                <Text style={[styles.tabText, activeTab === index && styles.tabTextActive]}>
+                  {tab.label}
+                </Text>
+              </ImageBackground>
             </TouchableOpacity>
           ))}
         </View>
@@ -127,52 +183,97 @@ export default function OrdersScreen() {
               <Text style={styles.emptyText}>暂无订单</Text>
             </View>
           ) : (
-            orders.map((item) => (
-              <TouchableOpacity
-                key={item.tradeNo}
-                style={styles.orderItem}
-                onPress={() => goToDetail(item.tradeNo)}
-              >
+            orders.map((item) => {
+              const statusInfo = getStatusText(item.status);
+              return (
                 <ImageBackground
+                  key={item.tradeNo}
                   source={{ uri: Images.common.itemBg }}
-                  style={styles.orderItemBg}
+                  style={styles.orderCard}
                   resizeMode="stretch"
                 >
-                  <View style={styles.orderHeader}>
-                    <Text style={styles.orderNo}>订单号:{item.tradeNo}</Text>
-                    <Text style={styles.orderStatus}>{item.statusText}</Text>
-                  </View>
-                  <View style={styles.orderContent}>
-                    <Image source={item.goodsCover} style={styles.goodsImage} contentFit="cover" />
-                    <View style={styles.goodsInfo}>
-                      <Text style={styles.goodsName} numberOfLines={2}>{item.goodsName}</Text>
-                      <View style={styles.goodsBottom}>
-                        <Text style={styles.goodsPrice}>¥{item.paymentAmount}</Text>
-                        <Text style={styles.goodsQty}>x{item.quantity}</Text>
+                  <View style={styles.cardContent}>
+                    {/* 顶部信息 */}
+                    <View style={styles.orderTop}>
+                      <Text style={styles.orderTime}>下单时间:{item.createTime}</Text>
+                      <Text style={[styles.orderStatus, { color: statusInfo.color }]}>
+                        {statusInfo.text}
+                      </Text>
+                    </View>
+                    {item.status === 0 && item.paymentTimeoutTime && (
+                      <Text style={styles.timeoutText}>
+                        {item.paymentTimeoutTime} 将自动取消该订单,如有优惠券,将自动退回
+                      </Text>
+                    )}
+
+                    {/* 中间商品信息 */}
+                    <View style={styles.orderMiddle}>
+                      <ImageBackground 
+                        source={{ uri: Images.box?.detail?.firstItemBg || Images.common.itemBg }} 
+                        style={styles.productImgBg}
+                      >
+                        <Image 
+                          source={{ uri: item.luckPool?.cover }} 
+                          style={styles.productImg} 
+                          contentFit="cover" 
+                        />
+                      </ImageBackground>
+                      <View style={styles.productInfo}>
+                        <Text style={styles.productName} numberOfLines={1}>{item.luckPool?.name}</Text>
+                        <View style={styles.priceRow}>
+                          <Text style={styles.priceText}>¥{item.price}</Text>
+                          <Text style={styles.qtyText}>X{item.quantity}</Text>
+                        </View>
+                        <Text style={styles.discountText}>
+                          使用优惠券-{item.couponAmount} 使用果实-{item.magicAmount}
+                        </Text>
+                        <View style={styles.paymentRow}>
+                          <Text style={styles.discountText}>实付款:</Text>
+                          <Text style={styles.paymentAmount}>¥{item.paymentAmount}</Text>
+                        </View>
                       </View>
                     </View>
-                  </View>
-                  <View style={styles.orderFooter}>
-                    <Text style={styles.orderTime}>{item.createTime}</Text>
-                    <View style={styles.orderActions}>
-                      {item.status === 1 && (
-                        <TouchableOpacity style={styles.actionBtn}>
-                          <Text style={styles.actionBtnText}>去支付</Text>
-                        </TouchableOpacity>
-                      )}
-                      {item.status === 3 && (
-                        <TouchableOpacity style={styles.actionBtn}>
-                          <Text style={styles.actionBtnText}>确认收货</Text>
+
+                    {/* 底部商品列表 */}
+                    {item.status === 99 && item.itemList && item.itemList.length > 0 && (
+                      <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.itemList}>
+                        {item.itemList.map((goods, idx) => {
+                          const levelInfo = LEVEL_MAP[goods.level] || LEVEL_MAP.D;
+                          return (
+                            <View key={idx} style={styles.itemBox}>
+                              <View style={styles.itemImgBox}>
+                                <Image source={{ uri: goods.spu?.cover }} style={styles.itemImg} contentFit="contain" />
+                              </View>
+                              <View style={[styles.levelTag, { backgroundColor: levelInfo.color }]}>
+                                <Text style={styles.levelText}>{levelInfo.title}</Text>
+                              </View>
+                            </View>
+                          );
+                        })}
+                      </ScrollView>
+                    )}
+
+                    {/* 未完成订单显示订单号 */}
+                    {item.status !== 99 && (
+                      <View style={styles.orderNoRow}>
+                        <Text style={styles.orderNoText} numberOfLines={1}>订单号:{item.tradeNo}</Text>
+                        <TouchableOpacity style={styles.copyBtn}>
+                          <Text style={styles.copyBtnText}>复制</Text>
                         </TouchableOpacity>
-                      )}
-                    </View>
+                      </View>
+                    )}
                   </View>
                 </ImageBackground>
-              </TouchableOpacity>
-            ))
+              );
+            })
           )}
-          <View style={{ height: 20 }} />
+          <View style={{ height: 80 }} />
         </ScrollView>
+
+        {/* 商城订单入口 */}
+        <TouchableOpacity style={[styles.shopOrderBtn, { bottom: insets.bottom + 154 }]} onPress={goToShopOrders}>
+          <Image source={{ uri: Images.mine.shopOrder }} style={styles.shopOrderIcon} contentFit="contain" />
+        </TouchableOpacity>
       </ImageBackground>
     </View>
   );
@@ -201,43 +302,40 @@ const styles = StyleSheet.create({
   },
   backText: {
     color: '#fff',
-    fontSize: 24,
+    fontSize: 32,
+    fontWeight: 'bold',
   },
   headerTitle: {
     color: '#fff',
-    fontSize: 18,
-    fontWeight: '600',
+    fontSize: 15,
+    fontWeight: 'bold',
   },
   placeholder: {
     width: 40,
   },
   tabBar: {
     flexDirection: 'row',
-    backgroundColor: 'rgba(255,255,255,0.1)',
-    marginHorizontal: 15,
-    borderRadius: 8,
-    paddingVertical: 5,
+    marginHorizontal: 10,
+    marginBottom: 15,
   },
   tabItem: {
-    flex: 1,
+    width: 80, // 原项目 160rpx
+    height: 40, // 原项目 80rpx
+    justifyContent: 'center',
     alignItems: 'center',
-    paddingVertical: 8,
-  },
-  tabItemActive: {
-    borderBottomWidth: 2,
-    borderBottomColor: '#ff6b00',
+    marginRight: 10, // 原项目 20rpx
   },
   tabText: {
-    color: '#999',
-    fontSize: 14,
+    color: '#000',
+    fontSize: 14, // 原项目 28rpx
+    fontWeight: '400',
   },
   tabTextActive: {
-    color: '#ff6b00',
-    fontWeight: '600',
+    fontWeight: 'bold',
   },
   scrollView: {
     flex: 1,
-    paddingHorizontal: 15,
+    paddingHorizontal: 10,
   },
   loadingContainer: {
     paddingTop: 100,
@@ -251,88 +349,140 @@ const styles = StyleSheet.create({
     color: '#999',
     fontSize: 14,
   },
-  orderItem: {
-    marginTop: 10,
+  orderCard: {
+    marginVertical: 10,
+    minHeight: 200,
   },
-  orderItemBg: {
-    borderRadius: 12,
-    overflow: 'hidden',
+  cardContent: {
+    padding: 15,
   },
-  orderHeader: {
+  orderTop: {
     flexDirection: 'row',
     justifyContent: 'space-between',
-    alignItems: 'center',
-    padding: 12,
+    paddingBottom: 10,
     borderBottomWidth: 1,
-    borderBottomColor: 'rgba(0,0,0,0.1)',
+    borderBottomColor: '#CDCDCD',
   },
-  orderNo: {
-    color: '#666',
+  orderTime: {
     fontSize: 12,
+    color: '#666',
   },
   orderStatus: {
-    color: '#ff6b00',
-    fontSize: 13,
-    fontWeight: '500',
+    fontSize: 12,
+  },
+  timeoutText: {
+    fontSize: 11,
+    color: '#999',
+    marginTop: 5,
   },
-  orderContent: {
+  orderMiddle: {
     flexDirection: 'row',
-    padding: 12,
+    paddingVertical: 12,
   },
-  goodsImage: {
-    width: 80,
-    height: 80,
-    borderRadius: 8,
-    backgroundColor: '#fff',
+  productImgBg: {
+    width: 90,
+    height: 90,
+    padding: 8,
+  },
+  productImg: {
+    width: '100%',
+    height: '100%',
+    borderRadius: 3,
   },
-  goodsInfo: {
+  productInfo: {
     flex: 1,
     marginLeft: 12,
     justifyContent: 'space-between',
   },
-  goodsName: {
-    color: '#333',
+  productName: {
     fontSize: 14,
-    lineHeight: 20,
+    color: '#333',
+    fontWeight: 'bold',
   },
-  goodsBottom: {
+  priceRow: {
     flexDirection: 'row',
     justifyContent: 'space-between',
+  },
+  priceText: {
+    fontSize: 14,
+    color: '#333',
+  },
+  qtyText: {
+    fontSize: 14,
+    color: '#333',
+  },
+  discountText: {
+    fontSize: 12,
+    color: '#F1423D',
+    fontWeight: '500',
+  },
+  paymentRow: {
+    flexDirection: 'row',
     alignItems: 'center',
   },
-  goodsPrice: {
-    color: '#ff6b00',
+  paymentAmount: {
     fontSize: 16,
-    fontWeight: '600',
+    color: '#F1423D',
+    fontWeight: 'bold',
   },
-  goodsQty: {
-    color: '#666',
-    fontSize: 13,
+  itemList: {
+    marginTop: 5,
+    paddingBottom: 10,
   },
-  orderFooter: {
-    flexDirection: 'row',
-    justifyContent: 'space-between',
+  itemBox: {
+    marginRight: 10,
     alignItems: 'center',
-    padding: 12,
-    borderTopWidth: 1,
-    borderTopColor: 'rgba(0,0,0,0.1)',
   },
-  orderTime: {
-    color: '#999',
-    fontSize: 12,
+  itemImgBox: {
+    width: 60,
+    height: 65,
+    backgroundColor: '#fff',
+    borderRadius: 3,
+    overflow: 'hidden',
+  },
+  itemImg: {
+    width: '100%',
+    height: '100%',
+  },
+  levelTag: {
+    width: '100%',
+    paddingVertical: 2,
+    alignItems: 'center',
+  },
+  levelText: {
+    color: '#fff',
+    fontSize: 10,
+    fontWeight: 'bold',
   },
-  orderActions: {
+  orderNoRow: {
     flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    height: 29,
   },
-  actionBtn: {
-    paddingHorizontal: 16,
-    paddingVertical: 6,
-    backgroundColor: '#ff6b00',
-    borderRadius: 15,
-    marginLeft: 10,
+  orderNoText: {
+    flex: 1,
+    fontSize: 12,
+    color: '#666',
   },
-  actionBtnText: {
+  copyBtn: {
+    paddingHorizontal: 6,
+    paddingVertical: 3,
+    backgroundColor: '#1FA4FF',
+    borderRadius: 4,
+  },
+  copyBtnText: {
     color: '#fff',
-    fontSize: 13,
+    fontSize: 12,
+  },
+  shopOrderBtn: {
+    position: 'absolute',
+    left: 10,
+    width: 60,
+    height: 63,
+  },
+  shopOrderIcon: {
+    width: '100%',
+    height: '100%',
   },
 });

+ 524 - 0
app/orders/shop.tsx

@@ -0,0 +1,524 @@
+import { Image } from 'expo-image';
+import { useLocalSearchParams, useRouter } from 'expo-router';
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+    ActivityIndicator,
+    Alert,
+    Clipboard,
+    ImageBackground,
+    RefreshControl,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View
+} from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+import { Images } from '@/constants/images';
+import { getOrders, OrderItem } from '@/services/mall';
+
+const tabs = [
+  { label: '全部', value: 'all' },
+  { label: '待支付', value: 'to_pay' },
+  { label: '待补款', value: 'to_payLeft' },
+  { label: '待发货', value: 'to_delivery' },
+  { label: '待收货', value: 'to_receive' },
+  { label: '已完成', value: 'complete' },
+  { label: '已取消', value: 'uncomplete' },
+];
+
+export default function ShopOrdersScreen() {
+  const router = useRouter();
+  const insets = useSafeAreaInsets();
+
+  const params = useLocalSearchParams();
+  const [loading, setLoading] = useState(true);
+  const [refreshing, setRefreshing] = useState(false);
+  const [activeTab, setActiveTab] = useState(() => {
+    if (params.active) return parseInt(params.active as string, 10);
+    return 0;
+  });
+  const [orders, setOrders] = useState<OrderItem[]>([]);
+  const [page, setPage] = useState(1);
+
+  const loadData = useCallback(async (tabValue?: string, refresh = false) => {
+    if (refresh) {
+      setRefreshing(true);
+      setPage(1);
+    } else {
+      setLoading(true);
+    }
+    try {
+      let tabId: any = tabValue;
+      if (tabValue === 'all') tabId = undefined;
+      
+      const data = await getOrders(refresh ? 1 : page, 10, tabId);
+      if (data?.records) {
+        setOrders(refresh ? data.records : [...orders, ...data.records]);
+      }
+    } catch (error) {
+      console.error('加载商城订单失败:', error);
+    }
+    setLoading(false);
+    setRefreshing(false);
+  }, [page, orders]);
+
+  useEffect(() => {
+    loadData(tabs[activeTab].value, true);
+  }, [activeTab]);
+
+  const onRefresh = () => {
+    loadData(tabs[activeTab].value, true);
+  };
+
+  const switchTab = (index: number) => {
+    if (index !== activeTab) {
+      setActiveTab(index);
+      setOrders([]);
+    }
+  };
+
+  const goBack = () => {
+    router.back();
+  };
+
+  const goBox = () => {
+    router.replace('/orders' as any);
+  };
+
+  const copyTradeNo = (tradeNo: string) => {
+    Clipboard.setString(tradeNo);
+    Alert.alert('提示', '订单号已复制');
+  };
+
+  const getStatusInfo = (status: number) => {
+    switch (status) {
+      case 0: return { text: '待支付', color: '#F62C71' };
+      case 99: return { text: '已完成', color: '#20D7EF' };
+      case 1: return { text: '待发货', color: '#F62C71' };
+      case 2: return { text: '待收货', color: '#F62C71' };
+      case 10: return { text: '已取消', color: '#999' };
+      case 11: return { text: '超时取消', color: '#999' };
+      case 12: return { text: '系统取消', color: '#999' };
+      default: return { text: '未知', color: '#999' };
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <StatusBar barStyle="light-content" />
+      <ImageBackground
+        source={{ uri: Images.mine.kaixinMineBg }}
+        style={styles.background}
+        resizeMode="cover"
+      >
+        <View style={[styles.header, { paddingTop: insets.top }]}>
+          <TouchableOpacity style={styles.backBtn} onPress={goBack}>
+            <Text style={styles.backText}>‹</Text>
+          </TouchableOpacity>
+          <Text style={styles.headerTitle}>商城订单</Text>
+          <View style={styles.placeholder} />
+        </View>
+
+        <View style={styles.tabBar}>
+          <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.tabScroll}>
+            {tabs.map((tab, index) => (
+              <TouchableOpacity
+                key={index}
+                onPress={() => switchTab(index)}
+                activeOpacity={0.8}
+              >
+                <ImageBackground
+                  source={{ uri: activeTab === index ? Images.home.typeBgOn : Images.home.typeBg }}
+                  style={styles.tabItem}
+                  resizeMode="stretch"
+                >
+                  <Text style={[styles.tabText, activeTab === index && styles.tabTextActive]}>
+                    {tab.label}
+                  </Text>
+                </ImageBackground>
+              </TouchableOpacity>
+            ))}
+          </ScrollView>
+        </View>
+
+        <ScrollView
+          style={styles.scrollView}
+          showsVerticalScrollIndicator={false}
+          refreshControl={
+            <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#fff" />
+          }
+        >
+          {loading && orders.length === 0 ? (
+            <View style={styles.loadingContainer}>
+              <ActivityIndicator size="large" color="#fff" />
+            </View>
+          ) : orders.length === 0 ? (
+            <View style={styles.emptyContainer}>
+              <Text style={styles.emptyText}>暂无订单</Text>
+            </View>
+          ) : (
+            orders.map((item) => {
+              const statusInfo = getStatusInfo(item.status);
+              return (
+                <ImageBackground
+                  key={item.tradeNo}
+                  source={{ uri: Images.common.itemBg }}
+                  style={styles.orderCard}
+                  resizeMode="stretch"
+                >
+                  <View style={styles.cardContent}>
+                    {/* 顶部信息 */}
+                    <View style={styles.orderTop}>
+                      <View style={styles.flexRow}>
+                        <Text style={styles.orderTimeText}>下单时间:{item.createTime}</Text>
+                        {item.type === 2 && <View style={styles.presellTag}><Text style={styles.presellText}>预售订单</Text></View>}
+                      </View>
+                      <Text style={[styles.orderStatusText, { color: statusInfo.color }]}>
+                        {statusInfo.text}
+                      </Text>
+                    </View>
+                    
+                    {item.status === 0 && item.paymentTimeoutTime && (
+                      <Text style={styles.timeoutText}>
+                        {item.paymentTimeoutTime}将自动取消该订单,如有优惠券,将自动退回
+                      </Text>
+                    )}
+
+                    {/* 商品信息 */}
+                    <View style={styles.orderMiddle}>
+                      <ImageBackground
+                        source={{ uri: Images.box.detail.firstItemBg }}
+                        style={styles.productImgBg}
+                        resizeMode="stretch"
+                      >
+                        <Image source={{ uri: item.goodsCover }} style={styles.goodsImage} contentFit="cover" />
+                      </ImageBackground>
+                      <View style={styles.goodsInfo}>
+                        <Text style={styles.goodsName} numberOfLines={1}>{item.goodsName}</Text>
+                        <View style={styles.priceRow}>
+                          <Text style={styles.goodsPrice}>¥{item.paymentAmount}</Text>
+                          <Text style={styles.goodsQty}>X{item.quantity}</Text>
+                        </View>
+                        {(item.couponAmount ?? 0) > 0 && (
+                          <Text style={styles.discountText}>使用优惠券-{item.couponAmount}</Text>
+                        )}
+                        
+                        <View style={styles.totalRow}>
+                          <View style={styles.totalItem}>
+                            <Text style={styles.totalLabel}>总价:</Text>
+                            <Text style={styles.totalValueSymbol}>¥</Text>
+                            <Text style={styles.totalValue}>{item.totalAmount || item.paymentAmount}</Text>
+                          </View>
+                          <View style={[styles.totalItem, { marginLeft: 12 }]}>
+                            <Text style={styles.payLabel}>{item.status === 0 ? '需付款:' : '实付款:'}</Text>
+                            <Text style={styles.payValueSymbol}>¥</Text>
+                            <Text style={styles.payValue}>{item.paidAmount || item.paymentAmount}</Text>
+                          </View>
+                        </View>
+                      </View>
+                    </View>
+
+                    {/* 订单号 & 复制 */}
+                    <View style={styles.orderNoRow}>
+                      <Text style={styles.orderNo} numberOfLines={1}>订单号:{item.tradeNo}</Text>
+                      <TouchableOpacity style={styles.copyBtn} onPress={() => copyTradeNo(item.tradeNo)}>
+                        <Text style={styles.copyBtnText}>复制</Text>
+                      </TouchableOpacity>
+                    </View>
+
+                    {/* 功能按钮 */}
+                    <View style={styles.orderFooter}>
+                      {item.status === 0 && (
+                        <TouchableOpacity style={styles.actionBtn}>
+                          <Text style={styles.actionBtnText}>付款</Text>
+                        </TouchableOpacity>
+                      )}
+                      {item.status === 1 && (
+                        <TouchableOpacity style={[styles.actionBtn, styles.infoBtn]}>
+                          <Text style={styles.actionBtnText}>修改地址</Text>
+                        </TouchableOpacity>
+                      )}
+                      {item.status === 2 && (
+                        <TouchableOpacity style={styles.actionBtn}>
+                          <Text style={styles.actionBtnText}>确认收货</Text>
+                        </TouchableOpacity>
+                      )}
+                      {[2, 99].includes(item.status) && (
+                        <TouchableOpacity style={[styles.actionBtn, { marginLeft: 8 }]}>
+                          <Text style={styles.actionBtnText}>物流信息</Text>
+                        </TouchableOpacity>
+                      )}
+                    </View>
+                  </View>
+                </ImageBackground>
+              );
+            })
+          )}
+          <View style={{ height: 80 }} />
+        </ScrollView>
+
+        {/* 奖池订单入口 */}
+        <TouchableOpacity style={[styles.floatBtn, { bottom: insets.bottom + 154 }]} onPress={goBox}>
+          <Image source={{ uri: Images.mine.boxOrder }} style={styles.floatIcon} contentFit="contain" />
+        </TouchableOpacity>
+      </ImageBackground>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: '#1a1a2e',
+  },
+  background: {
+    flex: 1,
+  },
+  header: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    paddingHorizontal: 15,
+    paddingBottom: 10,
+  },
+  backBtn: {
+    width: 40,
+    height: 40,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  backText: {
+    color: '#fff',
+    fontSize: 32,
+    fontWeight: 'bold',
+  },
+  headerTitle: {
+    color: '#fff',
+    fontSize: 15,
+    fontWeight: 'bold',
+  },
+  placeholder: {
+    width: 40,
+  },
+  tabBar: {
+    height: 50,
+    marginHorizontal: 10,
+    marginBottom: 7,
+  },
+  tabScroll: {
+    flex: 1,
+  },
+  tabItem: {
+    width: 80,
+    height: 40,
+    justifyContent: 'center',
+    alignItems: 'center',
+    marginRight: 10,
+    paddingTop: 4,
+  },
+  tabText: {
+    color: '#000',
+    fontSize: 14,
+    fontWeight: '400',
+  },
+  tabTextActive: {
+    fontWeight: 'bold',
+  },
+  scrollView: {
+    flex: 1,
+    paddingHorizontal: 10,
+  },
+  loadingContainer: {
+    paddingTop: 100,
+    alignItems: 'center',
+  },
+  emptyContainer: {
+    paddingTop: 100,
+    alignItems: 'center',
+  },
+  emptyText: {
+    color: '#999',
+    fontSize: 14,
+  },
+  orderCard: {
+    marginVertical: 10,
+    minHeight: 220,
+  },
+  cardContent: {
+    paddingHorizontal: 20,
+    paddingVertical: 10,
+  },
+  orderTop: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    paddingVertical: 15,
+    borderBottomWidth: 1,
+    borderBottomColor: '#CDCDCD',
+    alignItems: 'center',
+  },
+  flexRow: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  orderTimeText: {
+    fontSize: 12,
+    color: '#333',
+  },
+  presellTag: {
+    backgroundColor: '#F1423D',
+    borderRadius: 11,
+    paddingHorizontal: 8,
+    height: 22,
+    justifyContent: 'center',
+    marginLeft: 8,
+  },
+  presellText: {
+    color: '#fff',
+    fontSize: 12,
+  },
+  orderStatusText: {
+    fontSize: 12,
+    fontWeight: '500',
+  },
+  timeoutText: {
+    fontSize: 11,
+    color: '#999',
+    marginTop: 5,
+  },
+  orderMiddle: {
+    flexDirection: 'row',
+    paddingVertical: 12,
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee',
+  },
+  productImgBg: {
+    width: 70,
+    height: 70,
+    padding: 7,
+  },
+  goodsImage: {
+    width: '100%',
+    height: '100%',
+    borderRadius: 3,
+  },
+  goodsInfo: {
+    flex: 1,
+    marginLeft: 12,
+    justifyContent: 'center',
+  },
+  goodsName: {
+    fontSize: 14,
+    color: '#333',
+    fontWeight: 'bold',
+  },
+  priceRow: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    marginTop: 8,
+  },
+  goodsPrice: {
+    fontSize: 14,
+    color: '#333',
+    fontWeight: 'bold',
+  },
+  goodsQty: {
+    fontSize: 12,
+    color: '#333',
+  },
+  discountText: {
+    fontSize: 12,
+    color: '#F1423D',
+    marginTop: 4,
+  },
+  totalRow: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginTop: 8,
+  },
+  totalItem: {
+    flexDirection: 'row',
+    alignItems: 'baseline',
+  },
+  totalLabel: {
+    fontSize: 11,
+    color: '#666',
+  },
+  totalValueSymbol: {
+    fontSize: 11,
+    color: '#666',
+  },
+  totalValue: {
+    fontSize: 14,
+    color: '#666',
+    fontWeight: 'bold',
+  },
+  payLabel: {
+    fontSize: 12,
+    color: '#F1423D',
+  },
+  payValueSymbol: {
+    fontSize: 12,
+    color: '#F1423D',
+  },
+  payValue: {
+    fontSize: 18,
+    color: '#F1423D',
+    fontWeight: 'bold',
+  },
+  orderNoRow: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    paddingVertical: 12,
+    borderBottomWidth: 1,
+    borderBottomColor: '#CDCDCD',
+  },
+  orderNo: {
+    flex: 1,
+    fontSize: 12,
+    color: '#666',
+  },
+  copyBtn: {
+    paddingHorizontal: 6,
+    paddingVertical: 3,
+    backgroundColor: '#1FA4FF',
+    borderRadius: 4,
+  },
+  copyBtnText: {
+    color: '#fff',
+    fontSize: 12,
+  },
+  orderFooter: {
+    flexDirection: 'row',
+    justifyContent: 'flex-end',
+    paddingVertical: 12,
+  },
+  actionBtn: {
+    height: 24,
+    paddingHorizontal: 10,
+    backgroundColor: '#1FA4FF',
+    borderRadius: 12,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  infoBtn: {
+    backgroundColor: '#1FA4FF',
+  },
+  actionBtnText: {
+    color: '#fff',
+    fontSize: 12,
+  },
+  floatBtn: {
+    position: 'absolute',
+    left: 10,
+    width: 60,
+    height: 63,
+  },
+  floatIcon: {
+    width: '100%',
+    height: '100%',
+  },
+});

+ 5 - 2
app/product/[id].tsx

@@ -10,10 +10,11 @@ import {
     StyleSheet,
     Text,
     TouchableOpacity,
-    View
+    View,
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
+import { KefuPopup, KefuPopupRef } from '@/components/mine/KefuPopup';
 import { CheckoutModal } from '@/components/product/CheckoutModal';
 import { Images } from '@/constants/images';
 import { getGoodsDetail, GoodsDetail, previewSubmit } from '@/services/mall';
@@ -25,6 +26,7 @@ export default function ProductDetailScreen() {
   const router = useRouter();
   const insets = useSafeAreaInsets();
   const checkoutRef = useRef<any>(null);
+  const kefuRef = useRef<KefuPopupRef>(null);
 
   const [loading, setLoading] = useState(true);
   const [data, setData] = useState<GoodsDetail | null>(null);
@@ -168,7 +170,7 @@ export default function ProductDetailScreen() {
 
         {/* 底部购买栏 */}
         <View style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}>
-          <TouchableOpacity style={styles.serviceBtn}>
+          <TouchableOpacity style={styles.serviceBtn} onPress={() => kefuRef.current?.open()}>
             <Text style={styles.serviceBtnText}>客服</Text>
           </TouchableOpacity>
           <TouchableOpacity style={styles.buyBtn} onPress={showCheckout} activeOpacity={0.8}>
@@ -192,6 +194,7 @@ export default function ProductDetailScreen() {
         goodsId={id!}
         subjectId={subjectId}
       />
+      <KefuPopup ref={kefuRef} />
     </View>
   );
 }

+ 18 - 3
app/profile/index.tsx

@@ -17,7 +17,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
 import { Images } from '@/constants/images';
 import { useAuth } from '@/contexts/AuthContext';
-import { getUserInfo, updateNickname, updateUserInfo } from '@/services/user';
+import { uploadFile } from '@/services/base';
+import { getUserInfo, updateAvatar, updateNickname, updateUserInfo } from '@/services/user';
 
 interface FormData {
   nickname: string;
@@ -69,7 +70,7 @@ export default function ProfileScreen() {
       }
 
       const result = await ImagePicker.launchImageLibraryAsync({
-        mediaTypes: ImagePicker.MediaTypeOptions.Images,
+        mediaTypes: ['images'],
         allowsEditing: true,
         aspect: [1, 1],
         quality: 0.8,
@@ -78,7 +79,6 @@ export default function ProfileScreen() {
       if (!result.canceled && result.assets[0]) {
         const imageUri = result.assets[0].uri;
         setFormData(prev => ({ ...prev, avatar: imageUri }));
-        Alert.alert('提示', '头像选择成功,保存时将更新');
       }
     } catch (error) {
       console.error('选择头像失败:', error);
@@ -99,6 +99,21 @@ export default function ProfileScreen() {
     try {
       setLoading(true);
       
+      // 如果头像是本地文件(file://开头),需要先上传
+      let avatarUrl = formData.avatar;
+      if (formData.avatar && formData.avatar.startsWith('file://')) {
+        const uploadedUrl = await uploadFile(formData.avatar, 'avatar');
+        if (uploadedUrl) {
+          avatarUrl = uploadedUrl;
+          // 更新头像
+          await updateAvatar(avatarUrl);
+        } else {
+          Alert.alert('提示', '头像上传失败');
+          setLoading(false);
+          return;
+        }
+      }
+      
       // 更新昵称
       const nicknameRes = await updateNickname(formData.nickname);
       

+ 41 - 4
app/store/components/CheckoutModal.tsx

@@ -1,4 +1,5 @@
 import { Image } from 'expo-image';
+import Alipay from 'expo-native-alipay';
 import { useRouter } from 'expo-router';
 import React, { useEffect, useState } from 'react';
 import {
@@ -96,22 +97,58 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
     router.push('/address?type=1' as any);
   };
 
+  /* 
+   * Handle Submit with Payment Choice
+   */
   const handleSubmit = async () => {
     if (!address) {
       showAlert('请选择收货地址');
       return;
     }
+    
+    if (expressAmount > 0) {
+        Alert.alert(
+            '支付运费',
+            `需支付运费 ¥${expressAmount}`,
+            [
+                { text: '取消', style: 'cancel' },
+                { text: '钱包支付', onPress: () => processTakeApply('WALLET') },
+                { text: '支付宝支付', onPress: () => processTakeApply('ALIPAY_APP') }
+            ]
+        );
+    } else {
+        processTakeApply('WALLET');
+    }
+  };
+
+  const processTakeApply = async (paymentType: string) => {
     if (submitting) return;
     setSubmitting(true);
     try {
       const ids = selectedItems.map(item => item.id);
-      const res = await takeApply(ids, address.id, 'WALLET');
-      if (res) {
-        showAlert('提货成功');
-        onSuccess();
+      const res: any = await takeApply(ids, address!.id, paymentType);
+      
+      console.log('Take Apply Res:', res, paymentType);
+
+      if (paymentType === 'ALIPAY_APP' && res?.payInfo) {
+           Alipay.setAlipayScheme('alipay2021005175632205');
+           const result = await Alipay.pay(res.payInfo);
+           console.log('Alipay Result:', result);
+           if (result?.resultStatus === '9000') {
+               showAlert('提货成功');
+               onSuccess();
+           } else {
+               showAlert('支付未完成');
+           }
+      } else if (res) {
+         // Wallet payment or free success
+         showAlert('提货成功');
+         onSuccess();
       }
     } catch (e) {
       console.error('提货失败:', e);
+      // Usually axios interceptor handles error alerts, but just in case
+      // showAlert('提货失败'); 
     }
     setSubmitting(false);
   };

+ 275 - 96
app/store/index.tsx

@@ -27,10 +27,12 @@ import {
 import CheckoutModal from './components/CheckoutModal';
 
 const LEVEL_MAP: Record<string, { title: string; color: string }> = {
-  A: { title: '超神', color: '#ffae00' },
-  B: { title: '欧皇', color: '#ff0000' },
+  A: { title: '超神', color: '#ff0000' },  // 红色
+  B: { title: '欧皇', color: '#ffae00' },  // 黄色
   C: { title: '隐藏', color: '#9745e6' },
   D: { title: '普通', color: '#666666' },
+  SUBSTITUTE: { title: '置换款', color: '#666666' },
+  OTHER: { title: '其他', color: '#666666' },
 };
 
 const LEVEL_TABS = [
@@ -39,11 +41,18 @@ const LEVEL_TABS = [
   { title: '隐藏', value: 'C' },
   { title: '欧皇', value: 'B' },
   { title: '超神', value: 'A' },
+  { title: '其他', value: 'OTHER' },
 ];
 
 const FROM_TYPE_MAP: Record<string, string> = {
   LUCK: '奖池', MALL: '商城', LUCK_ROOM: '福利房', LUCK_WHEEL: '魔天轮',
-  DOLL_MACHINE: '扭蛋', ACTIVITY: '活动', SUBSTITUTE: '置换', TRANSFER: '转赠',
+  DOLL_MACHINE: '扭蛋', ACTIVITY: '活动', SUBSTITUTE: '商品置换', TRANSFER: '商品转赠',
+  DISTRIBUTION: '分销', LUCK_PICKUP: '商品提货', LUCK_EXCHANGE: '商品兑换',
+  RECHARGE: '充值', WITHDRAW: '提现', OFFICIAL: '官方',
+  LUCK_ACTIVITY: '奖池活动', CONSUMPTION_ACTIVITY: '消费活动',
+  NEW_USER_RANK_ACTIVITY: '拉新排名活动', CONSUMPTION_RANK_ACTIVITY: '消费排行榜活动',
+  WHEEL_ACTIVITY: '大转盘活动', TA_ACTIVITY: '勇者之塔活动',
+  ISLAND_ACTIVITY: '海岛活动', REDEEM_CODE_ACTIVITY: '兑换码活动',
 };
 
 interface StoreItem {
@@ -167,7 +176,6 @@ export default function StoreScreen() {
     if (res) {
       const newList = [...list]; newList[index] = { ...item, safeFlag: 1 }; setList(newList);
       setCheckMap(prev => { const m = { ...prev }; delete m[item.id]; return m; });
-      showAlert('已锁定到保险柜');
     }
   };
 
@@ -176,7 +184,6 @@ export default function StoreScreen() {
     if (res) {
       if (mainTabIndex === 1) setList(list.filter((_, i) => i !== index));
       else { const newList = [...list]; newList[index] = { ...item, safeFlag: 0 }; setList(newList); }
-      showAlert('已从保险柜移出');
     }
   };
 
@@ -184,7 +191,7 @@ export default function StoreScreen() {
     const selected = Object.values(checkMap);
     if (selected.length === 0) { showAlert('请至少选择一个商品!'); return; }
     const res = await moveOutSafeStore(selected.map(i => i.id));
-    if (res) { setCheckMap({}); handleRefresh(); showAlert('已从保险柜移出'); }
+    if (res) { setCheckMap({}); handleRefresh(); }
   };
 
   const handleTakeGoods = () => {
@@ -193,6 +200,19 @@ export default function StoreScreen() {
     setCheckoutVisible(true);
   };
 
+  const handleSelectAll = () => {
+    const allSelected = list.every(item => checkMap[item.id]);
+    if (allSelected) {
+      setCheckMap({});
+    } else {
+      const newMap: Record<string, StoreItem> = {};
+      list.forEach(item => {
+        newMap[item.id] = item;
+      });
+      setCheckMap(newMap);
+    }
+  };
+
   const handleCheckoutSuccess = () => {
     setCheckoutVisible(false);
     setCheckMap({});
@@ -210,27 +230,30 @@ export default function StoreScreen() {
     const canSelect = mainTabIndex === 1 || item.safeFlag !== 1;
     return (
       <ImageBackground source={{ uri: Images.mine.storeItemBg }} style={styles.cell} resizeMode="stretch">
-        <TouchableOpacity style={styles.cellHeader} onPress={() => canSelect && handleChoose(item)}>
-          <View style={styles.headerLeft}>
-            {canSelect && (
-              <View style={[styles.checkBox, isChecked && styles.checkBoxChecked]}>
-                {isChecked && <Text style={styles.checkIcon}>✓</Text>}
-              </View>
-            )}
-            <Text style={[styles.levelTitle, { color: levelInfo.color }]}>{levelInfo.title}</Text>
-          </View>
-          <TouchableOpacity style={styles.lockBox} onPress={() => item.safeFlag !== 1 ? handleLock(item, index) : handleUnlock(item, index)}>
-            <Text style={styles.lockText}>{item.safeFlag !== 1 ? '锁定' : '解锁'}</Text>
-            <Image source={{ uri: item.safeFlag !== 1 ? Images.mine.lock : Images.mine.unlock }} style={styles.lockIcon} />
+        <View style={styles.cellContent}>
+          <TouchableOpacity style={styles.cellHeader} onPress={() => canSelect && handleChoose(item)}>
+            <View style={styles.headerLeft}>
+              {canSelect && (
+                <View style={[styles.checkBox, isChecked && styles.checkBoxChecked]}>
+                  {isChecked && <Text style={styles.checkIcon}>✓</Text>}
+                </View>
+              )}
+              <Text style={[styles.levelTitle, { color: levelInfo.color }]}>{levelInfo.title}</Text>
+            </View>
+            <TouchableOpacity style={styles.lockBox} onPress={() => item.safeFlag !== 1 ? handleLock(item, index) : handleUnlock(item, index)}>
+              <Text style={styles.lockText}>{item.safeFlag !== 1 ? '锁定' : '解锁'}</Text>
+              <Image source={{ uri: item.safeFlag !== 1 ? Images.mine.lock : Images.mine.unlock }} style={styles.lockIcon} />
+            </TouchableOpacity>
           </TouchableOpacity>
-        </TouchableOpacity>
-        <View style={styles.cellBody}>
-          <ImageBackground source={{ uri: Images.mine.storeGoodsImgBg }} style={styles.goodsImgBg}>
-            <Image source={{ uri: item.spu?.cover }} style={styles.goodsImg} contentFit="contain" />
-          </ImageBackground>
-          <View style={styles.goodsInfo}>
-            <Text style={styles.goodsName} numberOfLines={2}>{item.spu?.name}</Text>
-            <Text style={styles.goodsSource}>从{FROM_TYPE_MAP[item.fromRelationType] || '其他'}获得</Text>
+          <View style={styles.cellBody}>
+            <ImageBackground source={{ uri: Images.mine.storeGoodsImgBg }} style={styles.goodsImgBg}>
+              <Image source={{ uri: item.spu?.cover }} style={styles.goodsImg} contentFit="contain" />
+            </ImageBackground>
+            <View style={styles.goodsInfo}>
+              <Text style={styles.goodsName} numberOfLines={2}>{item.spu?.name}</Text>
+              <Text style={styles.goodsSource}>从{FROM_TYPE_MAP[item.fromRelationType] || '其他'}获得</Text>
+            </View>
+            <Text style={styles.arrow}>{'>'}</Text>
           </View>
         </View>
       </ImageBackground>
@@ -333,20 +356,35 @@ export default function StoreScreen() {
         </View>
         <View style={[styles.content, { paddingTop: insets.top + 50 }]}>
           <View style={styles.mainTabs}>
-            {mainTabs.map((tab, index) => (
-              <TouchableOpacity key={index} style={styles.mainTabItem} onPress={() => setMainTabIndex(index)}>
-                <Text style={[styles.mainTabText, mainTabIndex === index && styles.mainTabTextActive]}>{tab}</Text>
-                {mainTabIndex === index && <View style={styles.mainTabLine} />}
-              </TouchableOpacity>
-            ))}
+            {mainTabs.map((tab, index) => {
+              const isActive = mainTabIndex === index;
+              // Use Yellow L bg for active, Grey (Hui) for inactive
+              const bg = isActive ? Images.common.butBgL : Images.common.butBgHui;
+              return (
+                <TouchableOpacity key={index} style={styles.mainTabItem} onPress={() => setMainTabIndex(index)}>
+                  <ImageBackground source={{ uri: bg }} style={styles.mainTabBg} resizeMode="contain">
+                    <Text style={isActive ? styles.mainTabTextActive : styles.mainTabText}>{tab}</Text>
+                  </ImageBackground>
+                </TouchableOpacity>
+              );
+            })}
           </View>
           {mainTabIndex === 0 && (
             <View style={styles.levelTabs}>
-              {LEVEL_TABS.map((tab, index) => (
-                <TouchableOpacity key={index} style={[styles.levelTabItem, levelTabIndex === index && styles.levelTabItemActive]} onPress={() => setLevelTabIndex(index)}>
-                  <Text style={[styles.levelTabText, levelTabIndex === index && styles.levelTabTextActive]}>{tab.title}</Text>
-                </TouchableOpacity>
-              ))}
+              {LEVEL_TABS.map((tab, index) => {
+                const isActive = levelTabIndex === index;
+                return (
+                  <TouchableOpacity 
+                    key={index} 
+                    style={[styles.levelTabItem, isActive && styles.levelTabItemActive]} 
+                    onPress={() => setLevelTabIndex(index)}
+                  >
+                    {isActive && <View style={styles.decorTL} />}
+                    <Text style={[styles.levelTabText, isActive && styles.levelTabTextActive]}>{tab.title}</Text>
+                    {isActive && <View style={styles.decorBR} />}
+                  </TouchableOpacity>
+                );
+              })}
             </View>
           )}
           <FlatList
@@ -369,12 +407,33 @@ export default function StoreScreen() {
         </View>
         {mainTabIndex !== 2 && list.length > 0 && (
           <View style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}>
+            {mainTabIndex === 1 ? (
+               <View style={styles.buttonRow}>
+                 <TouchableOpacity 
+                   style={[styles.actionBtn, styles.selectAllBtn]} 
+                   onPress={handleSelectAll}
+                 >
+                   <Text style={styles.selectAllText}>
+                      {list.length > 0 && list.every(item => checkMap[item.id]) ? '取消全选' : '全选'}
+                   </Text>
+                 </TouchableOpacity>
+                 <TouchableOpacity 
+                   style={[styles.actionBtn, styles.removeBtn]} 
+                   onPress={handleMoveOutAll}
+                 >
+                    <ImageBackground source={{ uri: Images.common.butBgL }} style={styles.btnBg} resizeMode="stretch">
+                      <Text style={styles.removeText}>移出保险柜</Text>
+                    </ImageBackground>
+                 </TouchableOpacity>
+               </View>
+            ) : (
+                <TouchableOpacity style={styles.bottomBtn} onPress={handleTakeGoods}>
+                  <ImageBackground source={{ uri: Images.common.butBgL }} style={styles.bottomBtnBg} resizeMode="stretch">
+                    <Text style={styles.bottomBtnText}>立即提货</Text>
+                  </ImageBackground>
+                </TouchableOpacity>
+            )}
             <Text style={styles.bottomInfoText}>已选 <Text style={styles.bottomInfoCount}>{selectedCount}</Text> 件商品</Text>
-            <TouchableOpacity style={styles.bottomBtn} onPress={mainTabIndex === 0 ? handleTakeGoods : handleMoveOutAll}>
-              <ImageBackground source={{ uri: Images.common.loginBtn }} style={styles.bottomBtnBg} resizeMode="contain">
-                <Text style={styles.bottomBtnText}>{mainTabIndex === 0 ? '立即提货' : '移出保险柜'}</Text>
-              </ImageBackground>
-            </TouchableOpacity>
           </View>
         )}
         
@@ -400,72 +459,192 @@ const styles = StyleSheet.create({
   title: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
   placeholder: { width: 40 },
   content: { flex: 1 },
-  mainTabs: { flexDirection: 'row', justifyContent: 'space-around', paddingVertical: 10 },
-  mainTabItem: { alignItems: 'center', paddingHorizontal: 15 },
-  mainTabText: { color: '#aaa', fontSize: 14 },
-  mainTabTextActive: { color: '#fff', fontWeight: 'bold', fontSize: 16 },
-  mainTabLine: { width: 20, height: 3, backgroundColor: '#fff', marginTop: 5, borderRadius: 2 },
-  levelTabs: { flexDirection: 'row', paddingHorizontal: 10, paddingVertical: 8 },
-  levelTabItem: { paddingHorizontal: 12, paddingVertical: 6, marginRight: 8, borderRadius: 15, backgroundColor: 'rgba(255,255,255,0.1)' },
-  levelTabItemActive: { backgroundColor: '#FC7D2E' },
-  levelTabText: { color: '#aaa', fontSize: 12 },
-  levelTabTextActive: { color: '#fff' },
-  listContent: { paddingHorizontal: 10, paddingBottom: 150 },
-  cell: { width: '100%', minHeight: 154, marginBottom: 10, padding: 15 },
-  cellHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.1)', paddingBottom: 10, marginBottom: 10 },
+  mainTabs: { 
+    flexDirection: 'row', 
+    justifyContent: 'space-between', 
+    paddingHorizontal: 12, // Reduced from 15 to match everything else
+    paddingBottom: 2
+  },
+  mainTabItem: { 
+    width: '30%', 
+    height: 44, // Slightly taller
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
+  mainTabBg: {
+    width: '100%',
+    height: '100%',
+    justifyContent: 'center',
+    alignItems: 'center'
+  },
+  mainTabText: { fontSize: 15, color: '#333', fontWeight: 'bold' },
+  mainTabTextActive: { fontSize: 16, color: '#000', fontWeight: 'bold' },
+  mainTabLine: { display: 'none' },
+  
+  levelTabs: { 
+    flexDirection: 'row', 
+    alignItems: 'center',
+    paddingHorizontal: 10, // Add some padding back for the text content since container is full width
+    paddingVertical: 12,
+    borderBottomWidth: 1,
+    borderBottomColor: 'rgba(255,255,255,0.15)' 
+  },
+  levelTabItem: { 
+    marginRight: 25, 
+    alignItems: 'center',
+    justifyContent: 'center',
+    paddingHorizontal: 6,
+    paddingVertical: 2,
+    position: 'relative',
+    minWidth: 40
+  },
+  levelTabItemActive: { backgroundColor: 'transparent' },
+  levelTabText: { color: '#666', fontSize: 15, fontWeight: 'bold' },
+  levelTabTextActive: { 
+    color: '#ff6b00', 
+    fontSize: 17, 
+    fontWeight: '900',
+    textShadowColor: 'rgba(0, 0, 0, 0.3)',
+    textShadowOffset: { width: 1, height: 1 },
+    textShadowRadius: 1
+  },
+  
+  // Corner Decorations - Larger and jagged simulation
+  decorTL: {
+    position: 'absolute',
+    top: 0,
+    left: -4,
+    width: 0,
+    height: 0,
+    borderTopWidth: 8,
+    borderRightWidth: 8,
+    borderTopColor: '#ff6b00',
+    borderRightColor: 'transparent',
+  },
+  decorBR: {
+    position: 'absolute',
+    bottom: 0,
+    right: -4,
+    width: 0,
+    height: 0,
+    borderBottomWidth: 8,
+    borderLeftWidth: 8,
+    borderBottomColor: '#ff6b00',
+    borderLeftColor: 'transparent',
+  },
+  
+  levelInd: { display: 'none' },
+
+  listContent: { 
+    paddingHorizontal: 8, 
+    paddingVertical: 10,
+    paddingBottom: 150,
+  },
+  
+  cell: { 
+    marginBottom: 0,
+    width: '100%',
+    minHeight: 154, // 原项目 308rpx
+  },
+  cellContent: {
+    paddingTop: 15,
+    paddingBottom: 15,
+    paddingLeft: 18,
+    paddingRight: 18, // 原项目 36rpx
+  },
+  cellHeader: { 
+    flexDirection: 'row', 
+    justifyContent: 'space-between', 
+    alignItems: 'center',
+    marginBottom: 10,
+    paddingBottom: 10,
+    borderBottomWidth: 1,
+    borderBottomColor: 'rgba(0,0,0,0.15)',
+  },
   headerLeft: { flexDirection: 'row', alignItems: 'center' },
-  checkBox: { width: 18, height: 18, borderWidth: 2, borderColor: '#000', backgroundColor: '#fff', marginRight: 10, justifyContent: 'center', alignItems: 'center' },
+  checkBox: { width: 16, height: 16, borderWidth: 2, borderColor: '#000', marginRight: 8, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff' },
   checkBoxChecked: { backgroundColor: '#000' },
-  checkIcon: { color: '#fff', fontSize: 12 },
-  levelTitle: { fontSize: 16, fontWeight: 'bold' },
+  checkIcon: { color: '#fff', fontSize: 10, fontWeight: 'bold' },
+  levelTitle: { fontSize: 16, fontWeight: 'bold', textShadowColor: '#000', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 0 },
+  
   lockBox: { flexDirection: 'row', alignItems: 'center' },
-  lockText: { fontSize: 12, color: '#666' },
-  lockIcon: { width: 16, height: 16, marginLeft: 5 },
-  cellBody: { flexDirection: 'row' },
-  goodsImgBg: { width: 65, height: 65, justifyContent: 'center', alignItems: 'center', marginRight: 10 },
-  goodsImg: { width: 55, height: 55 },
-  goodsInfo: { flex: 1, justifyContent: 'space-between' },
-  goodsName: { fontSize: 14, color: '#333', fontWeight: 'bold' },
-  goodsSource: { fontSize: 12, color: '#999' },
-  bottomBar: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'rgba(0,0,0,0.8)', paddingHorizontal: 15, paddingTop: 10, alignItems: 'center' },
-  bottomInfoText: { color: '#fff', fontSize: 14, marginBottom: 10 },
-  bottomInfoCount: { fontWeight: 'bold', fontSize: 18 },
-  bottomBtn: { width: 260 },
-  bottomBtnBg: { width: 260, height: 60, justifyContent: 'center', alignItems: 'center' },
+  lockText: { fontSize: 12, color: '#666', marginRight: 4 },
+  lockIcon: { width: 16, height: 16 },
+  
+  cellBody: { 
+    flexDirection: 'row', 
+    alignItems: 'center',
+  },
+  goodsImgBg: { width: 65, height: 65, justifyContent: 'center', alignItems: 'center', marginRight: 12, padding: 7 },
+  goodsImg: { width: '100%', height: '100%' },
+  goodsInfo: { flex: 1, justifyContent: 'center', paddingRight: 8 },
+  goodsName: { fontSize: 15, color: '#333', fontWeight: 'bold', marginBottom: 6 },
+  goodsDesc: { fontSize: 12, color: '#999' },
+  arrowIcon: { fontSize: 18, color: '#ccc', marginLeft: 8 },
+
+  bottomBar: { 
+    position: 'absolute', 
+    bottom: 0, 
+    left: 0, 
+    right: 0, 
+    height: 100, // Taller for Top Button / Bottom Text layout
+    paddingBottom: 20, 
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: 'transparent' // Screenshot shows transparent or gradient? 
+  },
+  bottomBtn: { width: '80%', height: 45, marginBottom: 5 },
+  buttonRow: { flexDirection: 'row', justifyContent: 'center', width: '100%', paddingHorizontal: 20, marginBottom: 5 },
+  actionBtn: { height: 45, borderRadius: 22, justifyContent: 'center', alignItems: 'center' },
+  selectAllBtn: { width: '30%', backgroundColor: '#fff', marginRight: 10, borderWidth: 1, borderColor: '#ccc' },
+  removeBtn: { width: '65%' },
+  btnBg: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center' },
+  selectAllText: { fontSize: 16, fontWeight: 'bold', color: '#333' },
+  removeText: { fontSize: 16, fontWeight: 'bold', color: '#000' },
+  
+  bottomBtnSecondary: { display: 'none' }, // Removed
+  bottomBtnSecondaryText: { display: 'none' }, // Removed
+  
+  bottomBtnBg: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center' },
   bottomBtnText: { color: '#000', fontSize: 16, fontWeight: 'bold' },
+  bottomInfoText: { color: '#333', fontSize: 12 }, // Text below button
+  bottomInfoCount: { fontWeight: 'bold' },
+  
   emptyBox: { marginTop: 100, alignItems: 'center' },
   emptyText: { color: '#999', fontSize: 14 },
-  // 已提货样式
-  pickupTip: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#1FA4FF', padding: 8, marginBottom: 10, borderRadius: 4 },
-  pickupTipIcon: { fontSize: 14, marginRight: 6 },
-  pickupTipText: { flex: 1, color: '#fff', fontSize: 12 },
-  pickupCell: { width: '100%', marginBottom: 10, padding: 15 },
-  pickupTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.05)', paddingBottom: 10 },
-  pickupTime: { fontSize: 12, color: '#666' },
+  
+  pickupCell: { width: '100%', marginBottom: 10, padding: 12 },
+  pickupTop: { flexDirection: 'row', justifyContent: 'space-between', paddingBottom: 8, borderBottomWidth: 1, borderBottomColor: '#eee' },
+  pickupTime: { fontSize: 12, color: '#999' },
   pickupStatus: { fontSize: 12, fontWeight: 'bold' },
-  pickupTimeout: { fontSize: 11, color: '#ff6b00', marginTop: 5 },
-  pickupAddress: { flexDirection: 'row', alignItems: 'flex-start', paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.05)' },
-  locationIcon: { fontSize: 16, marginRight: 10 },
+  pickupTimeout: { fontSize: 11, color: '#ff6b00', marginTop: 4 },
+  pickupAddress: { flexDirection: 'row', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#eee' },
+  locationIcon: { fontSize: 16, marginRight: 8 },
   addressInfo: { flex: 1 },
-  addressName: { fontSize: 14, color: '#333', fontWeight: 'bold' },
+  addressName: { fontSize: 14, fontWeight: 'bold', color: '#333' },
   addressDetail: { fontSize: 12, color: '#666', marginTop: 4 },
-  pickupGoodsBox: { backgroundColor: '#f8f8f8', borderRadius: 6, padding: 10, marginVertical: 10 },
+  pickupGoodsBox: { paddingVertical: 10 },
   pickupGoodsList: { flexDirection: 'row' },
-  pickupGoodsItem: { width: 79, height: 103, backgroundColor: '#fff', borderRadius: 6, marginRight: 8, alignItems: 'center', justifyContent: 'center', position: 'relative', borderWidth: 1, borderColor: '#eaeaea' },
-  pickupGoodsImg: { width: 73, height: 85 },
-  pickupGoodsCount: { position: 'absolute', top: 0, right: 0, backgroundColor: '#ff6b00', borderRadius: 2, paddingHorizontal: 4, paddingVertical: 2 },
-  pickupGoodsCountText: { color: '#fff', fontSize: 10, fontWeight: 'bold' },
-  pickupOrderRow: { flexDirection: 'row', alignItems: 'center', borderTopWidth: 1, borderTopColor: 'rgba(0,0,0,0.05)', paddingTop: 14 },
+  pickupGoodsItem: { marginRight: 10, alignItems: 'center' },
+  pickupGoodsImg: { width: 60, height: 60, borderRadius: 4 },
+  pickupGoodsCount: { position: 'absolute', right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 4, borderRadius: 4 },
+  pickupGoodsCountText: { color: '#fff', fontSize: 10 },
+  pickupOrderRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 8 },
   pickupOrderLabel: { fontSize: 12, color: '#666' },
-  pickupOrderNo: { flex: 1, fontSize: 12, color: '#333', textAlign: 'right' },
-  copyBtn: { backgroundColor: '#1FA4FF', borderRadius: 4, paddingHorizontal: 6, paddingVertical: 4, marginLeft: 5 },
-  copyBtnText: { color: '#fff', fontSize: 12 },
-  pickupInfoRow: { flexDirection: 'row', alignItems: 'center', paddingTop: 10 },
+  pickupOrderNo: { flex: 1, fontSize: 12, color: '#333' },
+  copyBtn: { paddingHorizontal: 8, paddingVertical: 4, backgroundColor: '#f5f5f5', borderRadius: 4 },
+  copyBtnText: { fontSize: 12, color: '#666' },
+  pickupInfoRow: { flexDirection: 'row', paddingVertical: 4 },
   pickupInfoLabel: { fontSize: 12, color: '#666' },
   pickupInfoValue: { fontSize: 12, color: '#333' },
-  pickupBottom: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', paddingTop: 10 },
+  pickupBottom: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingTop: 10 },
   pickupExpressAmount: { fontSize: 12, color: '#333' },
-  priceText: { color: '#ff6b00' },
-  expressBtn: { backgroundColor: '#1FA4FF', borderRadius: 12, paddingHorizontal: 10, paddingVertical: 6, marginLeft: 15 },
-  expressBtnText: { color: '#fff', fontSize: 12 },
+  priceText: { color: '#ff6b00', fontWeight: 'bold' },
+  expressBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#fec433', borderRadius: 4 },
+  expressBtnText: { fontSize: 12, color: '#000', fontWeight: 'bold' },
+  pickupTip: { flexDirection: 'row', alignItems: 'center', padding: 10, backgroundColor: 'rgba(255,235,200,0.8)', marginHorizontal: 8, borderRadius: 6, marginBottom: 10 },
+  pickupTipIcon: { fontSize: 14, marginRight: 6 },
+  pickupTipText: { flex: 1, fontSize: 11, color: '#ff6b00' },
+  goodsSource: { fontSize: 12, color: '#666', opacity: 0.8 },
+  arrow: { fontSize: 18, color: '#fec433', marginLeft: 8 },
 });

+ 23 - 24
app/wallet/recharge.tsx

@@ -1,3 +1,4 @@
+import Alipay from 'expo-native-alipay';
 import { Stack, useRouter } from 'expo-router';
 import React, { useState } from 'react';
 import { ActivityIndicator, Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
@@ -31,34 +32,32 @@ export default function RechargeScreen() {
             return;
         }
 
+        setLoading(true);
         setLoading(true);
         try {
-            // Uniapp logic: generatePaymentLink -> tradeNo
-            const res = await services.wallet.generatePaymentLink(amount, 'ALIPAY_H5', 'CASH');
+            // Use ALIPAY_APP for native payment with correct API
+            const res: any = await services.wallet.rechargeSubmit(amount, 'ALIPAY_APP', 'CASH');
             
-            if (res && res.data && res.data.tradeNo) {
-                 const tradeNo = res.data.tradeNo;
-                 Alert.alert(
-                    '提示', 
-                    '已生成充值订单,请完成支付',
-                    [
-                        { 
-                            text: '已支付', 
-                            onPress: async () => {
-                                const checkRes = await services.wallet.checkPaymentStatus({ tradeNo: tradeNo }); 
-                                if (checkRes && checkRes.code === 0 && checkRes.data.paySuccess) {
-                                    Alert.alert('提示', '支付成功');
-                                    router.replace('/wallet/recharge_record');
-                                } else {
-                                    Alert.alert('提示', checkRes.msg || '支付未完成或查询失败');
-                                }
-                            } 
-                        },
-                        { text: '稍后支付' }
-                    ]
-                 );
+            console.log('Recharge API Res:', res);
+
+            // Handle response which might be direct string or object with payInfo
+            const payInfo = typeof res?.data === 'string' ? res?.data : (res?.data?.payInfo || res?.data?.orderInfo || res?.data?.tradeNo);
+
+            if (payInfo && typeof payInfo === 'string' && (payInfo.startsWith('alipay_root_cert_sn') || payInfo.includes('alipay_sdk'))) {
+                 Alipay.setAlipayScheme('alipay2021005175632205');
+                 const result = await Alipay.pay(payInfo);
+                 console.log('Alipay Result:', result);
+                 
+                 if (result?.resultStatus === '9000') {
+                     Alert.alert('提示', '充值成功');
+                     router.replace('/wallet/recharge_record');
+                 } else {
+                     Alert.alert('提示', '支付未完成');
+                 }
+            } else if (res?.data?.tradeNo && !payInfo) {
+                 Alert.alert('提示', '获取支付信息失败(仅获取到订单号)');
             } else {
-                 Alert.alert('失败', '生成充值订单失败');
+                 Alert.alert('失败', '生成充值订单失败 ' + (res?.msg || ''));
             }
 
         } catch (error) {

+ 73 - 41
app/weal/components/WishRuleModal.tsx

@@ -1,38 +1,73 @@
 import { Images } from '@/constants/images';
-import { Image } from 'expo-image';
+import { getParamConfig } from '@/services/user';
 import React, { forwardRef, useImperativeHandle, useState } from 'react';
-import { ImageBackground, Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { Dimensions, ImageBackground, Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
 
 export interface WishRuleModalRef {
     show: () => void;
     close: () => void;
 }
 
+// 简单的HTML标签清理函数
+const stripHtmlTags = (html: string): string => {
+    if (!html) return '';
+    return html
+        .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
+        .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
+        .replace(/<[^>]+>/g, '\n')
+        .replace(/&nbsp;/g, ' ')
+        .replace(/&lt;/g, '<')
+        .replace(/&gt;/g, '>')
+        .replace(/&amp;/g, '&')
+        .replace(/\n\s*\n/g, '\n\n') // Merge multiple newlines
+        .trim();
+};
+
 export const WishRuleModal = forwardRef<WishRuleModalRef, {}>((props, ref) => {
     const [visible, setVisible] = useState(false);
+    const [content, setContent] = useState('');
 
     useImperativeHandle(ref, () => ({
-        show: () => setVisible(true),
+        show: () => {
+            setVisible(true);
+            loadRule();
+        },
         close: () => setVisible(false),
     }));
 
+    const loadRule = async () => {
+        try {
+            const res = await getParamConfig('wish_rule');
+            if (res && res.data) {
+                setContent(stripHtmlTags(res.data));
+            }
+        } catch (error) {
+            console.error('加载祈愿规则失败:', error);
+        }
+    };
+
     if (!visible) return null;
 
     return (
         <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
             <View style={styles.overlay}>
-                <View style={styles.contentContainer}>
-                    <ImageBackground style={styles.content} source={{ uri: Images.welfare.welfareDialogBg }} resizeMode="contain">
-                        <View style={styles.titleBox}>
-                            <Image style={styles.title} source={{ uri: Images.welfare.qijiWelfareDollRule }} resizeMode="contain" />
-                        </View>
-                        <View style={styles.closeBox}>
-                            <TouchableOpacity onPress={() => setVisible(false)}>
-                                <Image style={styles.close} source={{ uri: Images.common.closeBut }} />
-                            </TouchableOpacity>
-                        </View>
-                        <Image source={{ uri: Images.welfare.catchDollRule }} style={styles.ruleImage} contentFit="contain" />
+                <View style={styles.windowSection}>
+                    <ImageBackground style={styles.main} source={{ uri: Images.common.windBg }} resizeMode="stretch">
+                        <ScrollView 
+                            style={styles.scrollView}
+                            contentContainerStyle={styles.scrollContent}
+                            showsVerticalScrollIndicator={false}
+                        >
+                            <Text style={styles.text}>{content || '暂无规则内容'}</Text>
+                        </ScrollView>
                     </ImageBackground>
+
+                    {/* 关闭按钮 - 移到底部 */}
+                    <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
+                        <ImageBackground source={{ uri: Images.common.closeBut }} style={styles.closeIcon} resizeMode="contain" />
+                    </TouchableOpacity>
                 </View>
             </View>
         </Modal>
@@ -48,40 +83,37 @@ const styles = StyleSheet.create({
         justifyContent: 'center',
         alignItems: 'center',
     },
-    contentContainer: {
-        width: 300,
-        height: 400,
+    windowSection: {
+        width: SCREEN_WIDTH,
         alignItems: 'center',
-        justifyContent: 'center',
     },
-    content: {
-        width: '100%',
-        height: '100%',
+    main: {
+        width: 360, // 720rpx
+        height: 302, // 604rpx
+        paddingTop: 90, // 180rpx
+        paddingHorizontal: 30, // 60rpx
         alignItems: 'center',
-        justifyContent: 'center',
-    },
-    titleBox: {
-        position: 'absolute',
-        top: -20,
         zIndex: 1,
+        marginBottom: 20, // Space between modal and close button
+    },
+    scrollView: {
+        width: '100%',
+        height: 190, // 380rpx
     },
-    title: {
-        width: 200,
-        height: 50,
+    scrollContent: {
+        paddingBottom: 20,
     },
-    closeBox: {
-        position: 'absolute',
-        top: 0,
-        right: -10,
-        zIndex: 2,
+    text: {
+        fontSize: 14,
+        color: '#8B4513',
+        lineHeight: 22,
     },
-    close: {
-        width: 30,
-        height: 30,
+    closeBtn: {
+        width: 35,
+        height: 35,
     },
-    ruleImage: {
-        width: 260,
-        height: 300,
-        marginTop: 20,
+    closeIcon: {
+        width: '100%',
+        height: '100%',
     },
 });

+ 27 - 4
app/weal/detail.tsx

@@ -7,6 +7,7 @@ import {
     ImageBackground,
     Modal,
     ScrollView,
+    Share,
     StatusBar,
     StyleSheet,
     Text,
@@ -144,6 +145,26 @@ export default function WealDetailScreen() {
         }
     };
 
+    const handleShare = async () => {
+        try {
+            const result = await Share.share({
+                message: `快来参与福利房:${data?.name},房间ID:${id}`,
+                title: '福利房分享',
+            });
+            if (result.action === Share.sharedAction) {
+                if (result.activityType) {
+                    // shared with activity type of result.activityType
+                } else {
+                    // shared
+                }
+            } else if (result.action === Share.dismissedAction) {
+                // dismissed
+            }
+        } catch (error: any) {
+            Alert.alert(error.message);
+        }
+    };
+
     const showWinRecords = async () => {
         try {
             const res = await getWinningRecord(id as string);
@@ -170,7 +191,9 @@ export default function WealDetailScreen() {
                         <Text style={styles.backText}>←</Text>
                     </TouchableOpacity>
                     <Text style={styles.navTitle}>{TYPE_MAP[data.type]?.title || '详情'}</Text>
-                    <View style={styles.placeholder} />
+                    <TouchableOpacity onPress={handleShare} style={styles.backBtn}>
+                        <Image source={{ uri: Images.mine.invite }} style={{ width: 20, height: 20 }} contentFit="contain" />
+                    </TouchableOpacity>
                 </View>
 
                 <ScrollView
@@ -226,14 +249,14 @@ export default function WealDetailScreen() {
                                 <Text style={styles.cardTitle}>参与度</Text>
                                 <Text style={styles.cardNum}>{data.participatingList?.length}个玩家</Text>
                             </View>
-                            <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.userList}>
+                            <View style={[styles.userList, { overflow: 'hidden' }]}>
                                 {data.participatingList?.map((user: any, index: number) => (
                                     <View key={index} style={styles.userItem}>
                                         <Image source={{ uri: user.avatar }} style={styles.userAvatar} />
                                         <Text style={styles.userName} numberOfLines={1}>{user.nickname}</Text>
                                     </View>
                                 ))}
-                            </ScrollView>
+                            </View>
                         </View>
                     </View>
                     <View style={{ height: 120 }} />
@@ -353,7 +376,7 @@ const styles = StyleSheet.create({
     goodsItem: { width: '31%', aspectRatio: 0.8, backgroundColor: 'rgba(0,0,0,0.3)', borderRadius: 10, padding: 8, marginBottom: 10, alignItems: 'center' },
     goodsImg: { width: '80%', height: '60%' },
     goodsName: { color: '#fff', fontSize: 10, marginTop: 5 },
-    goodsCountTag: { position: 'absolute', left: 0, bottom: 25, backgroundColor: '#FFDD00', paddingHorizontal: 5, borderTopRightRadius: 5, borderBottomRightRadius: 5 },
+    goodsCountTag: { position: 'absolute', left: 0, bottom: 10, backgroundColor: '#FFDD00', paddingHorizontal: 5, borderTopRightRadius: 5, borderBottomRightRadius: 5 },
     goodsCountText: { color: '#000', fontSize: 8, fontWeight: 'bold' },
 
     participantSection: { marginTop: 20, backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 15, padding: 15 },

+ 45 - 14
app/weal/room.tsx

@@ -2,16 +2,17 @@ import { Image } from 'expo-image';
 import { useRouter } from 'expo-router';
 import React, { useCallback, useEffect, useRef, useState } from 'react';
 import {
-  ActivityIndicator,
-  ImageBackground,
-  RefreshControl,
-  ScrollView,
-  StatusBar,
-  StyleSheet,
-  Text,
-  TextInput,
-  TouchableOpacity,
-  View,
+    ActivityIndicator,
+    Dimensions,
+    ImageBackground,
+    RefreshControl,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TextInput,
+    TouchableOpacity,
+    View,
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
@@ -27,7 +28,9 @@ interface RoomItem {
   officialFlag: number;
   user: { avatar: string; username: string };
   luckRoomGoodsList: { spu: { cover: string } }[];
+
   participatingList: any[];
+  mode?: string;
 }
 
 export default function RoomScreen() {
@@ -96,7 +99,11 @@ export default function RoomScreen() {
   };
 
   const handleRoomPress = (item: RoomItem) => {
-    router.push({ pathname: '/weal/detail', params: { id: item.id } } as any);
+    if (item.mode === 'YFS_PRO') {
+        router.push({ pathname: '/award-detail-yfs', params: { id: item.id } } as any);
+    } else {
+        router.push({ pathname: '/weal/detail', params: { id: item.id } } as any);
+    }
   };
 
   const isItemType = (type: string) => {
@@ -176,6 +183,12 @@ export default function RoomScreen() {
                   onPress={() => handleTypeChange(item, index)}
                 >
                   <Text style={[styles.typeText, typeIndex === index && styles.typeTextActive]}>{item.name}</Text>
+                  {typeIndex === index && (
+                      <>
+                        <Image source={{ uri: Images.mine.typeSelectIconT }} style={styles.typeSelectIconT} />
+                        <Image source={{ uri: Images.mine.typeSelectIconB }} style={styles.typeSelectIconB} />
+                      </>
+                  )}
                 </TouchableOpacity>
               ))}
             </ScrollView>
@@ -245,6 +258,8 @@ export default function RoomScreen() {
   );
 }
 
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+
 const styles = StyleSheet.create({
   container: { flex: 1, backgroundColor: '#1a1a2e' },
   background: { flex: 1 },
@@ -281,7 +296,7 @@ const styles = StyleSheet.create({
   // 类型切换
   typeContainer: { paddingHorizontal: 15, marginBottom: 40 },
   typeSection: {},
-  typeItem: { marginRight: 20, paddingVertical: 8 },
+  typeItem: { marginRight: 15, width: 55, height: 38, justifyContent: 'center', alignItems: 'center', position: 'relative' },
   typeText: { fontSize: 12, color: '#DFDFDF' },
   typeTextActive: {
     color: '#e79018',
@@ -291,10 +306,26 @@ const styles = StyleSheet.create({
     textShadowOffset: { width: 1, height: 1 },
     textShadowRadius: 1
   },
+  typeSelectIconT: {
+    position: 'absolute',
+    right: -5,
+    top: 5,
+    zIndex: 2,
+    width: 28,
+    height: 10,
+  },
+  typeSelectIconB: {
+      position: 'absolute',
+      left: 0,
+      bottom: 0,
+      zIndex: 2,
+      width: 24,
+      height: 14,
+  },
 
   // 房间列表
-  roomList: { paddingHorizontal: 15 },
-  roomItem: { width: '100%', height: 84, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, marginBottom: 6, position: 'relative' },
+  roomList: { paddingHorizontal: 0, alignItems: 'center' },
+  roomItem: { width: SCREEN_WIDTH - 12, height: 84, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, marginBottom: 6, position: 'relative' },
   officialBadge: { position: 'absolute', right: 0, top: 0, width: 48, height: 22 },
   officialImg: { width: '100%', height: '100%' },
   mustBeBadge: { position: 'absolute', left: 0, top: 0, width: 51, height: 51 },

+ 37 - 1
app/weal/store_choose.tsx

@@ -101,6 +101,18 @@ const StoreChooseScreen = () => {
         router.back();
     };
 
+    const handleSelectAll = () => {
+        if (list.length === 0) return;
+        
+        const allSelected = list.every(item => selectedIds.includes(item.id));
+        if (allSelected) {
+            setSelectedIds([]);
+        } else {
+            const allIds = list.map(item => item.id);
+            setSelectedIds(allIds);
+        }
+    };
+
     const renderItem = ({ item }: { item: any }) => {
         const isSelected = selectedIds.includes(item.id);
         return (
@@ -173,13 +185,20 @@ const StoreChooseScreen = () => {
                 />
 
                 <View style={[styles.footer as any, { paddingBottom: Math.max(insets.bottom, 20) }]}>
+                    <TouchableOpacity style={styles.selectAllBtn} onPress={handleSelectAll}>
+                        <Image
+                            source={{ uri: list.length > 0 && selectedIds.length === list.length ? Images.mine.checkAll : Images.mine.checkAll }} 
+                            style={[styles.selectAllIcon, { opacity: list.length > 0 && selectedIds.length === list.length ? 1 : 0.5 }]} 
+                            resizeMode="contain" 
+                        />
+                    </TouchableOpacity>
                     <TouchableOpacity style={styles.submitBtn as any} onPress={handleConfirm}>
                         <ImageBackground
                             source={{ uri: Images.common.butBgL }}
                             style={styles.submitBtnBg as any}
                             resizeMode="stretch"
                         >
-                            <Text style={styles.submitBtnText}>确认选择放入赠品池</Text>
+                            <Text style={styles.submitBtnText}>确认选择 ({selectedIds.length})</Text>
                         </ImageBackground>
                     </TouchableOpacity>
                 </View>
@@ -323,6 +342,23 @@ const styles = StyleSheet.create({
         color: '#999',
         fontSize: 14,
     },
+    selectAllBtn: {
+        position: 'absolute',
+        top: -50,
+        right: 20,
+        flexDirection: 'row',
+        alignItems: 'center',
+    },
+    selectAllIcon: {
+        width: 30,
+        height: 30,
+        marginRight: 5,
+    },
+    selectAllText: {
+        color: '#fff',
+        fontSize: 14,
+        fontWeight: 'bold',
+    },
 });
 
 export default StoreChooseScreen;

+ 28 - 21
app/weal/wish.tsx

@@ -1,21 +1,21 @@
 import { Images } from '@/constants/images';
 import Service from '@/services/weal';
+import EventUtils from '@/utils/event';
 import { useRouter } from 'expo-router';
 import React, { useCallback, useEffect, useRef, useState } from 'react';
 import {
-  Alert,
-  Dimensions,
-  Image,
-  ImageBackground,
-  ScrollView,
-  StatusBar,
-  StyleSheet,
-  Text,
-  TouchableOpacity,
-  View,
+    Alert,
+    Dimensions,
+    Image,
+    ImageBackground,
+    ScrollView,
+    StatusBar,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
 } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import { WishMaterialModal, WishMaterialModalRef } from './components/WishMaterialModal';
 import { WishRecordModal, WishRecordModalRef } from './components/WishRecordModal';
 import { WishRuleModal, WishRuleModalRef } from './components/WishRuleModal';
 
@@ -59,10 +59,8 @@ export default function WishScreen() {
   const [progress, setProgress] = useState(0);
   const [magicAmount, setMagicAmount] = useState(0);
 
-  // Modals
   const ruleRef = useRef<WishRuleModalRef>(null);
   const recordRef = useRef<WishRecordModalRef>(null);
-  const materialRef = useRef<WishMaterialModalRef>(null);
 
   const loadData = useCallback(async () => {
     try {
@@ -139,16 +137,26 @@ export default function WishScreen() {
     setGoodsList(goodsList.filter((g) => g.id !== item.id));
   };
 
-  const openMaterialModal = () => {
-    if (!currentItem) return;
-    materialRef.current?.show((selected) => {
-      // Add selected items to goodsList
+  useEffect(() => {
+    const subscription = EventUtils.on(EventUtils.keys.STORE_CHOOSE, (selectedMap: any) => {
+      // SelectedMap comes as Array from store_choose
+      const selectedList = Array.isArray(selectedMap) ? selectedMap : Object.values(selectedMap);
+      
       setGoodsList(prev => {
-        const newIds = new Set(prev.map(i => i.id));
-        const toAdd = selected.filter(i => !newIds.has(i.id));
-        return [...prev, ...toAdd];
+        const existingIds = new Set(prev.map(i => i.id));
+        const newItems = selectedList.filter((i: any) => !existingIds.has(i.id));
+        return [...prev, ...newItems];
       });
     });
+
+    return () => {
+      subscription.remove();
+    };
+  }, []);
+
+  const openMaterialModal = () => {
+    if (!currentItem) return;
+    router.push('/weal/store_choose');
   };
 
   const submitWish = async () => {
@@ -353,7 +361,6 @@ export default function WishScreen() {
 
         <WishRuleModal ref={ruleRef} />
         <WishRecordModal ref={recordRef} />
-        <WishMaterialModal ref={materialRef} />
       </ImageBackground>
     </View>
   );

+ 175 - 0
components/Barrage.tsx

@@ -0,0 +1,175 @@
+import { Image } from 'expo-image';
+import React, { useEffect, useRef, useState } from 'react';
+import { Animated, Easing, ImageBackground, StyleSheet, Text, View } from 'react-native';
+
+import { Images } from '@/constants/images';
+
+interface BarrageItemType {
+  id?: string;
+  poolId?: string;
+  nickname?: string;
+  poolName?: string;
+  text?: string; // amount or feedback
+  type?: string; // '奖池'
+  avatar?: string;
+}
+
+interface BarrageProps {
+  data: BarrageItemType[];
+  speed?: number; // ms per pixel? Or just a factor.
+  style?: any;
+}
+
+export const Barrage: React.FC<BarrageProps> = ({ data, speed = 30, style }) => {
+  const [contentWidth, setContentWidth] = useState(0);
+  const translateX = useRef(new Animated.Value(0)).current;
+
+  useEffect(() => {
+    if (contentWidth > 0 && data.length > 0) {
+      startAnimation();
+    }
+  }, [contentWidth, data]);
+
+  const startAnimation = () => {
+    // Reset to 0
+    translateX.setValue(0);
+    
+    // Duration: width * factor. 
+    // If width is 1000, speed 30 -> 30000ms (30s).
+    const duration = contentWidth * speed;
+
+    Animated.loop(
+      Animated.timing(translateX, {
+        toValue: -contentWidth,
+        duration: duration,
+        easing: Easing.linear,
+        useNativeDriver: true,
+      })
+    ).start();
+  };
+
+  const renderItem = (item: BarrageItemType, index: number) => {
+    const isPool = item.poolId && Number(item.text || '0') > 0;
+    // item.type == '奖池' logic from Vue
+    
+    return (
+      <ImageBackground
+        key={`${item.id || index}-main`}
+        source={{ uri: Images.box.barrageItem }}
+        style={styles.itemBg}
+        resizeMode="stretch"
+      >
+        <View style={styles.contentRow}>
+            {/* Avatar */}
+            <View style={styles.avatarBox}>
+                <Image source={{ uri: item.avatar }} style={styles.avatar} contentFit="cover" />
+            </View>
+            
+            {/* Text Content */}
+            <View style={styles.textContainer}>
+                {isPool ? (
+                    <Text style={styles.text} numberOfLines={1}>
+                        <Text style={styles.nickname}>{item.nickname?.slice(0,1) + '***' + item.nickname?.slice(-1)}</Text>
+                        <Text> 在 </Text>
+                        <Text style={styles.poolName}>{item.poolName}</Text>
+                        <Text> {item.type === '奖池' ? '消费了' : '获得'} </Text>
+                        <Text style={styles.amount}>{item.text}</Text>
+                        <Text> {item.type === '奖池' ? '元' : ''} </Text>
+                    </Text>
+                ) : (
+                   <Text style={styles.text} numberOfLines={1}>
+                       <Text style={styles.nickname}>{item.nickname?.slice(0,1) + '***' + item.nickname?.slice(-1)}</Text>
+                       <Text>: {item.text}</Text>
+                   </Text>
+                )}
+            </View>
+        </View>
+      </ImageBackground>
+    );
+  };
+
+  if (!data || data.length === 0) return null;
+
+  return (
+    <View style={[styles.container, style]}>
+      <Animated.View
+        style={[
+          styles.scrollContainer,
+          { transform: [{ translateX }] },
+        ]}
+      >
+        {/* Measure the width of the first set */}
+        <View 
+            style={styles.row} 
+            onLayout={(e) => setContentWidth(e.nativeEvent.layout.width)}
+        >
+            {data.map((item, index) => renderItem(item, index))}
+        </View>
+        {/* Duplicate set for seamless loop */}
+        <View style={styles.row}>
+            {data.map((item, index) => renderItem(item, index))}
+        </View>
+      </Animated.View>
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    width: '100%',
+    overflow: 'hidden',
+    height: 40, // Adjust based on item height
+  },
+  scrollContainer: {
+    flexDirection: 'row',
+  },
+  row: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  itemBg: {
+    paddingHorizontal: 12, // Reduced padding
+    paddingVertical: 4,
+    marginRight: 10,
+    justifyContent: 'center',
+    height: 32, // Fixed height
+    minWidth: 150,
+  },
+  contentRow: {
+      flexDirection: 'row',
+      alignItems: 'center',
+  },
+  avatarBox: {
+      marginRight: 6,
+      width: 24,
+      height: 24,
+      borderRadius: 12,
+      borderWidth: 1,
+      borderColor: '#000',
+      overflow: 'hidden',
+      backgroundColor: '#fff',
+  },
+  avatar: {
+      width: '100%',
+      height: '100%',
+  },
+  textContainer: {
+      justifyContent: 'center',
+  },
+  text: {
+      color: '#fff',
+      fontSize: 10,
+  },
+  nickname: {
+      fontWeight: 'bold',
+  },
+  poolName: {
+      color: '#0084FF',
+      fontSize: 10,
+  },
+  amount: {
+      color: '#FF0000',
+      fontSize: 11,
+      fontWeight: 'bold',
+  }
+});

+ 31 - 9
components/CustomTabBar.tsx

@@ -1,6 +1,6 @@
 import { Images } from '@/constants/images';
 import { Image } from 'expo-image';
-import { usePathname, useRouter } from 'expo-router';
+import { usePathname, useRouter, useSegments } from 'expo-router';
 import React from 'react';
 import { Dimensions, ImageBackground, StyleSheet, TouchableOpacity, View } from 'react-native';
 import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -36,18 +36,39 @@ const tabList = [
 
 export function CustomTabBar() {
   const router = useRouter();
+  const segments = useSegments();
   const pathname = usePathname();
   const insets = useSafeAreaInsets();
 
   const getTabIndex = () => {
-    if (pathname === '/' || pathname === '/index') return 0;
-    if (pathname === '/box') return 1;
-    if (pathname === '/welfare') return 2;
-    if (pathname === '/mine') return 3; 
-    return 0;
+    // Check Box
+    if (segments[1] === 'box' || pathname?.startsWith('/box')) return 1;
+    // Check Welfare
+    if ((segments[1] as string) === 'weal' || (segments[1] as string) === 'welfare' || pathname?.startsWith('/weal') || pathname?.startsWith('/welfare')) return 2;
+    // Check Mine
+    if (segments[1] === 'mine' || pathname?.startsWith('/mine')) return 3;
+    
+    // Check Home (Explicit)
+    // Home is usually index. or path /
+    if ((segments[1] as string) === 'index' || pathname === '/' || pathname === '/index') return 0;
+
+    // No valid tab match (e.g. navigating to detail page)
+    return -1;
   };
 
-  const currentIndex = getTabIndex();
+  // Initialize with correct value to avoid mount flash
+  const [currentIndex, setCurrentIndex] = React.useState(() => {
+      const idx = getTabIndex();
+      return idx === -1 ? 0 : idx;
+  });
+
+  // Update only when valid match found (avoids unmatch flash)
+  React.useEffect(() => {
+      const idx = getTabIndex();
+      if (idx !== -1) {
+          setCurrentIndex(idx);
+      }
+  }, [segments, pathname]);
 
   const handlePress = (index: number) => {
     const route = tabList[index].route;
@@ -58,8 +79,9 @@ export function CustomTabBar() {
   const bottomPadding = insets.bottom;
 
   return (
-    <View style={[styles.wrapper, { paddingBottom: bottomPadding }]}>
+    <View style={styles.wrapper}>
       <ImageBackground
+        source={{ uri: Images.common.tabsBg }}
         style={styles.container}
         resizeMode="cover"
       >
@@ -74,7 +96,7 @@ export function CustomTabBar() {
               <Image
                 source={currentIndex === index ? item.active : item.img}
                 style={styles.icon}
-                contentFit="contain"
+                contentFit="fill"
               />
             </TouchableOpacity>
           ))}

+ 280 - 0
components/RegionPicker.tsx

@@ -0,0 +1,280 @@
+import { getArea } from '@/services/address';
+import React, { useEffect, useState } from 'react';
+import {
+    ActivityIndicator,
+    Dimensions,
+    Modal,
+    ScrollView,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+
+interface AreaItem {
+  id: string;
+  name: string;
+  pid?: number;
+}
+
+interface RegionPickerProps {
+  visible: boolean;
+  onClose: () => void;
+  onSelect: (province: string, city: string, district: string) => void;
+}
+
+const { width, height } = Dimensions.get('window');
+
+export const RegionPicker: React.FC<RegionPickerProps> = ({
+  visible,
+  onClose,
+  onSelect,
+}) => {
+  const [loading, setLoading] = useState(false);
+  const [tabs, setTabs] = useState(['请选择', '', '']);
+  const [activeTab, setActiveTab] = useState(0);
+
+  const [provinces, setProvinces] = useState<AreaItem[]>([]);
+  const [cities, setCities] = useState<AreaItem[]>([]);
+  const [districts, setDistricts] = useState<AreaItem[]>([]);
+
+  const [selectedProvince, setSelectedProvince] = useState<AreaItem | null>(null);
+  const [selectedCity, setSelectedCity] = useState<AreaItem | null>(null);
+  // District selection ends the flow
+
+  useEffect(() => {
+    if (visible && provinces.length === 0) {
+      loadProvinces();
+    }
+  }, [visible]);
+
+  const loadProvinces = async () => {
+    setLoading(true);
+    try {
+      // Assuming pid=0 or 1 for top level. service says pid=1 is default
+      const list = await getArea(0); // Try 0 first, typically 0 is root
+      if (list && list.length > 0) {
+          setProvinces(list);
+      } else {
+        // Fallback or retry with 1 if 0 returns empty/null?
+        // Service code: pid ? { pid } : { pid: 1 }. So if I pass 0, it passes {pid:0}.
+        // Let's assume typical tree structure. If fail, maybe try different PID or log error.
+        // Actually, let's try 0.
+         setProvinces(list || []);
+      }
+    } catch (e) {
+      console.error(e);
+    }
+    setLoading(false);
+  };
+
+  const handleSelect = async (item: AreaItem) => {
+    if (activeTab === 0) {
+      // Province selected
+      setSelectedProvince(item);
+      setTabs([item.name, '请选择', '']);
+      setActiveTab(1);
+      setLoading(true);
+      const list = await getArea(Number(item.id));
+      setCities(list || []);
+      setLoading(false);
+    } else if (activeTab === 1) {
+      // City selected
+      setSelectedCity(item);
+      setTabs([selectedProvince!.name, item.name, '请选择']);
+      setActiveTab(2);
+      setLoading(true);
+      const list = await getArea(Number(item.id));
+      setDistricts(list || []);
+      setLoading(false);
+    } else {
+      // District selected
+      onSelect(selectedProvince!.name, selectedCity!.name, item.name);
+      onClose();
+    }
+  };
+
+  const handleTabPress = (index: number) => {
+    // Only allow going back to previous tabs if data exists
+    if (index < activeTab) {
+      setActiveTab(index);
+    }
+  };
+
+  const renderList = () => {
+    let data : AreaItem[] = [];
+    if (activeTab === 0) data = provinces;
+    else if (activeTab === 1) data = cities;
+    else data = districts;
+
+    // Filter out invalid items if strictly needed, but let's trust API
+    return (
+      <ScrollView contentContainerStyle={styles.listContent}>
+        {data.map((item) => {
+            const isSelected = 
+                (activeTab === 0 && item.id === selectedProvince?.id) ||
+                (activeTab === 1 && item.id === selectedCity?.id);
+            
+            return (
+          <TouchableOpacity
+            key={item.id}
+            style={styles.item}
+            onPress={() => handleSelect(item)}
+          >
+            <Text style={[styles.itemText, isSelected && styles.itemTextActive]}>
+              {item.name}
+            </Text>
+            {isSelected && <Text style={styles.checkIcon}>✓</Text>}
+          </TouchableOpacity>
+        )})}
+      </ScrollView>
+    );
+  };
+
+  return (
+    <Modal
+      visible={visible}
+      transparent
+      animationType="slide"
+      onRequestClose={onClose}
+    >
+      <View style={styles.mask}>
+        <TouchableOpacity style={styles.maskClickable} onPress={onClose} />
+        <View style={styles.container}>
+          <View style={styles.header}>
+            <Text style={styles.title}>配送至</Text>
+            <TouchableOpacity onPress={onClose} style={styles.closeBtn}>
+              <Text style={styles.closeText}>✕</Text>
+            </TouchableOpacity>
+          </View>
+
+          <View style={styles.tabs}>
+            {tabs.map((tab, index) => (
+              tab ? (
+              <TouchableOpacity
+                key={index}
+                onPress={() => handleTabPress(index)}
+                style={[styles.tabItem, activeTab === index && styles.tabItemActive]}
+                disabled={!tab || (index > activeTab)} 
+              >
+                <Text style={[styles.tabText, activeTab === index && styles.tabTextActive]}>
+                  {tab}
+                </Text>
+                {activeTab === index && <View style={styles.tabLine} />}
+              </TouchableOpacity>
+              ) : null
+            ))}
+          </View>
+
+          {loading ? (
+            <View style={styles.loadingBox}>
+                <ActivityIndicator size="small" color="#e79018" />
+            </View>
+          ) : (
+            renderList()
+          )}
+        </View>
+      </View>
+    </Modal>
+  );
+};
+
+const styles = StyleSheet.create({
+  mask: {
+    flex: 1,
+    backgroundColor: 'rgba(0,0,0,0.5)',
+    justifyContent: 'flex-end',
+  },
+  maskClickable: {
+    flex: 1,
+  },
+  container: {
+    height: height * 0.7, // 70% height
+    backgroundColor: '#fff',
+    borderTopLeftRadius: 16,
+    borderTopRightRadius: 16,
+  },
+  header: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center', // Title centered
+    height: 50,
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee',
+    position: 'relative',
+  },
+  title: {
+    fontSize: 16,
+    fontWeight: 'bold',
+    color: '#333',
+  },
+  closeBtn: {
+    position: 'absolute',
+    right: 15,
+    top: 0,
+    bottom: 0,
+    justifyContent: 'center',
+    paddingHorizontal: 10,
+  },
+  closeText: {
+    fontSize: 18,
+    color: '#999',
+  },
+  tabs: {
+    flexDirection: 'row',
+    borderBottomWidth: 1,
+    borderBottomColor: '#eee',
+    paddingHorizontal: 15,
+  },
+  tabItem: {
+    marginRight: 25,
+    height: 44,
+    justifyContent: 'center',
+    position: 'relative',
+  },
+  tabItemActive: {},
+  tabText: {
+    fontSize: 14,
+    color: '#333',
+  },
+  tabTextActive: {
+    color: '#e79018',
+    fontWeight: 'bold',
+  },
+  tabLine: {
+    position: 'absolute',
+    bottom: 0,
+    left: '10%',
+    width: '80%',
+    height: 2,
+    backgroundColor: '#e79018',
+  },
+  loadingBox: {
+      flex: 1,
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  listContent: {
+      paddingBottom: 40,
+  },
+  item: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    paddingVertical: 14,
+    paddingHorizontal: 15,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    borderBottomColor: '#f5f5f5',
+  },
+  itemText: {
+    fontSize: 14,
+    color: '#333',
+  },
+  itemTextActive: {
+    color: '#e79018',
+  },
+  checkIcon: {
+      color: '#e79018',
+      fontSize: 16,
+  },
+});

+ 428 - 0
components/award-detail-yfs/BoxSelectionModal.tsx

@@ -0,0 +1,428 @@
+import { Images } from '@/constants/images';
+import { getBoxList } from '@/services/award';
+import { Image, ImageBackground } from 'expo-image';
+import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
+import {
+    ActivityIndicator,
+    Dimensions,
+    FlatList,
+    Modal,
+    ScrollView,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View
+} from 'react-native';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+
+interface BoxSelectionModalProps {
+  poolId: string;
+  onSelect: (box: any) => void;
+}
+
+export interface BoxSelectionModalRef {
+  show: () => void;
+  close: () => void;
+}
+
+const TABS = [
+  { title: '全部', value: '' },
+  { title: '超神款', value: 'A' },
+  { title: '欧皇款', value: 'B' },
+  { title: '隐藏款', value: 'C' },
+  { title: '普通款', value: 'D' },
+];
+
+export const BoxSelectionModal = forwardRef<BoxSelectionModalRef, BoxSelectionModalProps>(
+  ({ poolId, onSelect }, ref) => {
+    const [visible, setVisible] = useState(false);
+    const [activeTab, setActiveTab] = useState(TABS[0]);
+    const [list, setList] = useState<any[]>([]);
+    const [loading, setLoading] = useState(false);
+    const [refreshing, setRefreshing] = useState(false);
+    
+    // Bucket Logic
+    const [total, setTotal] = useState(0);
+    const [activeBucket, setActiveBucket] = useState(0);
+    const BUCKET_SIZE = 100;
+
+    const loadData = useCallback(async (bucketIndex = 0, tabValue = '', isRefresh = false) => {
+      if (isRefresh) setRefreshing(true);
+      setLoading(true);
+
+      try {
+        // Page is bucketIndex + 1, Size is 100
+        const res = await getBoxList(poolId, tabValue as any, bucketIndex + 1, BUCKET_SIZE);
+        const newData = res?.records || [];
+        
+        setList(newData);
+        setTotal(res?.total || 0); // Assuming API returns total
+        setActiveBucket(bucketIndex);
+        
+      } catch (error) {
+        console.error('Failed to load boxes:', error);
+      } finally {
+        setLoading(false);
+        setRefreshing(false);
+      }
+    }, [poolId]);
+
+    useImperativeHandle(ref, () => ({
+      show: () => {
+        setVisible(true);
+        setActiveTab(TABS[0]);
+        setActiveBucket(0);
+        setTotal(0);
+        loadData(0, TABS[0].value);
+      },
+      close: () => {
+        setVisible(false);
+      },
+    }));
+
+    const handleTabChange = (tab: typeof TABS[0]) => {
+      setActiveTab(tab);
+      // Reset to first bucket when changing level tab
+      loadData(0, tab.value);
+    };
+
+    const handleBucketChange = (index: number) => {
+        loadData(index, activeTab.value);
+    };
+
+    const handleRefresh = () => {
+        loadData(activeBucket, activeTab.value, true);
+    }
+
+    const renderItem = ({ item, index }: { item: any; index: number }) => {
+        if (item.leftQuantity <= 0) return null;
+
+        return (
+            <TouchableOpacity 
+                style={styles.item} 
+                onPress={() => {
+                    setVisible(false);
+                    onSelect(item);
+                }}
+            >
+                <View style={styles.itemIndex}>
+                    {/* Index should be absolute based on bucket */}
+                    <Text style={styles.indexText}>{(activeBucket * BUCKET_SIZE) + index + 1}</Text>
+                </View>
+                
+                <View style={styles.itemContent}>
+                    {/* Left Icon & Count */}
+                    <View style={styles.leftSection}>
+                        <Image source={{ uri: Images.box.detail.boxIcon }} style={styles.boxIcon} contentFit="contain"/>
+                        <Text style={styles.leftText}>剩{item.leftQuantity}发</Text>
+                    </View>
+                    
+                    <View style={styles.divider} />
+
+                    {/* Breakdown */}
+                    <View style={styles.breakdown}>
+                        <BreakdownItem label="A" current={item.leftQuantityA} total={item.quantityA} icon={Images.box.detail.levelTextA} />
+                        <BreakdownItem label="B" current={item.leftQuantityB} total={item.quantityB} icon={Images.box.detail.levelTextB} />
+                        <BreakdownItem label="C" current={item.leftQuantityC} total={item.quantityC} icon={Images.box.detail.levelTextC} />
+                        <BreakdownItem label="D" current={item.leftQuantityD} total={item.quantityD} icon={Images.box.detail.levelTextD} />
+                    </View>
+                </View>
+            </TouchableOpacity>
+        );
+    };
+
+    const buckets = Math.ceil(total / BUCKET_SIZE);
+
+    return (
+      <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
+        <View style={styles.overlay}>
+            <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={() => setVisible(false)} />
+            
+            <ImageBackground 
+                source={{ uri: Images.box.detail.recordBg }} 
+                style={styles.container}
+                resizeMode="stretch"
+            >
+                {/* Header */}
+                <View style={styles.header}>
+                    <Image source={{ uri: Images.box.detail.recordTitleLeft }} style={styles.titleDecor} contentFit="contain" />
+                    <Text style={styles.title}>换盒</Text>
+                    <Image source={{ uri: Images.box.detail.recordTitleRight }} style={styles.titleDecor} contentFit="contain" />
+                    
+                    <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
+                         <Text style={styles.closeText}>×</Text>
+                    </TouchableOpacity>
+                </View>
+
+                {/* Level Tabs */}
+                <View style={styles.tabs}>
+                    {TABS.map(tab => (
+                        <TouchableOpacity 
+                            key={tab.value} 
+                            style={[styles.tab, activeTab.value === tab.value && styles.activeTab]}
+                            onPress={() => handleTabChange(tab)}
+                        >
+                            <Text style={[styles.tabText, activeTab.value === tab.value && styles.activeTabText]}>
+                                {tab.title}
+                            </Text>
+                        </TouchableOpacity>
+                    ))}
+                </View>
+
+                {/* Bucket Selector (Range 1-100, etc.) */}
+                {buckets > 1 && (
+                    <View style={styles.bucketContainer}>
+                        <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.bucketScroll}>
+                            {Array.from({ length: buckets }).map((_, index) => {
+                                const start = index * BUCKET_SIZE + 1;
+                                const end = Math.min((index + 1) * BUCKET_SIZE, total);
+                                const isActive = activeBucket === index;
+                                return (
+                                    <TouchableOpacity 
+                                        key={index} 
+                                        style={[styles.bucketItem, isActive && styles.bucketItemActive]} 
+                                        onPress={() => handleBucketChange(index)}
+                                    >
+                                        <Text style={[styles.bucketText, isActive && styles.bucketTextActive]}>
+                                            {start}~{end}
+                                        </Text>
+                                    </TouchableOpacity>
+                                );
+                            })}
+                        </ScrollView>
+                    </View>
+                )}
+
+                {/* List */}
+                <View style={styles.listContainer}>
+                     <FlatList
+                        data={list}
+                        renderItem={renderItem}
+                        keyExtractor={(item, index) => item.id || String(index)}
+                        contentContainerStyle={styles.listContent}
+                        refreshing={refreshing}
+                        onRefresh={handleRefresh}
+                        ListEmptyComponent={
+                            !loading ? <Text style={styles.emptyText}>暂无数据</Text> : null
+                        }
+                        ListFooterComponent={loading && !refreshing ? <ActivityIndicator style={{ marginTop: 20 }} /> : null}
+                    />
+                </View>
+
+            </ImageBackground>
+        </View>
+      </Modal>
+    );
+  }
+);
+
+const BreakdownItem = ({ label, current, total, icon }: { label: string, current: number, total: number, icon: string }) => (
+    <View style={styles.breakdownItem}>
+        <Image source={{ uri: icon }} style={styles.levelIcon} contentFit="contain" />
+        <Text style={styles.breakdownText}>
+            <Text style={styles.currentNum}>{current}</Text>/{total}
+        </Text>
+    </View>
+);
+
+const styles = StyleSheet.create({
+  overlay: {
+    flex: 1,
+    backgroundColor: 'rgba(0,0,0,0.5)',
+    justifyContent: 'flex-end',
+  },
+  mask: { flex: 1 },
+  container: {
+    height: 600, // wrapperWidth rpx in Vue
+    paddingTop: 15,
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    overflow: 'hidden',
+    backgroundColor: '#fff', // Fallback
+  },
+  header: {
+      flexDirection: 'row',
+      alignItems: 'center',
+      justifyContent: 'center',
+      marginBottom: 15,
+      position: 'relative',
+      height: 44,
+  },
+  title: {
+      fontSize: 18,
+      fontWeight: 'bold',
+      color: '#fff',
+      marginHorizontal: 10,
+      textShadowColor: '#000',
+      textShadowOffset: { width: 1, height: 1 },
+      textShadowRadius: 1,
+  },
+  titleDecor: {
+      width: 17,
+      height: 19,
+  },
+  closeBtn: {
+      position: 'absolute',
+      right: 15,
+      top: 0,
+      width: 24,
+      height: 24,
+      backgroundColor: '#ebebeb',
+      borderRadius: 12,
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  closeText: {
+      fontSize: 18,
+      color: '#a2a2a2',
+      marginTop: -2,
+  },
+  tabs: {
+      flexDirection: 'row',
+      justifyContent: 'space-around',
+      paddingHorizontal: 15,
+      marginBottom: 10,
+  },
+  tab: {
+      paddingVertical: 6,
+      paddingHorizontal: 12,
+      borderRadius: 15,
+      backgroundColor: '#f5f5f5',
+  },
+  activeTab: {
+      backgroundColor: '#ffdb4d',
+  },
+  tabText: {
+      fontSize: 12,
+      color: '#666',
+  },
+  activeTabText: {
+      color: '#333',
+      fontWeight: 'bold',
+  },
+  listContainer: {
+      flex: 1,
+      backgroundColor: '#f3f3f3',
+      marginHorizontal: 15,
+      marginBottom: 20,
+      borderWidth: 2,
+      borderColor: '#000',
+      padding: 10,
+  },
+  listContent: {
+      paddingBottom: 20,
+  },
+  item: {
+      backgroundColor: '#fff',
+      borderWidth: 3,
+      borderColor: '#000',
+      marginBottom: 10,
+      flexDirection: 'row',
+      height: 80,
+      position: 'relative',
+      overflow: 'hidden',
+  },
+  itemIndex: {
+      position: 'absolute',
+      left: 0,
+      top: 0,
+      width: 22,
+      height: 22,
+      backgroundColor: '#fff',
+      borderRightWidth: 2,
+      borderBottomWidth: 2,
+      borderColor: '#000',
+      justifyContent: 'center',
+      alignItems: 'center',
+      zIndex: 1,
+  },
+  indexText: {
+      fontSize: 12,
+      fontWeight: 'bold',
+  },
+  itemContent: {
+      flex: 1,
+      flexDirection: 'row',
+      alignItems: 'center',
+      paddingHorizontal: 10,
+      paddingLeft: 22, // Space for index
+  },
+  leftSection: {
+      width: 60,
+      alignItems: 'center',
+      justifyContent: 'center',
+  },
+  boxIcon: {
+      width: 24,
+      height: 24,
+      marginBottom: 2,
+  },
+  leftText: {
+      fontSize: 10,
+      color: '#333',
+  },
+  divider: {
+      width: 1,
+      height: 40,
+      backgroundColor: '#dcdad3',
+      marginHorizontal: 10,
+      opacity: 0.5,
+  },
+  breakdown: {
+      flex: 1,
+      flexDirection: 'row',
+      flexWrap: 'wrap',
+  },
+  breakdownItem: {
+      width: '50%',
+      flexDirection: 'row',
+      alignItems: 'center',
+      marginBottom: 4,
+  },
+  levelIcon: {
+      width: 45,
+      height: 16,
+      marginRight: 4,
+  },
+  breakdownText: {
+      fontSize: 12,
+      color: '#666',
+  },
+  currentNum: {
+      color: '#000',
+      fontWeight: 'bold',
+  },
+  emptyText: {
+      textAlign: 'center',
+      marginTop: 20,
+      color: '#999',
+  },
+  // Bucket Styles
+  bucketContainer: {
+    height: 44,
+    marginBottom: 10,
+  },
+  bucketScroll: {
+    paddingHorizontal: 15,
+  },
+  bucketItem: {
+    paddingHorizontal: 15,
+    paddingVertical: 8,
+    borderRadius: 20,
+    backgroundColor: '#f5f5f5',
+    marginRight: 10,
+    justifyContent: 'center',
+  },
+  bucketItemActive: {
+    backgroundColor: '#ffdb4d',
+  },
+  bucketText: {
+    fontSize: 12,
+    color: '#666',
+  },
+  bucketTextActive: {
+      color: '#333',
+      fontWeight: 'bold',
+  },
+});

+ 213 - 0
components/award-detail-yfs/FirstLast.tsx

@@ -0,0 +1,213 @@
+import { Images } from '@/constants/images';
+import { Image, ImageBackground } from 'expo-image';
+import React from 'react';
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+interface FirstLastProps {
+  data: any;
+  box: any;
+  onPrevBox?: () => void;
+  onNextBox?: () => void;
+  onChangeBox?: () => void;
+  onProductClick?: (product: any) => void;
+}
+
+export default function FirstLast({ data, box, onPrevBox, onNextBox, onChangeBox, onProductClick }: FirstLastProps) {
+  // Filter for special prizes: FIRST, LAST, LASTALL
+  const prizeList = React.useMemo(() => {
+    if (!box || !box.prizeList) return [];
+    return box.prizeList.filter((item: any) => 
+        ['FIRST', 'LAST', 'LASTALL'].includes(item.level)
+    );
+  }, [box]);
+
+  if (!box) return null;
+
+  return (
+    <View style={styles.container}>
+      <ImageBackground 
+        source={{ uri: Images.box.detail.firstBoxBg }} 
+        style={styles.bg}
+        resizeMode="stretch"
+      >
+        {/* Title / Remain Info */}
+        <View style={styles.header}>
+            <View style={styles.remainInfo}>
+                <Text style={styles.remainLabel}>当前盒子剩余:</Text>
+                <Text style={styles.remainValue}>
+                     <Text style={styles.remainNum}>{box.leftQuantity}</Text>
+                     /{box.quantity}发
+                </Text>
+            </View>
+            <Image 
+                source={{ uri: Images.box.detail.playMethod }} 
+                style={styles.ruleBtn} 
+                contentFit="contain"
+            />
+        </View>
+
+        {/* Prize List (First/Last) */}
+        <View style={styles.prizeContainer}>
+            {prizeList.map((item: any, index: number) => (
+                <TouchableOpacity 
+                    key={index} 
+                    style={styles.prizeItem} 
+                    onPress={() => onProductClick && onProductClick(item.spu)}
+                >
+                    <ImageBackground 
+                        source={{ uri: Images.box.detail.firstItemBg }} 
+                        style={styles.prizeItemBg}
+                        resizeMode="contain"
+                    >
+                        <Image source={{ uri: item.cover }} style={styles.prizeImg} contentFit="contain" />
+                        
+                        <View style={styles.prizeLabelContainer}>
+                             {item.level === 'FIRST' && (
+                                 <Text style={[styles.prizeLabel, { backgroundColor: 'rgba(91, 189, 208, .8)' }]}>抢先赏</Text>
+                             )}
+                             {item.level === 'LAST' && (
+                                 <Text style={[styles.prizeLabel, { backgroundColor: 'rgba(246, 44, 113, .8)' }]}>最终赏</Text>
+                             )}
+                             {item.level === 'LASTALL' && (
+                                 <Text style={[styles.prizeLabel, { backgroundColor: 'rgba(44, 246, 74, .8)' }]}>全局赏</Text>
+                             )}
+                        </View>
+                        
+                        {/* User Avatar if someone won it? Legacy logic: v-if="item.nickname" */}
+                        {item.nickname && (
+                            <View style={styles.winnerBox}>
+                                <Image source={{ uri: item.avatar }} style={styles.winnerAvatar} />
+                                <Text style={styles.winnerName} numberOfLines={1}>{item.nickname}</Text>
+                            </View>
+                        )}
+                    </ImageBackground>
+                </TouchableOpacity>
+            ))}
+        </View>
+
+        {/* Controls */}
+        <ImageBackground 
+             source={{ uri: Images.box.detail.funBoxBg }} 
+             style={styles.controlsBg}
+             resizeMode="stretch"
+        >
+             <TouchableOpacity style={styles.controlBtn} onPress={onPrevBox}>
+                 <Text style={styles.controlText}>上一盒</Text>
+             </TouchableOpacity>
+
+             <TouchableOpacity style={styles.changeBtn} onPress={onChangeBox}>
+                 <Text style={[styles.controlText, { color: '#000' }]}>
+                     换盒(<Text style={{ fontWeight: 'bold' }}>{box.number}</Text>/{box.lastNumber})
+                 </Text>
+             </TouchableOpacity>
+
+             <TouchableOpacity style={styles.controlBtn} onPress={onNextBox}>
+                 <Text style={styles.controlText}>下一盒</Text>
+             </TouchableOpacity>
+        </ImageBackground>
+
+      </ImageBackground>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    paddingHorizontal: 10,
+    marginBottom: 10,
+  },
+  bg: {
+    width: '100%',
+    height: 193, // 386rpx / 2
+    alignItems: 'center',
+  },
+  header: {
+    width: '100%',
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    paddingHorizontal: 20,
+    paddingTop: 20,
+  },
+  remainInfo: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  remainLabel: { color: '#fff', fontSize: 12 },
+  remainValue: { color: '#fff', fontSize: 12 },
+  remainNum: { fontSize: 16, fontWeight: 'bold' },
+  ruleBtn: { width: 41, height: 17 },
+  
+  prizeContainer: {
+      flexDirection: 'row',
+      justifyContent: 'space-around',
+      width: '80%',
+      marginTop: 10,
+      height: 70, // Adjust based on item size
+  },
+  prizeItem: {
+      width: 63, // 126rpx / 2
+      height: 65,
+  },
+  prizeItemBg: {
+      flex: 1,
+      alignItems: 'center',
+      justifyContent: 'center',
+  },
+  prizeImg: {
+      width: 60,
+      height: 60,
+  },
+  prizeLabelContainer: {
+      position: 'absolute',
+      bottom: -8,
+      width: '100%',
+      alignItems: 'center',
+  },
+  prizeLabel: {
+      fontSize: 10,
+      color: '#fff',
+      paddingHorizontal: 4,
+      paddingVertical: 1,
+      borderRadius: 4,
+      overflow: 'hidden',
+  },
+  winnerBox: {
+      position: 'absolute',
+      top: 0,
+      left: 0,
+      right: 0,
+      bottom: 0,
+      justifyContent: 'center',
+      alignItems: 'center',
+      backgroundColor: 'rgba(0,0,0,0.6)',
+      borderRadius: 30, // Circleish
+  },
+  winnerAvatar: { width: 24, height: 24, borderRadius: 12, borderWidth: 1, borderColor: '#FFE996' },
+  winnerName: { color: '#FEC433', fontSize: 8, marginTop: 2, maxWidth: 50 },
+
+  controlsBg: {
+      width: 270, // 540rpx / 2
+      height: 38, // 76rpx / 2
+      flexDirection: 'row',
+      alignItems: 'center',
+      justifyContent: 'center',
+      marginTop: 25,
+  },
+  controlBtn: {
+      width: 90,
+      height: '100%',
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  changeBtn: {
+      width: 110,
+      height: '100%',
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  controlText: {
+      color: '#fff',
+      fontSize: 14,
+      fontWeight: '500',
+  }
+});

+ 472 - 0
components/award-detail-yfs/NumSelectionModal.tsx

@@ -0,0 +1,472 @@
+import { LEVEL_MAP } from '@/constants/config';
+import { Images } from '@/constants/images';
+import { getUnavailableSeatNumbers, previewOrder } from '@/services/award';
+import { Image, ImageBackground } from 'expo-image';
+import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
+import {
+    ActivityIndicator,
+    Alert,
+    Dimensions,
+    Modal,
+    ScrollView,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View
+} from 'react-native';
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window');
+
+interface NumSelectionModalProps {
+  poolId: string;
+  onPay: (params: { previewRes: any, chooseNum: number[], boxNum: string }) => void;
+}
+
+export interface NumSelectionModalRef {
+  show: (box: any) => void;
+  close: () => void;
+}
+
+
+
+export const NumSelectionModal = forwardRef<NumSelectionModalRef, NumSelectionModalProps>(
+  ({ poolId, onPay }, ref) => {
+    const [visible, setVisible] = useState(false);
+    const [box, setBox] = useState<any>(null);
+    const [tabs, setTabs] = useState<any[]>([]);
+    const [activeTab, setActiveTab] = useState<any>(null);
+    
+    // Status Maps
+    const [useMap, setUseMap] = useState<Record<number, string>>({}); // Sold/Opened (seatNum -> level)
+    const [lockMap, setLockMap] = useState<Record<number, number>>({}); // Locked/Pending (seatNum -> seatNum)
+    const [checkMap, setCheckMap] = useState<Record<number, number>>({}); // Selected by user (seatNum -> seatNum)
+
+    const [loading, setLoading] = useState(false);
+
+    useImperativeHandle(ref, () => ({
+      show: (boxItem: any) => {
+        setBox(boxItem);
+        setVisible(true);
+        setCheckMap({});
+        // Initialize Tabs based on box quantity
+        // Assuming max 100 per tab like legacy
+        const total = boxItem.quantity || 100; // Default or from box
+        const newTabs = [];
+        const itemsPerTab = 100;
+        const count = Math.ceil(total / itemsPerTab);
+        
+        for (let i = 0; i < count; i++) {
+            const start = i * itemsPerTab + 1;
+            const end = Math.min((i + 1) * itemsPerTab, total);
+            const data = Array.from({ length: end - start + 1 }, (_, k) => k + start);
+            newTabs.push({
+                title: `${start}~${end}`,
+                value: i,
+                data
+            });
+        }
+        setTabs(newTabs);
+        if (newTabs.length > 0) {
+            setActiveTab(newTabs[0]);
+            loadData(newTabs[0], boxItem.number);
+        }
+      },
+      close: () => {
+        setVisible(false);
+      },
+    }));
+
+    const loadData = async (tab: any, boxNum: string) => {
+        if (!tab || !boxNum) return;
+        
+        try {
+            const start = tab.data[0];
+            const end = tab.data[tab.data.length - 1];
+            const res = await getUnavailableSeatNumbers(poolId, boxNum, start, end);
+            
+            if (res) {
+                // Determine used (sold) and locked seats
+                const newUseMap: Record<number, string> = { ...useMap }; // Merge? Or reset per tab? Legacy merges logic via object assignment but here standard React state update
+                // Legacy clears map if not careful? No, it sets properties.
+                // Let's reset for the current range to avoid stale data visually if we revisit, but merging is safer for 'global' knowledge. 
+                // Efficiency-wise, let's keep it simple.
+                
+                if (res.usedSeatNumbers) {
+                    res.usedSeatNumbers.forEach((item: any) => {
+                        newUseMap[item.seatNumber] = item.level;
+                    });
+                }
+                 
+                const newLockMap: Record<number, number> = { ...lockMap };
+                if (res.applyedSeatNumbers) {
+                    res.applyedSeatNumbers.forEach((num: number) => {
+                        newLockMap[num] = num;
+                    });
+                }
+                
+                setUseMap(newUseMap);
+                setLockMap(newLockMap);
+
+                // Remove selected if they became unavailable
+                const newCheckMap = { ...checkMap };
+                let changed = false;
+                Object.keys(newCheckMap).forEach(key => {
+                    const num = Number(key);
+                    if (newUseMap[num] || newLockMap[num]) {
+                        delete newCheckMap[num];
+                        changed = true;
+                    }
+                });
+                if (changed) setCheckMap(newCheckMap);
+            }
+        } catch (error) {
+            console.error('Failed to load seat info', error);
+        }
+    };
+
+    useEffect(() => {
+        if (activeTab && box) {
+            loadData(activeTab, box.number);
+        }
+    }, [activeTab]);
+
+    const handleTabChange = (tab: any) => {
+        setActiveTab(tab);
+    };
+
+    const toggleSelect = (num: number) => {
+        if (useMap[num] || lockMap[num]) return;
+
+        setCheckMap(prev => {
+            const next = { ...prev };
+            if (next[num]) {
+                delete next[num];
+            } else {
+                if (Object.keys(next).length >= 50) {
+                    Alert.alert('提示', '最多不超过50发');
+                    return prev;
+                }
+                next[num] = num;
+            }
+            return next;
+        });
+    };
+
+    const handleConfirm = async () => {
+        const selectedNums = Object.values(checkMap).map(Number);
+        if (selectedNums.length === 0) {
+            Alert.alert('提示', '请选择号码');
+            return;
+        }
+
+        setLoading(true);
+        try {
+            const res = await previewOrder(poolId, selectedNums.length, box.number, selectedNums);
+            if (res) {
+                 if (res.duplicateSeatNumbers && res.duplicateSeatNumbers.length > 0) {
+                      Alert.alert('提示', `${res.duplicateSeatNumbers.join(',')}号被占用`);
+                      // Remove duplicates
+                      const newCheckMap = { ...checkMap };
+                      res.duplicateSeatNumbers.forEach((n: number) => delete newCheckMap[n]);
+                      setCheckMap(newCheckMap);
+                      // Refresh data
+                      loadData(activeTab, box.number);
+                      return;
+                 }
+                 
+                 // Proceed to pay callback
+                 onPay({ previewRes: res, chooseNum: selectedNums, boxNum: box.number });
+                 setVisible(false);
+            }
+        } catch (error: any) {
+            Alert.alert('错误', error?.message || '请求失败');
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const renderItem = (num: number) => {
+        const isUsed = !!useMap[num];
+        const isLocked = !!lockMap[num];
+        const isSelected = !!checkMap[num];
+        
+        let content = <Text style={[styles.itemText, (isUsed || isLocked) && styles.disabledText]}>{num}号</Text>;
+        let style = [styles.item];
+
+        if (isUsed) {
+            style.push(styles.levelItem as any);
+            const level = useMap[num];
+            content = (
+                <View style={styles.usedContent}>
+                    <Text style={styles.usedNum}>{num}号</Text>
+                    <Text style={[styles.levelText, { color: LEVEL_MAP[level]?.color }]}>
+                        {LEVEL_MAP[level]?.title || level}
+                    </Text>
+                </View>
+            );
+        } else if (isLocked) {
+             style.push(styles.lockedItem as any);
+             content = (
+                <>
+                   <Text style={styles.disabledText}>{num}号</Text>
+                   <View style={styles.lockIcon}>
+                       {/* Icon placeholder or text */}
+                       <Text style={{fontSize: 10, color: '#fff'}}>🔒</Text>
+                   </View>
+                </>
+             );
+        } else if (isSelected) {
+            style.push(styles.selectedItem as any);
+            content = (
+                <>
+                    <Text style={styles.itemText}>{num}号</Text>
+                    <View style={styles.checkIcon}>
+                         <Text style={{fontSize: 8, color: '#F1423D'}}>✓</Text>
+                    </View>
+                </>
+            );
+        }
+
+        return (
+            <TouchableOpacity 
+                key={num} 
+                style={style} 
+                onPress={() => toggleSelect(num)}
+                disabled={isUsed || isLocked}
+            >
+                {content}
+            </TouchableOpacity>
+        );
+    };
+
+    const selectedList = Object.values(checkMap);
+
+    return (
+      <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
+         <View style={styles.overlay}>
+             <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={() => setVisible(false)} />
+             
+             <ImageBackground 
+                source={{ uri: Images.box.detail.recordBg }} 
+                style={styles.container}
+                resizeMode="stretch"
+            >
+                {/* Header */}
+                <View style={styles.header}>
+                    <Image source={{ uri: Images.box.detail.recordTitleLeft }} style={styles.titleDecor} contentFit="contain" />
+                    <Text style={styles.title}>换盒</Text>
+                    <Image source={{ uri: Images.box.detail.recordTitleRight }} style={styles.titleDecor} contentFit="contain" />
+                    
+                    <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
+                         <Text style={styles.closeText}>×</Text>
+                    </TouchableOpacity>
+                </View>
+
+                {/* Tabs for Ranges */}
+                {tabs.length > 1 && (
+                     <View style={styles.tabsContainer}>
+                        <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.tabsScroll}>
+                            {tabs.map(tab => (
+                                <TouchableOpacity 
+                                    key={tab.title} 
+                                    style={[styles.tab, activeTab?.value === tab.value && styles.activeTab]}
+                                    onPress={() => handleTabChange(tab)}
+                                >
+                                    <Text style={[styles.tabText, activeTab?.value === tab.value && styles.activeTabText]}>
+                                        {tab.title}
+                                    </Text>
+                                </TouchableOpacity>
+                            ))}
+                        </ScrollView>
+                    </View>
+                )}
+
+                {/* Grid */}
+                <ScrollView contentContainerStyle={styles.grid}>
+                    {activeTab?.data.map((num: number) => renderItem(num))}
+                </ScrollView>
+
+                {/* Bottom Selection Bar */}
+                <View style={styles.bottomSection}>
+                    <ScrollView horizontal style={styles.selectedScroll} contentContainerStyle={styles.selectedContent}>
+                        {selectedList.map((num) => (
+                            <View key={num} style={styles.selectedChip}>
+                                <Text style={styles.selectedChipText}>{num}号</Text>
+                                <TouchableOpacity onPress={() => toggleSelect(Number(num))} style={styles.chipClose}>
+                                    <Text style={styles.chipCloseText}>×</Text>
+                                </TouchableOpacity>
+                            </View>
+                        ))}
+                    </ScrollView>
+
+                    <TouchableOpacity 
+                        style={styles.confirmBtn} 
+                        onPress={handleConfirm}
+                        disabled={loading}
+                    >
+                         <ImageBackground source={{ uri: Images.common.loginBg }} style={styles.confirmBtnBg} resizeMode="stretch">
+                             {loading ? <ActivityIndicator color="#fff" /> : (
+                                 <View style={{ alignItems: 'center' }}>
+                                     <Text style={styles.confirmText}>确定选择</Text>
+                                     <Text style={styles.subConfirmText}>已选择({selectedList.length})发</Text>
+                                 </View>
+                             )}
+                         </ImageBackground>
+                    </TouchableOpacity>
+                </View>
+
+            </ImageBackground>
+         </View>
+      </Modal>
+    );
+  }
+);
+
+const styles = StyleSheet.create({
+  overlay: {
+    flex: 1,
+    backgroundColor: 'rgba(0,0,0,0.5)',
+    justifyContent: 'flex-end',
+  },
+  mask: { flex: 1 },
+  container: {
+    height: 600,
+    paddingTop: 15,
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    overflow: 'hidden',
+    backgroundColor: '#fff',
+  },
+  header: {
+      flexDirection: 'row',
+      alignItems: 'center',
+      justifyContent: 'center',
+      marginBottom: 15,
+      position: 'relative',
+      height: 44,
+  },
+  title: {
+      fontSize: 18,
+      fontWeight: 'bold',
+      color: '#fff',
+      marginHorizontal: 10,
+      textShadowColor: '#000',
+      textShadowOffset: { width: 1, height: 1 },
+      textShadowRadius: 1,
+  },
+  titleDecor: {
+      width: 17,
+      height: 19,
+  },
+  closeBtn: {
+      position: 'absolute',
+      right: 15,
+      top: 0,
+      width: 24,
+      height: 24,
+      backgroundColor: '#ebebeb',
+      borderRadius: 12,
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  closeText: { fontSize: 18, color: '#a2a2a2', marginTop: -2 },
+  tabsContainer: {
+    height: 40,
+    marginBottom: 10,
+  },
+  tabsScroll: {
+    paddingHorizontal: 10,
+  },
+  tab: {
+      paddingVertical: 6,
+      paddingHorizontal: 12,
+      backgroundColor: '#f5f5f5',
+      borderRadius: 20,
+      marginRight: 10,
+      justifyContent: 'center',
+  },
+  activeTab: { backgroundColor: '#FFC900' },
+  tabText: { fontSize: 12, color: '#666' },
+  activeTabText: { color: '#000', fontWeight: 'bold' },
+  grid: {
+      flexDirection: 'row',
+      flexWrap: 'wrap',
+      paddingHorizontal: 10,
+      paddingBottom: 20,
+  },
+  item: {
+      width: (SCREEN_WIDTH - 20 - 45) / 5, // Approx 5 items per row
+      height: ((SCREEN_WIDTH - 20 - 45) / 5) * 0.5,
+      margin: 4,
+      backgroundColor: '#FFC900',
+      borderWidth: 3,
+      borderColor: '#000',
+      justifyContent: 'center',
+      alignItems: 'center',
+      borderRadius: 0,
+  },
+  itemText: { fontSize: 12, fontWeight: 'bold', color: '#000' },
+  disabledText: { color: 'rgba(255,255,255,0.3)' },
+  levelItem: { backgroundColor: '#e8e8e8', borderColor: '#e8e8e8' },
+  lockedItem: { backgroundColor: 'rgba(98, 99, 115, 0.3)', borderColor: 'transparent', borderWidth: 0 },
+  selectedItem: { backgroundColor: '#FFC900', borderColor: '#000' }, // Same as default but with icon
+  usedContent: { alignItems: 'center' },
+  usedNum: { fontSize: 10, opacity: 0.5 },
+  levelText: { fontSize: 10, fontWeight: 'bold' },
+  lockIcon: { position: 'absolute', bottom: 0, right: 0, backgroundColor: '#000', padding: 2, borderTopLeftRadius: 5 },
+  checkIcon: {
+      position: 'absolute',
+      bottom: 0,
+      right: 0,
+      backgroundColor: '#fff',
+      paddingHorizontal: 4,
+      borderTopLeftRadius: 5,
+  },
+  bottomSection: {
+      paddingBottom: 30,
+      paddingTop: 10,
+      borderTopWidth: 1,
+      borderColor: '#eee',
+      backgroundColor: '#fff',
+  },
+  selectedScroll: { maxHeight: 50, marginBottom: 10 },
+  selectedContent: { paddingHorizontal: 10 },
+  selectedChip: {
+      flexDirection: 'row',
+      alignItems: 'center',
+      backgroundColor: '#FFC900',
+      borderWidth: 2,
+      borderColor: '#000',
+      paddingHorizontal: 8,
+      paddingVertical: 4,
+      borderRadius: 4,
+      marginRight: 8,
+      height: 30,
+  },
+  selectedChipText: { fontSize: 12, fontWeight: 'bold', marginRight: 5 },
+  chipClose: { backgroundColor: 'rgba(255,255,255,0.5)', borderRadius: 10, width: 16, height: 16, alignItems: 'center', justifyContent: 'center' },
+  chipCloseText: { fontSize: 12, lineHeight: 14 },
+  confirmBtn: {
+      alignSelf: 'center',
+      width: 200,
+      height: 50,
+  },
+  confirmBtnBg: {
+      flex: 1,
+      justifyContent: 'center',
+      alignItems: 'center',
+  },
+  confirmText: {
+      color: '#fff',
+      fontSize: 16,
+      fontWeight: 'bold',
+      textShadowColor: '#000',
+      textShadowOffset: { width: 1, height: 1 },
+      textShadowRadius: 1,
+  },
+  subConfirmText: {
+      color: '#fff',
+      fontSize: 10,
+      marginTop: -2,
+  },
+});

+ 260 - 0
components/award-detail-yfs/ProductListYfs.tsx

@@ -0,0 +1,260 @@
+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',
+  }
+});

+ 222 - 0
components/award-detail-yfs/ProductSwiper.tsx

@@ -0,0 +1,222 @@
+import { Image } from 'expo-image';
+import React, { useState } from 'react';
+import {
+    Dimensions,
+    FlatList,
+    ImageBackground,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+
+import { Images } from '@/constants/images';
+
+const { width } = Dimensions.get('window');
+
+interface Product {
+  cover: string;
+  name: string;
+  level: string;
+  quantity?: number;
+  probability?: string;
+}
+
+interface ProductSwiperProps {
+  products: Product[];
+  box?: any; // Add box prop
+  onShowSwipe?: (index: number) => void;
+}
+
+export default function ProductSwiper({ products, box, onShowSwipe }: ProductSwiperProps) {
+    const [current, setCurrent] = useState(0);
+
+    const getLeftNum = (item: any) => {
+        const usedStat = box?.usedStat || box?.used_stat;
+        if (!box || !usedStat) return item.quantity;
+        
+        const spuId = String(item.spu?.id || item.spu_id || item.id); // Try to match ID
+        const itemId = String(item.id);
+
+        let used: any = null;
+        if (Array.isArray(usedStat)) {
+             used = usedStat.find((u: any) => {
+                 const uId = String(u.spuId || u.spu_id || u.id);
+                 return uId === spuId || uId === itemId;
+             });
+        } else {
+            used = usedStat[spuId] || usedStat[itemId];
+        }
+
+        if (used) {
+            return item.quantity - (used.quantity || 0);
+        }
+        return item.quantity;
+    };
+
+    const getProbability = (item: Product) => {
+        if (!box || !box.leftQuantity) return item.probability || '0%';
+        const left = getLeftNum(item);
+        const prob = (left / box.leftQuantity * 100).toFixed(4);
+        return parseFloat(prob) === 0 ? '0%' : `${prob}%`;
+    };
+
+    if (!products || products.length === 0) return null;
+
+    const renderItem = ({ item, index }: { item: Product; index: number }) => (
+        <TouchableOpacity 
+            activeOpacity={0.9} 
+            onPress={() => onShowSwipe?.(index)}
+            style={styles.itemContainer}
+        >
+                <View style={styles.imageBox}>
+                <Image source={{ uri: item.cover }} style={styles.image} contentFit="contain" />
+            </View>
+            
+            {/* Details Button/Tag */}
+            <ImageBackground 
+                source={{ uri: Images.box.detail.detailsBut }} 
+                style={styles.detailsTag}
+                resizeMode="contain"
+            >
+                <View style={styles.detailsContent}>
+                    <Text style={styles.levelText}>
+                        {item.level === 'A' ? '超神款' : 
+                            item.level === 'B' ? '欧皇款' : 
+                            item.level === 'C' ? '隐藏款' : '普通款'}
+                    </Text>
+                    <Text style={styles.probText}>{getProbability(item)}</Text>
+                </View>
+            </ImageBackground>
+
+            {/* Name Tag */}
+            <ImageBackground 
+                source={{ uri: Images.box.detail.nameBg }} 
+                style={styles.nameTag}
+                resizeMode="stretch"
+            >
+                <Text style={styles.nameText} numberOfLines={1}>{item.name}</Text>
+            </ImageBackground>
+        </TouchableOpacity>
+    );
+
+    return (
+        <View style={styles.container}>
+            <FlatList
+                data={products}
+                renderItem={renderItem}
+                horizontal
+                pagingEnabled
+                showsHorizontalScrollIndicator={false}
+                keyExtractor={(_, index) => index.toString()}
+                onMomentumScrollEnd={(e) => {
+                    const contentOffset = e.nativeEvent.contentOffset;
+                    const viewSize = e.nativeEvent.layoutMeasurement;
+                    const pageNum = Math.floor(contentOffset.x / viewSize.width);
+                    setCurrent(pageNum);
+                }}
+            />
+             {/* Left/Right Arrows if needed */}
+             {current > 0 && (
+                 <View style={[styles.arrow, styles.leftArrow]}>
+                     <Image source={{ uri: Images.box.detail.left }} style={styles.arrowImg} contentFit="contain" />
+                 </View>
+             )}
+             {current < products.length - 1 && (
+                 <View style={[styles.arrow, styles.rightArrow]}>
+                      <Image source={{ uri: Images.box.detail.right }} style={styles.arrowImg} contentFit="contain" />
+                 </View>
+             )}
+        </View>
+    );
+}
+
+const styles = StyleSheet.create({
+    container: {
+        width: '100%',
+        height: 380, // Adjusted height
+        alignItems: 'center',
+        marginTop: 20,
+    },
+    itemContainer: {
+        width: width, // Full width for paging
+        height: '100%',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    imageBox: {
+        width: '60%',
+        height: '60%',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    image: {
+        width: '100%',
+        height: '100%',
+    },
+    detailsTag: {
+        position: 'absolute',
+        right: 40,
+        bottom: 80,
+        width: 100, 
+        height: 60,
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    detailsContent: {
+        alignItems: 'center',
+        justifyContent: 'center',
+        marginTop: 5,
+    },
+    levelText: {
+        color: '#fff',
+        fontSize: 12,
+        fontWeight: 'bold',
+        textShadowColor: '#000',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    },
+    probText: {
+        color: '#fff',
+        fontSize: 10,
+        fontWeight: 'bold',
+        textShadowColor: '#000',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    },
+    nameTag: {
+        position: 'absolute',
+        left: 40,
+        top: 40,
+        width: 40,
+        height: 120,
+        justifyContent: 'center',
+        alignItems: 'center',
+        paddingVertical: 10,
+    },
+    nameText: {
+        color: '#000',
+        fontSize: 12,
+        fontWeight: 'bold',
+        width: 12,
+        textAlign: 'center',
+    },
+    arrow: {
+        position: 'absolute',
+        top: '50%',
+        marginTop: -20,
+        width: 40,
+        height: 40,
+        zIndex: 10,
+    },
+    leftArrow: {
+        left: 10,
+    },
+    rightArrow: {
+        right: 10,
+    },
+    arrowImg: {
+        width: '100%',
+        height: '100%',
+    }
+});

+ 109 - 0
components/award-detail-yfs/PurchaseBar.tsx

@@ -0,0 +1,109 @@
+import React from 'react';
+import {
+    ImageBackground,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+
+import { Images } from '@/constants/images';
+
+// Helper to determine background image based on type
+const getBtnBg = (type: 'one' | 'five' | 'many') => {
+    switch (type) {
+        case 'one': return Images.common.butBgV;
+        case 'five': return Images.common.butBgL;
+        case 'many': return Images.common.butBgH;
+        default: return Images.common.butBgV;
+    }
+};
+
+interface PurchaseBarProps {
+    price: number;
+    specialPrice?: number;
+    specialPriceFive?: number;
+    onBuy: (count: number) => void;
+    onBuyMany: () => void;
+}
+
+export default function PurchaseBar({ price, specialPrice, specialPriceFive, onBuy, onBuyMany }: PurchaseBarProps) {
+    return (
+        <ImageBackground 
+            source={{ uri: Images.box.detail.boxDetailBott }} 
+            style={styles.container}
+            resizeMode="stretch"
+        >
+            <View style={styles.btnRow}>
+                {/* Buy One */}
+                <TouchableOpacity onPress={() => onBuy(1)} style={styles.btnWrapper}>
+                    <ImageBackground source={{ uri: getBtnBg('one') }} style={styles.btnBg} resizeMode="stretch">
+                        <Text style={styles.btnTitle}>购买一盒</Text>
+                        <Text style={styles.btnPrice}>
+                            (¥{specialPrice ?? price})
+                        </Text>
+                    </ImageBackground>
+                </TouchableOpacity>
+
+                {/* Buy Five */}
+                <TouchableOpacity onPress={() => onBuy(5)} style={styles.btnWrapper}>
+                    <ImageBackground source={{ uri: getBtnBg('five') }} style={styles.btnBg} resizeMode="stretch">
+                        <Text style={styles.btnTitle}>购买五盒</Text>
+                        <Text style={styles.btnPrice}>
+                             (¥{specialPriceFive ?? price * 5})
+                        </Text>
+                    </ImageBackground>
+                </TouchableOpacity>
+
+                {/* Buy Many */}
+                <TouchableOpacity onPress={onBuyMany} style={styles.btnWrapper}>
+                    <ImageBackground source={{ uri: getBtnBg('many') }} style={styles.btnBg} resizeMode="stretch">
+                        <Text style={styles.btnTitle}>购买多盒</Text>
+                    </ImageBackground>
+                </TouchableOpacity>
+            </View>
+        </ImageBackground>
+    );
+}
+
+const styles = StyleSheet.create({
+    container: {
+        width: '100%',
+        height: 100, // Adjust based on background aspect ratio
+        justifyContent: 'center',
+        paddingBottom: 20, // Safe area padding simulation
+    },
+    btnRow: {
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+        alignItems: 'center',
+        paddingHorizontal: 10,
+    },
+    btnWrapper: {
+        flex: 1,
+        height: 50,
+        marginHorizontal: 5,
+    },
+    btnBg: {
+        width: '100%',
+        height: '100%',
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    btnTitle: {
+        color: '#fff',
+        fontSize: 14,
+        fontWeight: 'bold',
+        textShadowColor: '#000',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    },
+    btnPrice: {
+        color: '#fff',
+        fontSize: 10,
+        fontWeight: 'bold',
+        textShadowColor: '#000',
+        textShadowOffset: { width: 1, height: 1 },
+        textShadowRadius: 1,
+    }
+});

+ 296 - 0
components/award-detail-yfs/RecordModal.tsx

@@ -0,0 +1,296 @@
+import { LEVEL_MAP } from '@/constants/config';
+import { Images } from '@/constants/images';
+import { countRecordsAfterLastLevel, getBuyRecord } from '@/services/award';
+import { Image, ImageBackground } from 'expo-image';
+import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
+import { ActivityIndicator, Dimensions, FlatList, Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+const { width, height } = Dimensions.get('window');
+
+const TABS = [
+    { title: '全部', value: '' },
+    { title: '超神款', value: 'A' },
+    { title: '欧皇款', value: 'B' },
+    { title: '隐藏款', value: 'C' },
+    { title: '普通款', value: 'D' }
+];
+
+interface RecordModalProps {
+    poolId: string;
+}
+
+export interface RecordModalRef {
+    show: () => void;
+    close: () => void;
+}
+
+export const RecordModal = forwardRef<RecordModalRef, RecordModalProps>(({ poolId }, ref) => {
+    const [visible, setVisible] = useState(false);
+    const [data, setData] = useState<any[]>([]);
+    const [activeTab, setActiveTab] = useState(TABS[0]);
+    const [loading, setLoading] = useState(false);
+    const [refreshing, setRefreshing] = useState(false);
+    const [lastId, setLastId] = useState<string | undefined>(undefined);
+    const [taggingA, setTaggingA] = useState(0);
+    const [taggingB, setTaggingB] = useState(0);
+
+    useImperativeHandle(ref, () => ({
+        show: () => {
+            setVisible(true);
+            refresh();
+        },
+        close: () => setVisible(false)
+    }));
+
+    const refresh = () => {
+        setLastId(undefined);
+        setData([]);
+        loadData(true);
+        loadStats();
+    };
+
+    const loadStats = async () => {
+        try {
+            const resA = await countRecordsAfterLastLevel({ levelEnumList: ['A'], poolId });
+            setTaggingA(resA.data || 0); // Check API structure: {data: number}
+            const resB = await countRecordsAfterLastLevel({ levelEnumList: ['B'], poolId });
+            setTaggingB(resB.data || 0);
+        } catch (e) {
+            console.error('Failed to load stats', e);
+        }
+    };
+
+    const loadData = async (isRefresh = false) => {
+        if (loading) return;
+        setLoading(true);
+        try {
+           const currentLastId = isRefresh ? undefined : lastId;
+           const res = await getBuyRecord(poolId, currentLastId, activeTab.value as any);
+           if (res && res.length > 0) {
+               setData(prev => isRefresh ? res : [...prev, ...res]);
+               setLastId(res[res.length - 1].id);
+           }
+        } catch (e) {
+            console.error('Failed to load records', e);
+        }
+        setLoading(false);
+        setRefreshing(false);
+    };
+
+    const handleTabChange = (tab: any) => {
+        setActiveTab(tab);
+        // Reset and reload
+        setLastId(undefined);
+        setData([]);
+        // We can't immediately call loadData due to state update async nature, 
+        // but let's assume useEffect or immediate call with arg works.
+        // Actually best to useEffect on activeTab change?
+        // Or specific effect for tab
+        // Let's do explicit reload in effect or here
+        // setData is sync-ish in RN usually batching, let's use effect.
+    };
+
+    useEffect(() => {
+        if (visible) {
+             setLastId(undefined);
+             setData([]);
+             loadData(true);
+        }
+    }, [activeTab]);
+
+    const renderItem = ({ item }: { item: any }) => (
+        <View style={styles.item}>
+            <Image source={{ uri: item.avatar || Images.common.defaultAvatar }} style={styles.avatar} />
+            <Text style={styles.nickname} numberOfLines={1}>{item.nickname}</Text>
+            
+            {/* Level Icon - Assuming we have images for level text similar to legacy */}
+            {/* Legacy: :src="LEVEL_MAP[item.level].titleText" */}
+            {/* We don't have titleText in our config yet, need to map or use text */}
+            {/* For now use Text with color */}
+            <View style={[styles.levelTag, { borderColor: LEVEL_MAP[item.level]?.color || '#000' }]}>
+                 <Text style={[styles.levelText, { color: LEVEL_MAP[item.level]?.color || '#000' }]}>
+                     {LEVEL_MAP[item.level]?.title}
+                 </Text>
+            </View>
+
+            <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
+            <Text style={styles.countText}>×{item.lastCount}</Text>
+            <Image source={{ uri: item.cover }} style={styles.itemImage} />
+        </View>
+    );
+
+    return (
+        <Modal visible={visible} transparent animationType="slide" onRequestClose={() => setVisible(false)}>
+             <View style={styles.overlay}>
+                 <TouchableOpacity style={styles.mask} activeOpacity={1} onPress={() => setVisible(false)} />
+                 <ImageBackground 
+                    source={{ uri: Images.box.detail.recordBg }} 
+                    style={styles.container}
+                    resizeMode="stretch"
+                 >
+                     {/* Header */}
+                     <View style={styles.header}>
+                         <Text style={styles.title}>购买记录</Text>
+                         <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
+                             {/* Close Icon or Text */}
+                             <Text style={{ fontSize: 20 }}>×</Text>
+                         </TouchableOpacity>
+                     </View>
+
+                     {/* Stats Banner */}
+                     <ImageBackground 
+                        source={{ uri: Images.box.detail.taggingBg }} 
+                        style={styles.statsBanner}
+                        resizeMode="stretch"
+                     >
+                         <View style={styles.statItem}>
+                             <Text style={styles.statNum}>{taggingA}</Text>
+                             <Text style={styles.statLabel}>发未出</Text>
+                             <Text style={[styles.statLevel, { color: LEVEL_MAP.A.color }]}>超神款</Text>
+                         </View>
+                         <View style={styles.statItem}>
+                             <Text style={styles.statNum}>{taggingB}</Text>
+                             <Text style={styles.statLabel}>发未出</Text>
+                             <Text style={[styles.statLevel, { color: LEVEL_MAP.B.color }]}>欧皇款</Text>
+                         </View>
+                     </ImageBackground>
+
+                     {/* Tabs */}
+                     <View style={styles.tabs}>
+                         {TABS.map(tab => (
+                             <TouchableOpacity 
+                                key={tab.value} 
+                                style={[styles.tab, activeTab.value === tab.value && styles.activeTab]}
+                                onPress={() => handleTabChange(tab)}
+                             >
+                                 <Text style={[styles.tabText, activeTab.value === tab.value && styles.activeTabText]}>
+                                     {tab.title}
+                                 </Text>
+                             </TouchableOpacity>
+                         ))}
+                     </View>
+
+                     {/* List */}
+                     <FlatList
+                        data={data}
+                        renderItem={renderItem}
+                        keyExtractor={(item, index) => item.id || String(index)}
+                        style={styles.list}
+                        contentContainerStyle={{ paddingBottom: 20 }}
+                        onEndReached={() => loadData(false)}
+                        onEndReachedThreshold={0.5}
+                        ListEmptyComponent={
+                            !loading ? <Text style={styles.emptyText}>暂无记录</Text> : null
+                        }
+                        ListFooterComponent={loading ? <ActivityIndicator color="#000" /> : null}
+                     />
+
+                 </ImageBackground>
+             </View>
+        </Modal>
+    );
+});
+
+const styles = StyleSheet.create({
+    overlay: {
+        flex: 1,
+        backgroundColor: 'rgba(0,0,0,0.5)',
+        justifyContent: 'flex-end',
+    },
+    mask: {
+        flex: 1,
+    },
+    container: {
+        height: height * 0.7,
+        paddingTop: 0,
+        backgroundColor: '#fff', 
+        borderTopLeftRadius: 15,
+        borderTopRightRadius: 15,
+        overflow: 'hidden',
+    },
+    header: {
+        height: 50,
+        justifyContent: 'center',
+        alignItems: 'center',
+        marginTop: 10,
+    },
+    title: {
+        fontSize: 18,
+        fontWeight: 'bold',
+    },
+    closeBtn: {
+        position: 'absolute',
+        right: 20,
+        top: 15,
+        width: 30,
+        height: 30,
+        alignItems: 'center',
+        justifyContent: 'center', 
+        backgroundColor: '#eee',
+        borderRadius: 15,
+    },
+    statsBanner: {
+        width: '90%',
+        alignSelf: 'center',
+        height: 50,
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+        alignItems: 'center',
+        marginBottom: 10,
+    },
+    statItem: {
+        flexDirection: 'row',
+        alignItems: 'center',
+    },
+    statNum: { fontSize: 16, fontWeight: 'bold', color: '#fff', textShadowColor:'#000', textShadowRadius:1 },
+    statLabel: { fontSize: 12, color: '#ddd', marginHorizontal: 5 },
+    statLevel: { fontSize: 12, fontWeight: 'bold' },
+    
+    tabs: {
+        flexDirection: 'row',
+        justifyContent: 'space-around',
+        paddingHorizontal: 10,
+        marginBottom: 10,
+        backgroundColor: 'rgba(255,255,255,0.1)',
+        paddingVertical: 5,
+    },
+    tab: {
+        paddingVertical: 5,
+        paddingHorizontal: 10,
+        borderRadius: 5,
+        backgroundColor: '#eee',
+    },
+    activeTab: {
+        backgroundColor: '#FEC433',
+    },
+    tabText: { fontSize: 12, color: '#666' },
+    activeTabText: { color: '#000', fontWeight: 'bold' },
+
+    list: {
+        flex: 1,
+        paddingHorizontal: 15,
+    },
+    item: {
+        flexDirection: 'row',
+        alignItems: 'center',
+        backgroundColor: '#FFF7E3',
+        marginBottom: 10,
+        padding: 5,
+        borderRadius: 8,
+        height: 60,
+    },
+    avatar: { width: 40, height: 40, borderRadius: 20 },
+    nickname: { flex: 1, marginLeft: 10, fontSize: 12, color: '#333' },
+    levelTag: { 
+        borderWidth: 1, 
+        paddingHorizontal: 4, 
+        borderRadius: 4, 
+        marginHorizontal: 5
+    },
+    levelText: { fontSize: 10, fontWeight: 'bold' },
+    itemName: { width: 80, fontSize: 12, color: '#FEC433', marginHorizontal: 5 },
+    countText: { fontSize: 12, color: '#000', fontWeight: 'bold', marginRight: 5 },
+    itemImage: { width: 40, height: 40, borderRadius: 20, backgroundColor: '#fff' },
+    emptyText: { textAlign: 'center', marginTop: 20, color: '#999' }
+
+});

+ 128 - 0
components/award-detail-yfs/RuleModal.tsx

@@ -0,0 +1,128 @@
+import { Images } from '@/constants/images';
+import { getParamConfig } from '@/services/user';
+import { ImageBackground } from 'expo-image';
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import { ActivityIndicator, Dimensions, Modal, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { WebView } from 'react-native-webview';
+
+const { width } = Dimensions.get('window');
+
+export interface RuleModalRef {
+  show: (ruleKey?: string) => void;
+  close: () => void;
+}
+
+export const RuleModal = forwardRef<RuleModalRef, {}>((props, ref) => {
+  const [visible, setVisible] = useState(false);
+  const [content, setContent] = useState('');
+  const [loading, setLoading] = useState(false);
+
+  useImperativeHandle(ref, () => ({
+    show: (ruleKey = 'baoxiang_rule') => {
+      setVisible(true);
+      loadRule(ruleKey);
+    },
+    close: () => setVisible(false),
+  }));
+
+  const loadRule = async (key: string) => {
+    setLoading(true);
+    try {
+      const res = await getParamConfig(key);
+      if (res && res.data) {
+        // Simple HTML wrap
+        const html = `
+          <html>
+            <head>
+              <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+              <style>
+                body { font-size: 14px; color: #333; padding: 10px; word-wrap: break-word; }
+                img { max-width: 100%; height: auto; }
+                p { margin: 5px 0; }
+              </style>
+            </head>
+            <body>${res.data}</body>
+          </html>
+        `;
+        setContent(html);
+      }
+    } catch (e) {
+      console.error(e);
+      setContent('<p>加载失败</p>');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <Modal visible={visible} transparent animationType="fade" onRequestClose={() => setVisible(false)}>
+       <View style={styles.overlay}>
+          <View style={styles.container}>
+             <ImageBackground 
+                source={{ uri: Images.mine.dialogContentBg }} 
+                style={styles.bg}
+                resizeMode="stretch"
+             >
+                 <View style={styles.content}>
+                     {loading ? (
+                         <ActivityIndicator color="#000" style={{ marginTop: 50 }} />
+                     ) : (
+                         <WebView 
+                            originWhitelist={['*']}
+                            source={{ html: content }}
+                            style={styles.webview}
+                            showsVerticalScrollIndicator={false}
+                         />
+                     )}
+                 </View>
+             </ImageBackground>
+
+             {/* Close Button Moved to Bottom */}
+             <TouchableOpacity style={styles.closeBtn} onPress={() => setVisible(false)}>
+                  <ImageBackground source={{ uri: Images.common.closeBut }} style={styles.closeIcon} resizeMode="contain" />
+             </TouchableOpacity>
+          </View>
+       </View>
+    </Modal>
+  );
+});
+
+const styles = StyleSheet.create({
+  overlay: {
+    flex: 1,
+    backgroundColor: 'rgba(0,0,0,0.6)',
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  container: {
+    width: width * 0.9,
+    alignItems: 'center',
+  },
+  bg: {
+      width: '100%',
+      height: 400, // Adjust height
+      paddingTop: 60,
+      paddingHorizontal: 30,
+      paddingBottom: 30,
+      marginBottom: 30, // Space for button
+  },
+  content: {
+      flex: 1,
+      overflow: 'hidden',
+  },
+  webview: {
+      flex: 1,
+      backgroundColor: 'transparent',
+  },
+  closeBtn: {
+      width: 45,
+      height: 45,
+      justifyContent: 'center',
+      alignItems: 'center',
+      zIndex: 10,
+  },
+  closeIcon: {
+      width: '100%',
+      height: '100%',
+  }
+});

+ 20 - 10
components/home/GoodsCard.tsx

@@ -23,16 +23,20 @@ export function GoodsCard({ data, onPress }: GoodsCardProps) {
       <ImageBackground
         source={{ uri: Images.home.cellGoodsBg }}
         style={[styles.cell, { width: CARD_WIDTH }]}
-        resizeMode="cover"
+        resizeMode="stretch"
       >
         <View style={[styles.content, { width: CARD_WIDTH - 10 }]}>
           <View style={styles.imgBox}>
-            <Image source={data.cover} style={styles.image} contentFit="contain" />
-            {/* 图片边框装饰 */}
+            <Image 
+                source={typeof data.cover === 'string' ? { uri: data.cover } : data.cover} 
+                style={styles.image} 
+                contentFit="contain" 
+            />
+            {/* 图片边框装饰 - scaled to 70% to match Vue implementation */}
             <ImageBackground
               source={{ uri: Images.home.imgBoxBorder }}
               style={styles.imgBoxBorder}
-              resizeMode="center"
+              resizeMode="contain"
             />
           </View>
           <View style={styles.textBox}>
@@ -59,18 +63,24 @@ const styles = StyleSheet.create({
   cell: {
     height: CARD_HEIGHT,
     marginBottom: 12,
+    justifyContent: 'center',
+    alignItems: 'center',
   },
   content: {
     overflow: 'hidden',
     borderRadius: 8,
+    alignItems: 'center',
+    // Width set inline to CARD_WIDTH - 10
   },
   imgBox: {
-    width: IMG_BOX_WIDTH,
+    width: IMG_BOX_WIDTH, // Vue uses fixed width 340rpx (170pt)
     height: IMG_BOX_HEIGHT,
     borderRadius: 7,
     overflow: 'hidden',
-    paddingHorizontal: 6,
+    paddingHorizontal: 6, // Match Vue: padding: 0 12rpx
     alignSelf: 'center',
+    justifyContent: 'center', // Ensure content centers if padding affects it
+    alignItems: 'center',
   },
   image: {
     width: '100%',
@@ -78,10 +88,10 @@ const styles = StyleSheet.create({
   },
   imgBoxBorder: {
     position: 'absolute',
-    left: 0,
-    top: 0,
-    width: '100%',
-    height: '100%',
+    width: '85%', // 70% background-size in Vue roughly maps to shrinking this, but let's try 85% first as 70% might be too small if cover is contain. Vue says bg-size 70%.
+    height: '85%',
+    alignSelf: 'center',
+    top: '7.5%', // Center it vertically
   },
   textBox: {
     paddingHorizontal: 9,

+ 109 - 0
components/mine/KefuPopup.tsx

@@ -0,0 +1,109 @@
+import { Image } from 'expo-image';
+import React, { forwardRef, useImperativeHandle, useState } from 'react';
+import {
+    Modal,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from 'react-native';
+
+export interface KefuPopupRef {
+  open: () => void;
+  close: () => void;
+}
+
+interface KefuPopupProps {
+  onClose?: () => void;
+}
+
+export const KefuPopup = forwardRef<KefuPopupRef, KefuPopupProps>(
+  ({ onClose }, ref) => {
+    const [visible, setVisible] = useState(false);
+
+    useImperativeHandle(ref, () => ({
+      open: () => setVisible(true),
+      close: () => {
+        setVisible(false);
+        onClose?.();
+      },
+    }));
+
+    const handleClose = () => {
+      setVisible(false);
+      onClose?.();
+    };
+
+    return (
+      <Modal
+        visible={visible}
+        transparent
+        animationType="fade"
+        onRequestClose={handleClose}
+      >
+        <View style={styles.overlay}>
+          <TouchableOpacity
+            style={styles.backdrop}
+            activeOpacity={1}
+            onPress={handleClose}
+          />
+          <View style={styles.content}>
+            <Image
+              source={{ uri: 'https://cdn.acetoys.cn/kefu/qr.jpg' }}
+              style={styles.qrImage}
+              contentFit="contain"
+            />
+            <Text style={styles.tipText}>长按识别二维码添加客服</Text>
+            <TouchableOpacity style={styles.closeBtn} onPress={handleClose}>
+              <Text style={styles.closeBtnText}>关闭</Text>
+            </TouchableOpacity>
+          </View>
+        </View>
+      </Modal>
+    );
+  }
+);
+
+const styles = StyleSheet.create({
+  overlay: {
+    flex: 1,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  backdrop: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    backgroundColor: 'rgba(0, 0, 0, 0.5)',
+  },
+  content: {
+    backgroundColor: '#fff',
+    borderRadius: 15,
+    padding: 20,
+    alignItems: 'center',
+    zIndex: 10,
+  },
+  qrImage: {
+    width: 250,
+    height: 250,
+  },
+  tipText: {
+    fontSize: 14,
+    color: '#666',
+    marginTop: 15,
+    marginBottom: 20,
+  },
+  closeBtn: {
+    backgroundColor: '#FC7D2E',
+    paddingHorizontal: 40,
+    paddingVertical: 12,
+    borderRadius: 25,
+  },
+  closeBtnText: {
+    color: '#fff',
+    fontSize: 16,
+    fontWeight: 'bold',
+  },
+});

+ 32 - 0
constants/config.ts

@@ -0,0 +1,32 @@
+import { Images } from './images';
+
+export const LEVEL_MAP: Record<string, { title: string; color: string; productItem: string; bg: string; titleText: string }> = {
+  A: {
+    title: '超神款',
+    color: '#E82C31',
+    productItem: Images.box.detail.productItemA,
+    bg: Images.box.detail.levelA,
+    titleText: Images.box.detail.levelTextA,
+  },
+  B: {
+    title: '欧皇款',
+    color: '#FF9500',
+    productItem: Images.box.detail.productItemB,
+    bg: Images.box.detail.levelB,
+    titleText: Images.box.detail.levelTextB,
+  },
+  C: {
+    title: '隐藏款',
+    color: '#8E44AD',
+    productItem: Images.box.detail.productItemC,
+    bg: Images.box.detail.levelC,
+    titleText: Images.box.detail.levelTextC,
+  },
+  D: {
+    title: '普通款',
+    color: '#34495E', // Using a dark grey/blue as fallback if not strictly defined in legacy view, legacy had #666
+    productItem: Images.box.detail.productItemD,
+    bg: Images.box.detail.levelD,
+    titleText: Images.box.detail.levelTextD,
+  },
+};

+ 9 - 1
constants/images.ts

@@ -143,9 +143,11 @@ export const Images = {
       levelTextB: `${CDN_BASE}/box/detail/levelTextB.png`,
       levelTextC: `${CDN_BASE}/box/detail/levelTextC.png`,
       levelTextD: `${CDN_BASE}/box/detail/levelTextD.png`,
+      playMethod: `${CDN_BASE}/box/detail/playMethod.png`,
     },
   },
   welfare: {
+    roomBg: `${CDN_BASE}/welfare/roomBg.png`,
     kaixinWelfareBg: `${CDN_BASE}/welfare/kaixinWelfareBg.png`,
     kaixinRoomBg: `${CDN_BASE}/welfare/kaixinRoomBg.png`,
     kaixinRoom1: `${CDN_BASE}/welfare/kaixinRoom1.png`,
@@ -209,7 +211,7 @@ export const Images = {
     order2: `${CDN_BASE}/mine/order2.png`,
     dialogContentBg: `${CDN_BASE}/mine/dialogContentBg.png`,
     dialogClose: `${CDN_BASE}/mine/dialogClose.png`,
-    wallet: `${CDN_BASE}/mine/wallet.png`,
+    wallet: 'https://cdn.acetoys.cn/qimiao-supermart/mine/wallet.png', // 原项目钱包图标
     exchangeIcon: `${CDN_BASE}/mine/exchangeIcon.png`,
     customerService: `${CDN_BASE}/mine/customerService.png`,
     address: `${CDN_BASE}/mine/address.png`,
@@ -224,6 +226,12 @@ export const Images = {
     stoneImage: `${CDN_BASE}/mine/stoneImage.png`,
     magicTypeBg: `${CDN_BASE}/mine/magicTypeBg.png`,
     stoneBg: `${CDN_BASE}/mine/stoneBg.png`,
+    avatarBorderBg: `${CDN_BASE}/common/noavatar.png`,  // 头像边框背景
+    shopOrder: `${CDN_BASE}/mine/shopOrder.png`,  // 商城订单入口图标
+    boxOrder: `${CDN_BASE}/mine/boxOrder.png`,  // 奖池订单入口图标
+    typeSelectIconT: `${CDN_BASE}/mine/typeSelectIconT.png`,
+    typeSelectIconB: `${CDN_BASE}/mine/typeSelectIconB.png`,
+    checkAll: `${CDN_BASE}/mine/checkAll.png`,
   },
   // 地址相关
   address: {

+ 6 - 1
contexts/AuthContext.tsx

@@ -1,7 +1,7 @@
 import React, { createContext, useContext, useEffect, useState } from 'react';
 
 import { clearToken, getToken } from '@/services/http';
-import { getUserInfo, UserInfo } from '@/services/user';
+import { getUserInfo, logout as logoutApi, UserInfo } from '@/services/user';
 
 interface AuthContextType {
   isLoggedIn: boolean;
@@ -41,6 +41,11 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
   };
 
   const logout = async () => {
+    try {
+      await logoutApi();
+    } catch (e) {
+      console.warn('Logout API failed', e);
+    }
     setUser(null);
     setHasToken(false);
     await clearToken();

+ 5 - 3
services/address.ts

@@ -51,8 +51,9 @@ export const getDefaultAddress = async (): Promise<Address | null> => {
 
 // 获取地址列表
 export const getAddressList = async (size = 100): Promise<Address[]> => {
-  const res = await get<{ records: Address[] }>(apis.LIST, { size });
-  return res.data?.records || [];
+  const res = await get<Address[]>(apis.LIST, { size });
+  // API returns array directly in data
+  return Array.isArray(res.data) ? res.data : (res.data as any)?.records || [];
 };
 
 // 添加地址
@@ -84,7 +85,8 @@ export const updateOrderAddress = async (params: { tradeNo: string; addressBookI
 
 // 获取地区数据
 export const getArea = async (pid?: number): Promise<AreaItem[]> => {
-  const res = await get<AreaItem[]>(apis.GET_AREA, pid ? { pid } : { pid: 1 });
+  const params = pid !== undefined ? { pid } : { pid: 0 }; // Default to 0 if undefined, allow 0 if passed
+  const res = await get<AreaItem[]>(apis.GET_AREA, params);
   return res.data || [];
 };
 

+ 12 - 0
services/award.ts

@@ -52,8 +52,19 @@ const apis = {
   OPEN_BOX: '/api/luck/treasure-box/open',
   HARRY_EXCHANGE: '/api/redeemCode/exchange',
   WIN_RECORDS: '/api/luck/treasure-box/win-records',
+  COUNT_RECORDS: '/api/luckPool/countRecordsAfterLastLevel',
 };
 
+// ... (existing code)
+
+// 获取最后一次出奖后的记录数
+export const countRecordsAfterLastLevel = async (params: { levelEnumList: string[], poolId: string }) => {
+  const res = await post<number>(apis.COUNT_RECORDS, params);
+  return res;
+};
+
+
+
 export interface IPItem {
   id: string;
   name: string;
@@ -470,4 +481,5 @@ export default {
   getSumInventory,
   harryExchange,
   getWinRecords,
+  countRecordsAfterLastLevel,
 };

+ 6 - 9
services/mall.ts

@@ -94,17 +94,14 @@ export interface OrderItem {
   quantity: number;
   paymentAmount: number;
   createTime: string;
+  type?: number;
+  paidAmount?: number;
+  totalAmount?: number;
+  couponAmount?: number;
+  paymentTimeoutTime?: string;
 }
 
-export interface OrderDetail {
-  tradeNo: string;
-  status: number;
-  statusText: string;
-  goodsName: string;
-  goodsCover: string;
-  quantity: number;
-  paymentAmount: number;
-  createTime: string;
+export interface OrderDetail extends OrderItem {
   address?: {
     contactName: string;
     contactNo: string;

+ 10 - 0
services/user.ts

@@ -23,14 +23,17 @@ const apis = {
   ACTIVITY_TA_LEVEL: '/api/activity/activityTa/level',
   CLAIM_DAILY_REWARD: '/api/wallet/recharge/claimDailyReward',
   NEW_USER_NUM: '/api/wallet/ranking/newUserNum',
+  LOGOUT: '/api/account/logout',
 };
 
 export interface UserInfo {
   id: string;
   userId?: string;
+  username?: string;
   nickname: string;
   avatar: string;
   phone?: string;
+  mobile?: string;
   realName?: string;
   idNum?: string;
   balance?: number;
@@ -66,6 +69,12 @@ export const login = async (params: LoginParams): Promise<LoginResult> => {
   return { success: false };
 };
 
+// 退出登录
+export const logout = async (): Promise<boolean> => {
+  const res = await get(apis.LOGOUT);
+  return res.success;
+};
+
 // 获取用户信息
 export const getUserInfo = async (): Promise<UserInfo | null> => {
   const res = await get<UserInfo>(apis.USER_INFO);
@@ -188,6 +197,7 @@ export const getNewUserNum = async (params?: any) => {
 
 export default {
   login,
+  logout,
   getUserInfo,
   updateUserInfo,
   updateAvatar,

+ 7 - 0
services/wallet.ts

@@ -10,6 +10,7 @@ const apis = {
     WITHDRAW_INFO: '/api/user/withdraw/info', // Bank info
     WITHDRAW_SAVE: '/api/user/withdraw/save',
 
+    RECHARGE_SUBMIT: '/api/wallet/recharge/submit',
     RECHARGE_GENERATE_LINK: '/api/user/recharge/generatePaymentLink',
     RECHARGE_CHECK_PAYMENT_STATUS: '/api/user/recharge/checkPaymentStatus',
     
@@ -59,6 +60,11 @@ export const generatePaymentLink = async (amount: number, payType: string, walle
     return res;
 };
 
+export const rechargeSubmit = async (amount: number, paymentType: string, walletType: string) => {
+    const res = await postL(apis.RECHARGE_SUBMIT, { amount, paymentType, walletType });
+    return res;
+};
+
 export const checkPaymentStatus = async (data: { tradeNo: string }) => {
     const res = await post(apis.RECHARGE_CHECK_PAYMENT_STATUS, data);
     return res;
@@ -78,6 +84,7 @@ export default {
     saveWithdraw,
     withdraw,
     generatePaymentLink,
+    rechargeSubmit,
     checkPaymentStatus,
     getNewUserPromotion,
     coupons: async (size = 30) => {

+ 1 - 0
supermart-app

@@ -0,0 +1 @@
+Subproject commit a23d34688f1b87ab6a7f7bc34f96fdbd029916ec