import { Image } from 'expo-image'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { ActivityIndicator, 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, 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[]; } interface ProductItem { id: string; name: string; cover: string; level: string; probability: number; price?: number; quantity?: number; } 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; prizeList?: any[]; } 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(null); const [products, setProducts] = useState([]); const [box, setBox] = useState(null); const [boxNum, setBoxNum] = useState(''); const [currentIndex, setCurrentIndex] = useState(0); const [leftTime, setLeftTime] = useState(0); const [probability, setProbability] = useState([]); const [scrollTop, setScrollTop] = useState(0); const checkoutRef = useRef(null); const recordRef = useRef(null); const ruleRef = useRef(null); const numChooseRef = useRef(null); const boxChooseRef = 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 getPoolDetail(poolId); if (detail) { setData(detail); setProducts(detail.luckGoodsList || []); } } catch (error) { console.error('加载数据失败:', error); } 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 = {}; 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); } }; 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(); }; // 选择盒子后的回调 const handleChooseBox = (boxNumber: string) => { loadBox(boxNumber); }; 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 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 = { A: '超神款', B: '欧皇款', C: '隐藏款', D: '普通款' }; return map[level] || level; }; 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 getProbability = (item: ProductItem) => { if (!box || box.leftQuantity <= 0) return '0'; const leftNum = getLeftNum(item); return ((leftNum / box.leftQuantity) * 100).toFixed(2); }; 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.name} setScrollTop(e.nativeEvent.contentOffset.y)} scrollEventThrottle={16} > {/* 主商品展示区域 */} {/* 商品轮播区域 */} {currentProduct && ( <> {/* 等级信息 */} {getLevelName(currentProduct.level)} ({getProbability(currentProduct)}%) {/* 商品名称 */} {currentProduct.name} )} {/* 左右切换按钮 */} {currentIndex > 0 && ( )} {currentIndex < products.length - 1 && ( )} {/* 左侧装饰 */} {/* 右侧装饰 */} {/* 底部装饰文字 */} {/* 侧边按钮 - 规则 */} ruleRef.current?.show()}> 规则 {/* 侧边按钮 - 记录 */} recordRef.current?.show()}> 记录 {/* 侧边按钮 - 锁定/解锁 */} {box && !box.lock && ( 锁定 )} {box?.lock && user && box.lock.locker === (user.userId || user.id) && ( 解锁 )} {/* 侧边按钮 - 仓库 */} router.push('/store' as any)}> 仓库 {/* 侧边按钮 - 刷新 */} loadBox(boxNum)}> 刷新 {/* 锁定倒计时 */} {box?.lock && ( 剩余时间: {leftTime} )} {/* 标题 */} {/* 一番赏盒子信息区域 */} {/* 当前盒子剩余数量 */} {box && ( 当前盒子剩余: {box.leftQuantity} /{box.quantity || '-'}发 )} {/* 抢先赏/最终赏/全局赏展示 */} {box?.prizeList && box.prizeList.length > 0 && ( {box.prizeList.map((item: any, index: number) => ( {item.level === 'FIRST' ? '抢先赏' : item.level === 'LAST' ? '最终赏' : '全局赏'} ))} )} {/* 换盒控制区 */} 上一盒 换盒({boxNum || '-'}/{box?.lastNumber || '-'}) = box.lastNumber}> = box.lastNumber) && styles.disabled]}>下一盒 {/* 商品列表 */} {/* 底部购买栏 */} handlePay(1)} activeOpacity={0.8}> 购买一盒 (¥{data.specialPrice || data.price}) handlePay(5)} activeOpacity={0.8}> 购买五盒 (¥{data.specialPriceFive || data.price * 5}) 购买多盒 ); } 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' }, });