import { Image } from 'expo-image'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Alert, Animated, Dimensions, ImageBackground, ScrollView, StatusBar, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Images } from '@/constants/images'; import { useAuth } from '@/contexts/AuthContext'; import { getBoxDetail, getWinRecords, poolIn, poolOut, previewOrder, unlockBox } from '@/services/award'; import { get } from '@/services/http'; import { CheckoutModal } from '../award-detail/components/CheckoutModal'; import { RuleModal } from '../award-detail/components/RuleModal'; import { BoxPopup, BoxPopupRef } from './components/BoxPopup'; import { DetailsPopup, DetailsPopupRef } from './components/DetailsPopup'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); interface PoolData { id: string; poolName: string; name?: string; cover: string; price: number; specialPrice?: number; bigBoxPrizes?: ProductItem[]; mediumBoxPrizes?: ProductItem[]; smallBoxPrizes?: ProductItem[]; activityGoods?: any[]; } interface ProductItem { id: string; name: string; cover: string; level: string; price?: number; } interface BoxData { number: string; leftQuantity: number; lastNumber: number; lock?: { locker: string; leftTime: number }; usedStat?: Record; } // 使用正确的 API 接口 const getBoxPoolDetail = async (poolId: string) => { const res = await get('/api/luck/treasure-box/pool-detail', { poolId }); return res.data; }; const getBoxHistory = async (poolId: string) => { const res = await get('/api/luck/treasure-box/box-history', { poolId }); return res.data; }; const getEmptyRunStatus = async (poolId: string) => { const res = await get('/api/luck/treasure-box/empty-run-status', { poolId }); return res.data; }; export default function BoxInBoxScreen() { const { poolId } = useLocalSearchParams<{ poolId: string }>(); const router = useRouter(); const insets = useSafeAreaInsets(); const { user } = useAuth(); const [loading, setLoading] = useState(true); const [data, setData] = useState(null); const [products, setProducts] = useState([]); const [activityGoods, setActivityGoods] = useState([]); const [boxHistory, setBoxHistory] = useState([]); const [boxHistoryInfo, setBoxHistoryInfo] = useState(null); const [box, setBox] = useState(null); const [boxNum, setBoxNum] = useState(''); const [currentIndex, setCurrentIndex] = useState(0); const [leftTime, setLeftTime] = useState(0); const [emptyRuns, setEmptyRuns] = useState(0); const [scrollTop, setScrollTop] = useState(0); const [tabIndex, setTabIndex] = useState(0); const [recordList, setRecordList] = useState([]); const checkoutRef = useRef(null); const ruleRef = useRef(null); const boxPopupRef = useRef(null); const detailsPopupRef = useRef(null); const floatAnim = useRef(new Animated.Value(0)).current; const timerRef = useRef | null>(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 loadData = useCallback(async () => { if (!poolId) return; setLoading(true); try { const detail = await getBoxPoolDetail(poolId); if (detail) { setData({ ...detail, name: detail.poolName, price: detail.price || detail.specialPrice || 0 }); setProducts(detail.bigBoxPrizes || []); setActivityGoods(detail.activityGoods || []); } } catch (error) { console.error('加载数据失败:', error); } setLoading(false); }, [poolId]); const loadBoxHistory = useCallback(async () => { if (!poolId) return; try { const res = await getBoxHistory(poolId); if (res && res.length > 0) { setBoxHistory(res); setBoxHistoryInfo(res[0]); } } catch {} }, [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 loadEmptyRuns = useCallback(async () => { if (!poolId) return; try { const res = await getEmptyRunStatus(poolId); if (res) setEmptyRuns(res.emptyRuns || 0); } catch {} }, [poolId]); // 加载中奖记录 const loadRecords = useCallback(async (boxNumber?: string) => { if (!poolId) return; try { const num = boxNumber || boxHistoryInfo?.boxNumber; if (!num) return; const res = await getWinRecords(poolId, num); if (res && res.records) { setRecordList(res.records); } } catch {} }, [poolId, boxHistoryInfo]); const refreshBox = useCallback(async () => { if (!poolId) return; try { const res = await getBoxHistory(poolId); if (res && res.length > 0) { setBoxHistory(res); setBoxHistoryInfo(res[0]); loadData(); loadBox(res[0].boxNumber); loadRecords(res[0].boxNumber); } loadEmptyRuns(); } catch {} }, [poolId, loadData, loadBox, loadEmptyRuns, loadRecords]); // 打开换盒弹窗 const openBoxPopup = useCallback(async () => { if (!poolId) return; try { const res = await getBoxHistory(poolId); if (res && res.length > 0) { boxPopupRef.current?.open(res); } } catch {} }, [poolId]); // 选择盒子 const handleSelectBox = useCallback((item: any) => { setBoxHistoryInfo(item); loadBox(item.boxNumber); loadEmptyRuns(); loadRecords(item.boxNumber); }, [loadBox, loadEmptyRuns, loadRecords]); // 打开商品详情弹窗 const handleShowDetails = useCallback((item: any) => { // 根据商品的 level 传入对应的奖品列表 let prizes = products; if (item.level === 'NESTED_BOX_MEDIUM') { prizes = data?.mediumBoxPrizes || products; } else if (item.level === 'NESTED_BOX_SMALL') { prizes = data?.smallBoxPrizes || products; } detailsPopupRef.current?.open(item, prizes); }, [products, data]); const handleBoxResult = (res: any) => { const map: Record = {}; if (res.usedStat) res.usedStat.forEach((item: any) => { map[item.spuId] = item; }); res.usedStat = map; setBox(res); setBoxNum(res.number); lockTimeStart(res); }; 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(); loadBoxHistory(); loadEmptyRuns(); loadRecords(); if (poolId) poolIn(poolId); return () => { if (poolId) poolOut(poolId); lockTimeEnd(); }; }, [poolId]); const handlePay = async (num: number) => { if (!poolId || !data || !box) { Alert.alert('提示', '请先选择盒子'); return; } try { const preview = await previewOrder(poolId, num, box.number); if (preview) checkoutRef.current?.show(num, preview, box.number); } catch (error) { console.error('预览订单失败:', error); } }; const handleSuccess = () => { setTimeout(() => { loadData(); loadBox(boxNum); loadEmptyRuns(); }, 500); }; const handleUnlock = async () => { if (!poolId || !boxNum) return; try { await unlockBox(poolId, boxNum); loadBox(boxNum); } catch {} }; 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 = { A: '超神款', B: '欧皇款', C: '隐藏款', D: '普通款', NESTED_BOX_GUARANTEED: '保底款' }; return map[level] || level; }; const getLevelBg = (level: string) => { const map: Record = { A: Images.box.detail.productItemA, B: Images.box.detail.productItemB, C: Images.box.detail.productItemC, D: Images.box.detail.productItemD, NESTED_BOX_GUARANTEED: Images.box.detail.productItemD, }; return map[level] || Images.box.detail.productItemD; }; const leftNum = box?.lock ? (leftTime / box.lock.leftTime) * 100 : 0; const headerBg = scrollTop > 0 ? '#333' : 'transparent'; if (loading) return ; if (!data) return ( 奖池不存在 router.back()}> 返回 ); const currentProduct = products[currentIndex]; return ( {/* 顶部导航 */} router.back()}> {'<'} {data.poolName || data.name} setScrollTop(e.nativeEvent.contentOffset.y)} scrollEventThrottle={16}> {/* 主商品展示区域 */} {currentProduct && ( <> {currentProduct.price && ¥{currentProduct.price}} {getLevelName(currentProduct.level)} {currentProduct.name} )} {currentIndex > 0 && ( )} {currentIndex < products.length - 1 && ( )} {/* 侧边按钮 */} ruleRef.current?.show()}> 规则 {box?.lock && user && box.lock.locker === (user.userId || user.id) && ( 解锁 )} router.push('/boxInBox/boxList' as any)}> 宝箱 refreshBox()}> 刷新 {/* 空车计数 */} 连续空车:{emptyRuns} {/* 锁定倒计时 */} {box?.lock && ( 剩余时间: {leftTime} )} {/* 箱子信息区域 */} {/* 标题栏 */} {boxHistoryInfo && ( <> 箱数: {boxHistoryInfo.boxNumber} /{boxHistory.length || '-'}箱 总数: {boxHistoryInfo.leftQuantity} /{boxHistoryInfo.quantity} )} 换箱 {/* Tab 切换 */} setTabIndex(0)}> 赏品预览 setTabIndex(1)}> 中奖记录 {/* 活动商品列表 */} {tabIndex === 0 && ( {activityGoods.map((item, index) => ( handleShowDetails(item)}> 概率:{((item.probability || 0) * 100).toFixed(2)}% 参考价:{item.price} {item.level === 'NESTED_BOX_GUARANTEED' ? 'D赏' : '全局赏'} {item.name} ))} )} {/* 中奖记录 */} {tabIndex === 1 && ( {recordList.length === 0 ? ( 暂无中奖记录 ) : ( recordList.map((item, index) => ( {item.nickname} {item.createTime} | 第{item.seatNumber}发 获得:{item.prizeName} {item.level === 'D' ? 'D赏' : '全局赏'} )) )} )} {/* 奖品列表 */} 奖品列表 {products.map((item, index) => ( {item.name} {getLevelName(item.level)} {item.price && ¥{item.price}} ))} {/* 底部购买栏 */} handlePay(1)} activeOpacity={0.8}> ×1 ); } 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: 280, justifyContent: 'center', alignItems: 'center' }, productImage: { width: 200, height: 280 }, priceText: { color: '#fff', fontSize: 16, fontWeight: 'bold', marginTop: -20 }, detailsBut: { width: 120, height: 45, justifyContent: 'center', alignItems: 'center', marginTop: -10 }, levelText: { fontSize: 14, color: '#FBC400', fontWeight: 'bold' }, 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 }, positionLock: { top: 300, left: 0 }, positionStore: { top: 256, right: 0 }, positionRefresh: { top: 300, right: 0 }, 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 }, 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 }, // 箱子信息区域样式 firstLastWrapper: { marginHorizontal: 10, marginBottom: 10, backgroundColor: '#fff', borderRadius: 8, overflow: 'hidden', borderWidth: 2, borderColor: '#000' }, firstLastTitle: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#ffc900', paddingHorizontal: 20, paddingVertical: 15, borderBottomWidth: 2, borderBottomColor: '#000' }, firstLastInfo: { flexDirection: 'row', alignItems: 'center' }, sizeInfo: { flexDirection: 'row', alignItems: 'center', marginRight: 15 }, sizeLabel: { fontSize: 11, color: '#fff' }, sizeValue: { fontSize: 16, fontWeight: 'bold', color: '#fff' }, changeBoxBtn: { backgroundColor: '#ff8c16', paddingHorizontal: 15, paddingVertical: 8, borderRadius: 4, borderWidth: 1, borderColor: '#333' }, changeBoxText: { fontSize: 11, color: '#fff' }, // Tab 切换样式 tabSection: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#E4E4E4' }, tabItem: { flex: 1, paddingVertical: 12, alignItems: 'center' }, tabItemActive: {}, tabText: { fontSize: 14, color: '#9E9E9E' }, tabTextActive: { color: '#ff8c16', fontWeight: 'bold' }, // 活动商品列表样式 activityGoodsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8 }, activityGoodsItem: { width: '33.33%', padding: 4, alignItems: 'center', marginBottom: 10 }, activityImageBox: { width: 100, height: 100, borderWidth: 2, borderColor: '#1A1A1A', borderRadius: 4, overflow: 'hidden', position: 'relative' }, activityImage: { width: '100%', height: '100%' }, probabilityBadge: { position: 'absolute', top: 0, left: 0, right: 0, backgroundColor: 'rgba(0,0,0,0.5)', paddingVertical: 2 }, probabilityText: { fontSize: 7, color: '#fff', textAlign: 'center' }, priceBadge: { position: 'absolute', bottom: 5, left: 5, right: 5, backgroundColor: 'rgba(0,0,0,0.5)', paddingVertical: 4, borderRadius: 2 }, priceTextSmall: { fontSize: 7, color: '#fff', textAlign: 'center' }, levelBadgeSmall: { marginTop: 5, paddingHorizontal: 10, paddingVertical: 3, borderRadius: 2 }, levelD: { backgroundColor: '#6340FF', borderWidth: 1, borderColor: '#A2BBFF' }, levelAll: { backgroundColor: '#A3E100', borderWidth: 1, borderColor: '#EAFFB1' }, levelBadgeText: { fontSize: 12, textAlign: 'center' }, activityName: { fontSize: 12, color: '#333', marginTop: 4, textAlign: 'center' }, // 空记录样式 emptyRecord: { padding: 40, alignItems: 'center' }, emptyRecordText: { fontSize: 14, color: '#999' }, // 中奖记录样式 recordScroll: { maxHeight: 220 }, recordItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 13, paddingHorizontal: 15, borderBottomWidth: 1, borderBottomColor: '#D8D8D8' }, recordLeft: { flex: 1 }, recordNickname: { fontSize: 12, fontWeight: 'bold', color: '#333' }, recordTime: { fontSize: 12, color: '#666', marginVertical: 3 }, recordPrize: { fontSize: 12, color: '#666' }, recordPrizeName: { fontWeight: '500', color: '#FF5100' }, recordLevelBadge: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 2 }, recordLevelText: { fontSize: 12, fontWeight: 'bold', color: '#fff' }, productGrid: { margin: 10, backgroundColor: 'rgba(0,0,0,0.3)', borderRadius: 15, padding: 15 }, gridTitle: { color: '#fff', fontSize: 16, fontWeight: 'bold', marginBottom: 15 }, gridContent: { flexDirection: 'row', flexWrap: 'wrap', marginHorizontal: -5 }, gridItem: { width: '33.33%', paddingHorizontal: 5, marginBottom: 10 }, gridItemBg: { width: '100%', aspectRatio: 0.75, padding: 8, alignItems: 'center' }, gridImageBox: { width: '100%', aspectRatio: 1, borderRadius: 5, overflow: 'hidden', backgroundColor: 'rgba(255,255,255,0.1)' }, gridImage: { width: '100%', height: '100%' }, gridName: { color: '#fff', fontSize: 10, marginTop: 5, textAlign: 'center', height: 26 }, gridLevel: { color: '#FBC400', fontSize: 9, marginTop: 2 }, gridPrice: { color: '#ff6600', fontSize: 10, marginTop: 2 }, bottomBar: { position: 'absolute', bottom: 0, left: 0, right: 0, height: 69, paddingHorizontal: 5 }, bottomBtns: { flexDirection: 'row', height: 64, alignItems: 'center', justifyContent: 'center' }, btnItemFull: { width: 200 }, btnBg: { width: '100%', height: 54, justifyContent: 'center', alignItems: 'center' }, btnText: { fontSize: 18, fontWeight: 'bold', color: '#fff' }, });