| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- import { Image } from 'expo-image';
- import { useRouter } from 'expo-router';
- import React, { useCallback, useEffect, useRef, useState } from 'react';
- import {
- Animated,
- Dimensions,
- ImageBackground,
- ScrollView,
- StatusBar,
- StyleSheet,
- Text,
- TouchableOpacity,
- View,
- } from 'react-native';
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
- import { get } from '@/services/http';
- const { width: SCREEN_WIDTH } = Dimensions.get('window');
- const CDN_BASE = 'https://cdn.acetoys.cn/kai_xin_ma_te/supermart';
- const catchDollImages = {
- bg: `${CDN_BASE}/common/commonBg.png`,
- dollBox: `${CDN_BASE}/welfare/qijiWelfareDollBox.png`,
- dollBall: `${CDN_BASE}/welfare/qijiWelfareDollBall.png`,
- dollOne: `${CDN_BASE}/welfare/qijiWelfareDollOne.png`,
- dollFive: `${CDN_BASE}/welfare/qijiWelfareDollFive.png`,
- recordBg: `${CDN_BASE}/welfare/qijiWelfareRecordBg.png`,
- ruleBtn: `${CDN_BASE}/welfare/catchDollRule.png`,
- molibiBoxBtn: `${CDN_BASE}/welfare/molibiBoxBtn.png`,
- opening: `${CDN_BASE}/welfare/opening.png`,
- dollBi1: `${CDN_BASE}/welfare/qijiWelfareDollBi1.png`,
- dollBi5: `${CDN_BASE}/welfare/qijiWelfareDollBi5.png`,
- };
- interface GoodsItem {
- id: string;
- cover: string;
- name: string;
- }
- export default function CatchDollScreen() {
- const router = useRouter();
- const insets = useSafeAreaInsets();
- const [goodsList, setGoodsList] = useState<GoodsItem[]>([]);
- const [molibi, setMolibi] = useState(0);
- const [balls] = useState(() =>
- Array.from({ length: 50 }, (_, i) => ({
- id: i,
- bottom: Math.random() * 80,
- left: Math.random() * 172,
- }))
- );
- // 动画
- const ballAnimations = useRef(balls.map(() => new Animated.ValueXY({ x: 0, y: 0 }))).current;
- const ballRotations = useRef(balls.map(() => new Animated.Value(0))).current;
- const loadData = useCallback(async () => {
- try {
- const res = await get('/api/luckWheel/detail');
- if (res.data) {
- setGoodsList(res.data.luckWheelGoodsList || []);
- }
- } catch (error) {
- console.error('加载扭蛋机数据失败:', error);
- }
- }, []);
- const loadMolibi = useCallback(async () => {
- try {
- const res = await get('/api/wallet/info', { type: 'MAGIC_POWER_COIN' });
- if (res.data) {
- setMolibi(res.data.balance || 0);
- }
- } catch (error) {
- console.error('加载源力币失败:', error);
- }
- }, []);
- useEffect(() => {
- loadData();
- loadMolibi();
- }, [loadData, loadMolibi]);
- const animateBalls = () => {
- const animations = balls.map((_, i) => {
- const randomX = (Math.random() - 0.5) * 50;
- const randomY = (Math.random() - 0.5) * 100;
- const randomRotate = Math.random() * 360;
- return Animated.parallel([
- Animated.sequence([
- Animated.timing(ballAnimations[i], {
- toValue: { x: randomX, y: randomY },
- duration: 200,
- useNativeDriver: true,
- }),
- Animated.timing(ballAnimations[i], {
- toValue: { x: randomX * 0.5, y: randomY * 0.5 },
- duration: 300,
- useNativeDriver: true,
- }),
- Animated.timing(ballAnimations[i], {
- toValue: { x: 0, y: 0 },
- duration: 300,
- useNativeDriver: true,
- }),
- ]),
- Animated.timing(ballRotations[i], {
- toValue: randomRotate,
- duration: 800,
- useNativeDriver: true,
- }),
- ]);
- });
- Animated.parallel(animations).start(() => {
- ballRotations.forEach((rot) => rot.setValue(0));
- });
- };
- const handlePress = (count: number) => {
- if (molibi < count) {
- // TODO: 显示源力币不足弹窗
- return;
- }
- animateBalls();
- // TODO: 调用抽奖接口
- };
- return (
- <View style={styles.container}>
- <StatusBar barStyle="light-content" />
- <ImageBackground source={{ uri: catchDollImages.bg }} style={styles.background} resizeMode="cover">
- {/* 固定头部 */}
- <View style={[styles.header, { paddingTop: insets.top }]}>
- <TouchableOpacity style={styles.backBtn} onPress={() => router.back()}>
- <Text style={styles.backText}>←</Text>
- </TouchableOpacity>
- <Text style={styles.title}>扭蛋机</Text>
- <View style={styles.placeholder} />
- </View>
- {/* 规则按钮 - 固定位置 */}
- <TouchableOpacity style={[styles.ruleBtn, { top: insets.top + 200 }]}>
- <Image source={{ uri: catchDollImages.ruleBtn }} style={styles.ruleBtnImg} contentFit="contain" />
- </TouchableOpacity>
- {/* 中奖记录按钮 - 固定位置 */}
- <TouchableOpacity style={[styles.recordBtn, { top: insets.top + 120 }]}>
- <ImageBackground source={{ uri: catchDollImages.recordBg }} style={styles.recordBtnBg} resizeMode="contain">
- <Text style={styles.recordText}>中</Text>
- <Text style={styles.recordText}>奖</Text>
- <Text style={styles.recordText}>记</Text>
- <Text style={styles.recordText}>录</Text>
- </ImageBackground>
- </TouchableOpacity>
- <ScrollView style={styles.scrollView} showsVerticalScrollIndicator={false}>
- <View style={{ height: insets.top + 36 }} />
- {/* 扭蛋机主体 */}
- <View style={styles.machineWrapper}>
- <ImageBackground source={{ uri: catchDollImages.dollBox }} style={styles.machineImg} resizeMode="contain">
- {/* 奖品列表 */}
- <View style={styles.goodsListWrapper}>
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.goodsScroll}>
- {goodsList.map((item, index) => (
- <View key={item.id || index} style={styles.goodsItem}>
- <Image source={{ uri: item.cover }} style={styles.goodsImg} contentFit="cover" />
- </View>
- ))}
- </ScrollView>
- </View>
- {/* 扭蛋球区域 */}
- <View style={styles.ballsBox}>
- {balls.map((ball, i) => (
- <Animated.View
- key={ball.id}
- style={[
- styles.ball,
- {
- bottom: ball.bottom,
- left: ball.left,
- transform: [
- { translateX: ballAnimations[i].x },
- { translateY: ballAnimations[i].y },
- {
- rotate: ballRotations[i].interpolate({
- inputRange: [0, 360],
- outputRange: ['0deg', '360deg'],
- }),
- },
- ],
- },
- ]}
- >
- <Image source={{ uri: catchDollImages.dollBall }} style={styles.ballImg} contentFit="contain" />
- </Animated.View>
- ))}
- </View>
- {/* 源力币信息框 */}
- <View style={styles.molibiBox}>
- <Text style={styles.molibiLabel}>
- 源力币:<Text style={styles.molibiNum}>{molibi}</Text> 个
- </Text>
- <TouchableOpacity style={styles.molibiBtn} onPress={() => router.push('/award' as any)}>
- <Image source={{ uri: catchDollImages.molibiBoxBtn }} style={styles.molibiBtnImg} contentFit="contain" />
- </TouchableOpacity>
- </View>
- {/* 开口动画区域 */}
- <View style={styles.openingBox}>
- <Image source={{ uri: catchDollImages.opening }} style={styles.openingImg} contentFit="contain" />
- </View>
- {/* 扭蛋把手 */}
- <TouchableOpacity style={[styles.switchBox, styles.switchBox1]} onPress={() => handlePress(1)}>
- <Image source={{ uri: catchDollImages.dollBi1 }} style={styles.switchImg} contentFit="contain" />
- </TouchableOpacity>
- <TouchableOpacity style={[styles.switchBox, styles.switchBox5]} onPress={() => handlePress(5)}>
- <Image source={{ uri: catchDollImages.dollBi5 }} style={styles.switchImg} contentFit="contain" />
- </TouchableOpacity>
- </ImageBackground>
- </View>
- {/* 底部按钮 */}
- <View style={styles.bottomBtns}>
- <TouchableOpacity style={styles.submitBtn} onPress={() => handlePress(1)}>
- <Image source={{ uri: catchDollImages.dollOne }} style={styles.submitBtnImg} contentFit="contain" />
- </TouchableOpacity>
- <TouchableOpacity style={styles.submitBtn} onPress={() => handlePress(5)}>
- <Image source={{ uri: catchDollImages.dollFive }} style={styles.submitBtnImg} contentFit="contain" />
- </TouchableOpacity>
- </View>
- <View style={{ height: 100 }} />
- </ScrollView>
- </ImageBackground>
- </View>
- );
- }
- const styles = StyleSheet.create({
- container: { flex: 1, backgroundColor: '#1a1a2e' },
- background: { flex: 1 },
- 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: 20 },
- title: { color: '#fff', fontSize: 15, fontWeight: 'bold' },
- placeholder: { width: 40 },
- scrollView: { flex: 1 },
- // 规则按钮
- ruleBtn: { position: 'absolute', left: 13, zIndex: 99 },
- ruleBtnImg: { width: 62, height: 20 },
- // 中奖记录按钮
- recordBtn: { position: 'absolute', right: 0, zIndex: 99 },
- recordBtnBg: {
- width: 26,
- height: 70,
- justifyContent: 'center',
- alignItems: 'center',
- paddingVertical: 9,
- },
- recordText: {
- color: '#fff',
- fontSize: 12,
- fontWeight: 'bold',
- textShadowColor: '#6C3200',
- textShadowOffset: { width: 1, height: 1 },
- textShadowRadius: 1,
- },
- // 扭蛋机主体
- machineWrapper: {
- width: SCREEN_WIDTH,
- alignItems: 'center',
- },
- machineImg: {
- width: SCREEN_WIDTH,
- height: SCREEN_WIDTH * 1.88,
- position: 'relative',
- },
- // 奖品列表
- goodsListWrapper: {
- position: 'absolute',
- top: SCREEN_WIDTH * 0.29,
- left: 0,
- right: 0,
- alignItems: 'center',
- },
- goodsScroll: {
- paddingHorizontal: SCREEN_WIDTH * 0.18,
- },
- goodsItem: {
- width: 46,
- height: 46,
- borderRadius: 4,
- backgroundColor: '#ADAEF6',
- borderWidth: 2.5,
- borderColor: '#8687E4',
- marginRight: 5,
- overflow: 'hidden',
- },
- goodsImg: { width: '100%', height: '100%' },
- // 扭蛋球区域
- ballsBox: {
- position: 'absolute',
- top: SCREEN_WIDTH * 0.33,
- left: SCREEN_WIDTH * 0.115,
- width: SCREEN_WIDTH * 0.73,
- height: SCREEN_WIDTH * 0.74,
- overflow: 'hidden',
- },
- ball: {
- position: 'absolute',
- width: 66,
- height: 66,
- },
- ballImg: { width: '100%', height: '100%' },
- // 源力币信息框
- molibiBox: {
- position: 'absolute',
- top: SCREEN_WIDTH * 1.24,
- left: 42,
- width: 120,
- height: 67,
- backgroundColor: '#1E1C5B',
- borderRadius: 8,
- paddingTop: 5,
- alignItems: 'center',
- },
- molibiLabel: {
- color: '#7982CB',
- fontSize: 12,
- },
- molibiNum: {
- color: '#FF8400',
- fontSize: 18,
- fontWeight: 'bold',
- },
- molibiBtn: {
- marginTop: 5,
- },
- molibiBtnImg: {
- width: 105,
- height: 30,
- },
- // 开口动画区域
- openingBox: {
- position: 'absolute',
- top: SCREEN_WIDTH * 1.21,
- right: 42,
- width: 133,
- height: 82,
- },
- openingImg: { width: '100%', height: '100%' },
- // 扭蛋把手
- switchBox: {
- position: 'absolute',
- top: SCREEN_WIDTH * 1.49,
- width: 65,
- height: 65,
- },
- switchBox1: { left: 42 },
- switchBox5: { right: 42 },
- switchImg: { width: '100%', height: '100%' },
- // 底部按钮
- bottomBtns: {
- flexDirection: 'row',
- justifyContent: 'center',
- marginTop: -SCREEN_WIDTH * 0.24,
- paddingHorizontal: 20,
- },
- submitBtn: {
- marginHorizontal: SCREEN_WIDTH * 0.08,
- },
- submitBtnImg: {
- width: 73,
- height: 50,
- },
- });
|