Ver Fonte

仓库添加全选-祈福添加全选

zbb há 3 meses atrás
pai
commit
c816a387f6

+ 142 - 2
app/award-detail/components/LotteryResultModal.tsx

@@ -1,3 +1,4 @@
+import { AVPlaybackStatus, ResizeMode, Video } from 'expo-av';
 import { Image } from 'expo-image';
 import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
 import {
@@ -59,6 +60,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,6 +136,11 @@ 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 flipAnims = useRef<Animated.Value[]>([]);
     const dataLoadedRef = useRef(false);
 
@@ -111,6 +156,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,8 +221,39 @@ 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
+                const hasLevelA = array.some((item: LotteryItem) => item.level === 'A');
+                if (hasLevelA) {
+                    playVideoUrl = DEFAULT_JACKPOT_VIDEO;
+                }
+            }
+
+            // Check for Level A item for KingModal logic (which happens after video)
+            const levelAItem = array.find((item: LotteryItem) => item.level === 'A');
+
+            const startFlow = () => {
+                setVideoVisible(false);
+                if (levelAItem) {
+                    setKingData(levelAItem);
+                    setKingVisible(true);
+                } else {
+                    setTimeout(() => flipCards(array), 500);
+                }
+            };
+
+            if (playVideoUrl) {
+                setVideoUrl(playVideoUrl);
+                setVideoVisible(true);
+            } else {
+                startFlow();
+            }
           } else if (attempts < maxAttempts) {
             attempts++;
             timeoutId = setTimeout(fetchData, 400);
@@ -377,6 +457,55 @@ 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
+                    source={{ uri: videoUrl }}
+                    style={styles.video}
+                    resizeMode={ResizeMode.COVER}
+                    shouldPlay
+                    isLooping={false}
+                    onPlaybackStatusUpdate={(status: AVPlaybackStatus) => {
+                        if (status.isLoaded && status.didJustFinish) {
+                             // Video finished
+                             setVideoVisible(false);
+                             // Proceed to next step
+                             const levelAItem = tableData.find((item: LotteryItem) => item.level === 'A');
+                             if (levelAItem) {
+                                 setKingData(levelAItem);
+                                 setKingVisible(true);
+                             } else {
+                                 setTimeout(() => flipCards(tableData), 300);
+                             }
+                        }
+                    }}
+                />
+                 <TouchableOpacity 
+                    style={styles.skipVideoBtn} 
+                    onPress={() => {
+                        setVideoVisible(false);
+                         // Proceed to next step
+                         const levelAItem = tableData.find((item: LotteryItem) => item.level === 'A');
+                         if (levelAItem) {
+                             setKingData(levelAItem);
+                             setKingVisible(true);
+                         } else {
+                             setTimeout(() => flipCards(tableData), 300);
+                         }
+                    }}
+                >
+                    <Text style={styles.skipText}>跳过</Text>
+                </TouchableOpacity>
+            </View>
+        ) : null}
       </Modal>
     );
   }
@@ -508,4 +637,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 },
 });

+ 51 - 6
app/store/index.tsx

@@ -191,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 = () => {
@@ -200,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({});
@@ -394,11 +407,32 @@ export default function StoreScreen() {
         </View>
         {mainTabIndex !== 2 && list.length > 0 && (
           <View style={[styles.bottomBar, { paddingBottom: insets.bottom + 10 }]}>
-            <TouchableOpacity style={styles.bottomBtn} onPress={mainTabIndex === 0 ? handleTakeGoods : handleMoveOutAll}>
-              <ImageBackground source={{ uri: Images.common.butBgL }} style={styles.bottomBtnBg} resizeMode="stretch">
-                <Text style={styles.bottomBtnText}>{mainTabIndex === 0 ? '立即提货' : '移出保险柜'}</Text>
-              </ImageBackground>
-            </TouchableOpacity>
+            {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>
           </View>
         )}
@@ -560,6 +594,17 @@ const styles = StyleSheet.create({
     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

+ 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>
   );

+ 1 - 0
constants/images.ts

@@ -231,6 +231,7 @@ export const Images = {
     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: {

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "@react-navigation/elements": "^2.6.3",
     "@react-navigation/native": "^7.1.8",
     "expo": "~54.0.30",
+    "expo-av": "^16.0.8",
     "expo-clipboard": "~8.0.8",
     "expo-constants": "~18.0.12",
     "expo-font": "~14.0.10",