import { Image } from 'expo-image'; import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { ActivityIndicator, Animated, Dimensions, ImageBackground, Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; 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 CDN_BASE = 'https://cdn.acetoys.cn'; const imgUrl = `${CDN_BASE}/kai_xin_ma_te/supermart`; const imgUrlSupermart = `${CDN_BASE}/supermart`; const LEVEL_MAP: Record = { A: { title: '超神款', color: '#F62C71', rgba: 'rgba(246, 44, 113, 1)', resultBg: `${imgUrlSupermart}/supermart/box/resultBgA.png`, borderImg: `${imgUrlSupermart}/supermart/box/borderImgA.png` }, B: { title: '欧皇款', color: '#E9C525', rgba: 'rgba(233,197,37, 1)', resultBg: `${imgUrlSupermart}/supermart/box/resultBgB.png`, borderImg: `${imgUrlSupermart}/supermart/box/borderImgB.png` }, C: { title: '隐藏款', color: '#A72CF6', rgba: 'rgba(167, 44, 246, 1)', resultBg: `${imgUrlSupermart}/supermart/box/resultBgC.png`, borderImg: `${imgUrlSupermart}/supermart/box/borderImgC.png` }, D: { title: '普通款', color: '#40c9d7', rgba: 'rgba(64, 201, 215, 1)', resultBg: `${imgUrlSupermart}/supermart/box/resultBgD.png`, borderImg: `${imgUrlSupermart}/supermart/box/borderImgD.png` }, }; const LotteryImages = { lotteryBg: `${imgUrlSupermart}/supermart/box/sequence/sequence0.jpg`, cardBack: `${imgUrl}/box/back1.png`, halo: `${imgUrlSupermart}/supermart/box/halo.gif`, }; interface LotteryItem { id: string; name: string; cover: string; level: string; magicAmount?: number; spu?: { marketPrice: number }; } export interface LotteryResultModalRef { show: (tradeNo: string) => void; close: () => void; } interface LotteryResultModalProps { onClose?: () => void; onGoStore?: () => void; } export const LotteryResultModal = forwardRef( ({ onClose, onGoStore }, ref) => { const insets = useSafeAreaInsets(); const [visible, setVisible] = useState(false); const [loading, setLoading] = useState(true); const [tableData, setTableData] = useState([]); const [total, setTotal] = useState(0); const [showResult, setShowResult] = useState(false); const [showDh, setShowDh] = useState(false); const [haloShow, setHaloShow] = useState(false); const [rebateAmount, setRebateAmount] = useState(0); const [animationEnabled, setAnimationEnabled] = useState(true); const [isSkip, setIsSkip] = useState(true); const [tradeNo, setTradeNo] = useState(''); const flipAnims = useRef([]); const dataLoadedRef = useRef(false); useImperativeHandle(ref, () => ({ show: (tNo: string) => { setTradeNo(tNo); setVisible(true); setLoading(true); setTableData([]); setTotal(0); setShowResult(false); setShowDh(false); setHaloShow(false); setRebateAmount(0); setIsSkip(true); dataLoadedRef.current = false; flipAnims.current = []; }, close: () => { setVisible(false); dataLoadedRef.current = false; }, })); const flipCards = (data: LotteryItem[]) => { setIsSkip(false); setHaloShow(true); setTimeout(() => setHaloShow(false), 1000); const maxCards = Math.min(data.length, 9); const animations = flipAnims.current.slice(0, maxCards).map((anim, index) => { return Animated.sequence([ Animated.delay(index * 200), Animated.timing(anim, { toValue: 1, duration: 200, useNativeDriver: true }), ]); }); Animated.parallel(animations).start(() => { setTimeout(() => { setShowDh(data.every((item) => item.level !== 'B' && item.level !== 'A')); setShowResult(true); }, 900); }); }; useEffect(() => { if (!visible || !tradeNo || dataLoadedRef.current) return; let attempts = 0; const maxAttempts = 13; let timeoutId: ReturnType; let isMounted = true; const fetchData = async () => { if (!isMounted) return; try { const res = await getApplyResult(tradeNo); if (!isMounted) return; if (res?.inventoryList && res.inventoryList.length > 0) { dataLoadedRef.current = true; if (res.rebateAmount) setRebateAmount(res.rebateAmount); let array = res.inventoryList; if (res.magicFireworksList && res.magicFireworksList.length > 0) { array = [...res.magicFireworksList, ...res.inventoryList]; } flipAnims.current = array.map(() => new Animated.Value(0)); const sum = array.reduce((acc: number, item: LotteryItem) => acc + (item.magicAmount || 0), 0); setTotal(sum); setTableData(array); setLoading(false); // 直接开始翻牌动画 setTimeout(() => flipCards(array), 500); } else if (attempts < maxAttempts) { attempts++; timeoutId = setTimeout(fetchData, 400); } else { setLoading(false); window.alert('获取结果超时,请在仓库中查看'); } } catch (error) { if (attempts < maxAttempts) { attempts++; timeoutId = setTimeout(fetchData, 400); } else { setLoading(false); } } }; fetchData(); return () => { isMounted = false; if (timeoutId) clearTimeout(timeoutId); }; }, [visible, tradeNo]); const handleSkip = () => { setIsSkip(false); flipAnims.current.forEach((anim) => anim.setValue(1)); setShowDh(tableData.every((item) => item.level !== 'B' && item.level !== 'A')); setShowResult(true); }; const handleDhAll = async () => { if (!total) return; try { const ids = tableData.filter((item) => item.magicAmount).map((item) => item.id); const res = await convertApply(ids); if (res) { setTableData((prev) => prev.map((item) => ({ ...item, magicAmount: 0 }))); setTotal(0); window.alert('兑换成功'); } } catch { window.alert('兑换失败,请重试'); } }; const handleBack = () => { setVisible(false); onClose?.(); }; const handleGoStore = () => { setVisible(false); onGoStore?.(); }; const renderCard = (item: LotteryItem, index: number) => { const levelConfig = LEVEL_MAP[item.level] || LEVEL_MAP.D; const flipAnim = flipAnims.current[index] || new Animated.Value(1); const isFirst9 = index < 9; // 前9张卡片有翻转动画 if (isFirst9) { const backRotate = flipAnim.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '90deg'] }); const frontRotate = flipAnim.interpolate({ inputRange: [0, 1], outputRange: ['-90deg', '0deg'] }); const backOpacity = flipAnim.interpolate({ inputRange: [0, 0.5, 1], outputRange: [1, 0, 0] }); const frontOpacity = flipAnim.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, 0, 1] }); return ( {/* 背面 - 卡牌背面 */} {/* 正面 - 商品信息 */} {levelConfig.title} ¥{item.spu?.marketPrice || 0} 价值:{item.magicAmount || 0}果实 {item.name} ); } // 9张以后的卡片直接显示正面 return ( {levelConfig.title} ¥{item.spu?.marketPrice || 0} 价值:{item.magicAmount || 0}果实 {item.name} ); }; return ( {/* 光晕效果 */} {haloShow && ( )} {/* 标题 */} 恭喜您获得 {/* 主内容区 */} {loading ? ( 正在开启宝箱... ) : ( {tableData.map((item, index) => renderCard(item, index))} )} {/* 底部按钮区 */} {showResult && ( {total > 0 && showDh && ( 全部兑换 共兑换果实 {total} )} 再来一发 前往 仓库 查看 {rebateAmount > 0 && ( 本次支付返还果实 {rebateAmount} )} 是否开启动画 setAnimationEnabled(!animationEnabled)} > )} {/* 跳过动画按钮 */} {isSkip && !loading && ( 跳过动画 )} ); } ); const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#1a1a2e' }, background: { flex: 1 }, haloSection: { position: 'absolute', left: 0, top: 0, right: 0, bottom: 0, zIndex: 9999 }, halo: { width: '100%', height: '100%' }, titleSection: { alignItems: 'center', marginBottom: 15 }, titleText: { fontSize: 31, fontWeight: 'bold', color: '#fffecc', textShadowColor: '#a06939', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 2 }, mainSection: { flex: 1, paddingTop: 20 }, loadingBox: { flex: 1, justifyContent: 'center', alignItems: 'center' }, loadingText: { marginTop: 15, fontSize: 14, color: '#fff' }, cardList: { flex: 1 }, cardGrid: { flexDirection: 'row', flexWrap: 'wrap', paddingHorizontal: 15, justifyContent: 'flex-start' }, cardWrapper: { width: CARD_WIDTH, height: CARD_HEIGHT, marginHorizontal: 5, marginBottom: 15, position: 'relative' }, cardBack: { position: 'absolute', width: '100%', height: '100%', backfaceVisibility: 'hidden' }, cardBackImage: { width: '100%', height: '100%', borderRadius: 10 }, cardFront: { position: 'absolute', width: '100%', height: '100%', backfaceVisibility: 'hidden', borderRadius: 10, overflow: 'hidden' }, cardFrontStatic: { width: '100%', height: '100%', borderRadius: 10, overflow: 'hidden' }, cardFrontBg: { width: '100%', height: '100%', paddingTop: 15, borderRadius: 10, overflow: 'hidden' }, productImage: { width: '85%', height: '55%', alignSelf: 'center' }, borderImage: { position: 'absolute', left: 0, top: 0, width: '100%', height: '100%' }, cardInfo: { position: 'absolute', left: 0, right: 0, bottom: 7, paddingHorizontal: 10 }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }, levelText: { fontSize: 13, fontWeight: 'bold', color: '#fff' }, priceText: { fontSize: 12, fontWeight: 'bold', color: '#fff' }, exchangeRow: { marginBottom: 2 }, exchangeText: { fontSize: 10, color: '#fff', fontWeight: 'bold' }, nameText: { fontSize: 12, fontWeight: 'bold', color: '#fff' }, bottomSection: { paddingHorizontal: 20, paddingTop: 20 }, bottomBtns: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }, dhBtn: { backgroundColor: '#fff7e3', borderRadius: 20, paddingHorizontal: 18, paddingVertical: 8, marginRight: 10, alignItems: 'center' }, dhBtnText: { fontSize: 14, fontWeight: '500', color: '#000' }, dhBtnSubText: { fontSize: 10, color: '#735200' }, againBtn: { backgroundColor: '#fec433', borderRadius: 20, paddingHorizontal: 25, paddingVertical: 12 }, againBtnText: { fontSize: 14, fontWeight: '600', color: '#000' }, storeLink: { alignItems: 'center', marginTop: 12 }, storeLinkText: { fontSize: 12, color: '#fff' }, storeHighlight: { color: '#ff9600' }, rebateText: { textAlign: 'center', marginTop: 13, fontSize: 13, color: '#fff' }, rebateAmount: { color: '#ffeb3b', fontSize: 14, fontWeight: 'bold' }, animationSwitch: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginTop: 8 }, switchLabel: { fontSize: 12, color: '#dedede', marginRight: 10 }, switchBtn: { width: 44, height: 24, borderRadius: 12, backgroundColor: '#666', justifyContent: 'center', paddingHorizontal: 2 }, switchBtnActive: { backgroundColor: '#ff9600' }, switchThumb: { width: 20, height: 20, borderRadius: 10, backgroundColor: '#fff' }, switchThumbActive: { alignSelf: 'flex-end' }, skipBtn: { position: 'absolute', bottom: '10%', alignSelf: 'center', backgroundColor: 'rgba(0,0,0,0.4)', paddingHorizontal: 15, paddingVertical: 7, borderRadius: 15 }, skipText: { fontSize: 14, color: '#fff' }, });