|
|
@@ -0,0 +1,799 @@
|
|
|
+import styles from "./styles.module.scss";
|
|
|
+// import d_bg from "@assets/holidaysGift/d.png";
|
|
|
+import img_close from "@assets/holidaysGift/close.png";
|
|
|
+import img_theme from "@assets/holidaysAct/theme.png";
|
|
|
+import icon_cash from "@assets/holidaysGift/cash.png";
|
|
|
+import icon_gift from "@assets/holidaysGift/gift.png";
|
|
|
+import icon_clock from "@assets/holidaysAct/clock.png";
|
|
|
+import icon_gift2 from "@assets/holidaysAct/gift.png";
|
|
|
+import giftLock from "@assets/holidaysAct/giftLock.png";
|
|
|
+import giftOpen from "@assets/holidaysAct/giftOpen.png";
|
|
|
+import gold from "@assets/holidaysAct/gold.png";
|
|
|
+import detailGift from "@assets/holidaysAct/detailGift.png";
|
|
|
+import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
|
+import { useDispatch, useSelector } from "react-redux";
|
|
|
+import { useNavigate } from "react-router";
|
|
|
+import { Fade, Grow, Modal } from "@mui/material";
|
|
|
+import symbol from "@assets/symbol-defs.svg";
|
|
|
+import { getHolidaysActInfo, getUserInfo, getUserVip, updateHolidaysActSpinTimes } from "@features/mainSlice";
|
|
|
+import { localStorageGet, localStorageSet } from "@utils/helpers";
|
|
|
+import { toast } from "react-toastify";
|
|
|
+import { reqGameInfo, reqHolidaysActSpin } from "@features/api";
|
|
|
+import usePaymentActions from "@hooks/usePaymentActions";
|
|
|
+
|
|
|
+const i18n = global.i18n;
|
|
|
+const WHolidaysAct = () => {
|
|
|
+ const [open, setOpen] = useState(false);
|
|
|
+ const navigate = useNavigate();
|
|
|
+ const dispatch = useDispatch();
|
|
|
+ const [isLock, setIsLock] = useState(false);
|
|
|
+ const info = useSelector(getHolidaysActInfo);
|
|
|
+ let { end_time, free_times, status, user, slots, recharge_rules } = info;
|
|
|
+ // console.log("info", info);
|
|
|
+ const [spinTimes, setSpinTimes] = useState(user?.left_times);
|
|
|
+ useEffect(() => {
|
|
|
+ if (user?.left_times !== undefined) {
|
|
|
+ setSpinTimes(user.left_times);
|
|
|
+ }
|
|
|
+ }, [user]);
|
|
|
+
|
|
|
+ // 新增状态管理
|
|
|
+ const [itemsState, setItemsState] = useState(
|
|
|
+ Array(9).fill({
|
|
|
+ isSelected: false,
|
|
|
+ isOpened: false,
|
|
|
+ showReward: false,
|
|
|
+ showMask: false,
|
|
|
+ isAnimating: false,
|
|
|
+ rotationAngle: 0
|
|
|
+ })
|
|
|
+ );
|
|
|
+ const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
|
+ const [isAnimationRunning, setIsAnimationRunning] = useState(false);
|
|
|
+ const [goldFlying, setGoldFlying] = useState(false);
|
|
|
+ const animationRef = useRef(null);
|
|
|
+ const coinRefs = useRef([]);
|
|
|
+
|
|
|
+ const userInfo = useSelector(getUserInfo);
|
|
|
+ // console.error("userVip", userInfo?.vip);
|
|
|
+
|
|
|
+ const [rewardData, setRewardData] = useState([]);
|
|
|
+
|
|
|
+
|
|
|
+ const statusRef = useRef(status);
|
|
|
+ useEffect(() => {
|
|
|
+ statusRef.current = status;
|
|
|
+ }, [status]);
|
|
|
+
|
|
|
+ const show = () => {
|
|
|
+ if (statusRef.current === 0) return global.openNextPopup();;
|
|
|
+ setOpen(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const hide = () => {
|
|
|
+ setOpen(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ const onClose = () => {
|
|
|
+ hide();
|
|
|
+ global.openNextPopup();
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const holidaysAct = {
|
|
|
+ show,
|
|
|
+ hide
|
|
|
+ };
|
|
|
+ global.holidaysAct = holidaysAct;
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const onCheckClose = () => {
|
|
|
+ onClose();
|
|
|
+ };
|
|
|
+
|
|
|
+ const { startPayingOrder } = usePaymentActions();
|
|
|
+
|
|
|
+ const handleBuyClick = (rule) => {
|
|
|
+ setOpen(false);
|
|
|
+ let payload = {
|
|
|
+ price: rule.amount,
|
|
|
+ goodsName: "",
|
|
|
+ payType: "",
|
|
|
+ GiftsID: rule.gear.gift_id,
|
|
|
+ gear: rule.gear.gear
|
|
|
+ };
|
|
|
+
|
|
|
+ startPayingOrder(payload);
|
|
|
+ localStorageSet("payHolidaysAct", true);
|
|
|
+ };
|
|
|
+
|
|
|
+ const resetAllItems = () => {
|
|
|
+ setItemsState(Array(9).fill({
|
|
|
+ isSelected: false,
|
|
|
+ isOpened: false,
|
|
|
+ showReward: false,
|
|
|
+ showMask: true,
|
|
|
+ isAnimating: false,
|
|
|
+ rotationAngle: 0
|
|
|
+ }));
|
|
|
+ setSelectedIndex(-1);
|
|
|
+ setIsAnimationRunning(false);
|
|
|
+ setGoldFlying(false);
|
|
|
+
|
|
|
+ if (animationRef.current) {
|
|
|
+ clearInterval(animationRef.current);
|
|
|
+ animationRef.current = null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const flyCoinAnimation = (startX, startY, endX, endY, reward) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ const coinCount = 3; // 3个金币
|
|
|
+ let completed = 0;
|
|
|
+ // 是否已显示金额文本
|
|
|
+ let amountShown = false;
|
|
|
+
|
|
|
+ for (let i = 0; i < coinCount; i++) {
|
|
|
+ setTimeout(() => {
|
|
|
+ const coin = document.createElement('img');
|
|
|
+ coin.src = gold;
|
|
|
+ coin.className = styles.flyingCoin;
|
|
|
+
|
|
|
+ coin.style.left = `${startX}px`;
|
|
|
+ coin.style.top = `${startY}px`;
|
|
|
+ coin.style.transform = 'translate(0, 0) scale(1)';
|
|
|
+ coin.style.opacity = '1';
|
|
|
+
|
|
|
+ document.body.appendChild(coin);
|
|
|
+
|
|
|
+ // 强制重排
|
|
|
+ coin.getBoundingClientRect();
|
|
|
+
|
|
|
+ // 设置transition
|
|
|
+ coin.style.transition = 'all 1.2s ease-out, opacity 1s ease-in 0.2s';
|
|
|
+
|
|
|
+ // 最终位置
|
|
|
+ coin.style.transform = `translate(${endX - startX}px, ${endY - startY}px) scale(0.5)`;
|
|
|
+ // opacity延迟淡出
|
|
|
+ coin.style.opacity = '0';
|
|
|
+
|
|
|
+
|
|
|
+ // 如果是第一个金币,在目标位置显示金额文本
|
|
|
+ if (i === 0) {
|
|
|
+ setTimeout(() => {
|
|
|
+ if (!amountShown) {
|
|
|
+ showAmountText(endX, endY, '+' + reward);
|
|
|
+ amountShown = true;
|
|
|
+ dispatch(reqGameInfo());
|
|
|
+ }
|
|
|
+ }, 1200); // 金币到达后显示
|
|
|
+ }
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ document.body.removeChild(coin);
|
|
|
+ completed++;
|
|
|
+ if (completed === coinCount) {
|
|
|
+ // resolve();
|
|
|
+ // 等金币都移除后再移除金额文本
|
|
|
+ removeAmountText();
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ }, 1500);
|
|
|
+
|
|
|
+ }, i * 150); // 金币间隔150ms
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ // 显示金额文本
|
|
|
+ const showAmountText = (x, y, text) => {
|
|
|
+ const amountText = document.createElement('div');
|
|
|
+ amountText.className = 'amount-text'; // 需要对应的CSS
|
|
|
+ amountText.textContent = text;
|
|
|
+ amountText.style.position = 'fixed';
|
|
|
+ amountText.style.left = `${x}px`;
|
|
|
+ amountText.style.top = `${y + 10}px`;
|
|
|
+ amountText.style.transform = 'translate(-50%, -50%)';
|
|
|
+ amountText.style.color = '#FFD700';
|
|
|
+ amountText.style.fontSize = '20px';
|
|
|
+ amountText.style.fontWeight = 'bold';
|
|
|
+ amountText.style.textShadow = '0 0 5px rgba(255,215,0,0.8)';
|
|
|
+ amountText.style.zIndex = '10001';
|
|
|
+ amountText.style.opacity = '0';
|
|
|
+ amountText.style.transition = 'all 0.2s ease';
|
|
|
+
|
|
|
+ document.body.appendChild(amountText);
|
|
|
+
|
|
|
+ // 触发动画
|
|
|
+ setTimeout(() => {
|
|
|
+ amountText.style.opacity = '1';
|
|
|
+ // amountText.style.transform = 'translate(-50%, -60px)';
|
|
|
+ }, 10);
|
|
|
+
|
|
|
+ return amountText;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 移除金额文本
|
|
|
+ const removeAmountText = () => {
|
|
|
+ const text = document.querySelector('.amount-text');
|
|
|
+ if (text) {
|
|
|
+ text.style.opacity = '0';
|
|
|
+ setTimeout(() => {
|
|
|
+ if (text.parentNode) {
|
|
|
+ text.parentNode.removeChild(text);
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleClickItem = async (event, index) => {
|
|
|
+ // 如果动画正在进行中,阻止新的点击
|
|
|
+ if (isAnimationRunning) return;
|
|
|
+
|
|
|
+ // 如果已经有选中的项目,点击任意一项重置
|
|
|
+ if (selectedIndex !== -1) {
|
|
|
+ resetAllItems();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!user) return;
|
|
|
+ if (spinTimes <= 0) {
|
|
|
+ toast.error("Not enough tries! Buy a pack gift", { toastId: "act_pack" });
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ // dispatch(updateHolidaysActSpinTimes(user.left_times - 1));
|
|
|
+ setSpinTimes(spinTimes - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ let res = await dispatch(reqHolidaysActSpin()).unwrap();
|
|
|
+ // let res = {
|
|
|
+ // "data": {
|
|
|
+ // "slot_index": 1,
|
|
|
+ // "reward": 0.5,
|
|
|
+ // "left_times": 0
|
|
|
+ // },
|
|
|
+ // "result": "",
|
|
|
+ // "msg": "success",
|
|
|
+ // "code": 200
|
|
|
+ // };
|
|
|
+
|
|
|
+ console.error("res", res);
|
|
|
+ if (!res || res.code !== 200) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let { reward, slot_index, left_times } = res.data;
|
|
|
+ let displayReward = [...slots];
|
|
|
+ // 1. 创建不包含目标元素的新数组
|
|
|
+ const otherElements = displayReward.filter((_, i) => i !== slot_index);
|
|
|
+
|
|
|
+ // 2. Fisher-Yates 洗牌算法打乱
|
|
|
+ for (let i = otherElements.length - 1; i > 0; i--) {
|
|
|
+ const j = Math.floor(Math.random() * (i + 1));
|
|
|
+ [otherElements[i], otherElements[j]] = [otherElements[j], otherElements[i]];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 插入目标元素
|
|
|
+ const newArray = [...otherElements.slice(0, index), displayReward[slot_index], ...otherElements.slice(index)];
|
|
|
+ displayReward = newArray;
|
|
|
+ setRewardData(displayReward);
|
|
|
+
|
|
|
+
|
|
|
+ setIsAnimationRunning(true);
|
|
|
+ setSelectedIndex(index);
|
|
|
+
|
|
|
+ // 1. 打开其他item遮罩
|
|
|
+ setItemsState(prev => prev.map((item, idx) => ({
|
|
|
+ ...item,
|
|
|
+ showMask: idx === index ? false : true
|
|
|
+ })));
|
|
|
+
|
|
|
+ // 2. 被选中的item旋转动画
|
|
|
+ let rotationCount = 0;
|
|
|
+ const maxRotations = 5;
|
|
|
+
|
|
|
+ await new Promise((resolve) => {
|
|
|
+ let swingCount = 0; // 完整的摇摆次数
|
|
|
+ let isForward = true; // 当前是向右旋转还是回正
|
|
|
+
|
|
|
+ animationRef.current = setInterval(() => {
|
|
|
+ setItemsState(prev => prev.map((item, idx) => {
|
|
|
+ if (idx === index) {
|
|
|
+ if (isForward) {
|
|
|
+ // 向右旋转10度
|
|
|
+ isForward = false;
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ rotationAngle: 10,
|
|
|
+ isAnimating: true
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // 回正到0度
|
|
|
+ isForward = true;
|
|
|
+ swingCount++; // 完成一次完整摇摆
|
|
|
+
|
|
|
+ // 3. 第3次摇摆结束时打开item
|
|
|
+ if (swingCount === 3) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ rotationAngle: 0,
|
|
|
+ isOpened: true,
|
|
|
+ isAnimating: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 第5次摇摆结束时
|
|
|
+ if (swingCount === 5) {
|
|
|
+ // 先回正到0度
|
|
|
+ const newState = {
|
|
|
+ ...item,
|
|
|
+ rotationAngle: 0,
|
|
|
+ isAnimating: false
|
|
|
+ };
|
|
|
+
|
|
|
+ clearInterval(animationRef.current);
|
|
|
+ animationRef.current = null;
|
|
|
+
|
|
|
+ // 等待旋转完全停止后再显示金额
|
|
|
+ setTimeout(async () => {
|
|
|
+ // 延迟300ms再显示金额
|
|
|
+ await new Promise(res => setTimeout(res, 200));
|
|
|
+
|
|
|
+ // 现在显示金额
|
|
|
+ setItemsState(prev => prev.map((item2, idx2) => {
|
|
|
+ if (idx2 === index) {
|
|
|
+ return {
|
|
|
+ ...item2,
|
|
|
+ showReward: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item2;
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 等待金额显示动画完成
|
|
|
+ await new Promise(res => setTimeout(res, 300));
|
|
|
+
|
|
|
+ // 金币飞行动画...
|
|
|
+ if (coinRefs.current[index]) {
|
|
|
+ const getBodyCenter = (el) => {
|
|
|
+ const bodyRect = document.body.getBoundingClientRect();
|
|
|
+ if (!el) return { x: bodyRect.width / 1.5, y: 50 };
|
|
|
+
|
|
|
+ const rect = el.getBoundingClientRect();
|
|
|
+
|
|
|
+ // 相对于body的坐标
|
|
|
+ const relativeX = rect.left - bodyRect.left + (rect.width / 2);
|
|
|
+ const relativeY = rect.top - bodyRect.top + (rect.height / 2);
|
|
|
+
|
|
|
+ return { x: relativeX, y: relativeY };
|
|
|
+ };
|
|
|
+
|
|
|
+ const itemCenter = getBodyCenter(coinRefs.current[index]);
|
|
|
+ // userInfo?.vip
|
|
|
+ let targetCenter = getBodyCenter(document.getElementById('userInsureScore'));
|
|
|
+ if (userInfo?.vip > 0) targetCenter = getBodyCenter(document.getElementById('userScore'));
|
|
|
+ const startX = itemCenter.x - 10;
|
|
|
+ const startY = itemCenter.y - 10;
|
|
|
+ const endX = targetCenter.x - 10;
|
|
|
+ const endY = targetCenter.y - 10;
|
|
|
+
|
|
|
+ setGoldFlying(true);
|
|
|
+ await flyCoinAnimation(startX, startY, endX, endY, reward);
|
|
|
+ setGoldFlying(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 其他item渐显
|
|
|
+ setTimeout(() => {
|
|
|
+ setItemsState(prev => prev.map((item2, idx3) => {
|
|
|
+ if (idx3 !== index) {
|
|
|
+ return {
|
|
|
+ ...item2,
|
|
|
+ showMask: false
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item2;
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 延迟500ms显示其他item的金额
|
|
|
+ setItemsState(prev => prev.map((item2, idx3) => {
|
|
|
+ if (idx3 !== index) {
|
|
|
+ return {
|
|
|
+ ...item2,
|
|
|
+ showReward: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item2;
|
|
|
+ }));
|
|
|
+
|
|
|
+ setIsAnimationRunning(false);
|
|
|
+ resolve();
|
|
|
+ }, 200);
|
|
|
+ }, 100); // 等待旋转动画完成
|
|
|
+
|
|
|
+ return newState;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ rotationAngle: 0,
|
|
|
+ isAnimating: true
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ }));
|
|
|
+ }, 100); // 改为100ms切换一次
|
|
|
+ });
|
|
|
+
|
|
|
+ dispatch(updateHolidaysActSpinTimes({ left_times }));
|
|
|
+ };
|
|
|
+
|
|
|
+ const getAdaptScale = () => {
|
|
|
+ // 设计稿宽高比(以iPhone X为例:375/812 ≈ 0.462)
|
|
|
+ const DESIGN_WIDTH = 375;
|
|
|
+ const DESIGN_HEIGHT = 812;
|
|
|
+ const designRatio = DESIGN_WIDTH / DESIGN_HEIGHT; // 0.462
|
|
|
+
|
|
|
+ // 获取当前视口尺寸
|
|
|
+ const bodyRect = document.body.getBoundingClientRect();
|
|
|
+ const currentWidth = bodyRect.width;
|
|
|
+ const currentHeight = bodyRect.height;
|
|
|
+ const currentRatio = currentWidth / currentHeight;
|
|
|
+
|
|
|
+ // console.log(`当前: ${currentWidth}x${currentHeight}, 比例: ${currentRatio.toFixed(3)}, 设计比例: ${designRatio.toFixed(3)}`);
|
|
|
+
|
|
|
+ // 判断是宽屏还是窄屏
|
|
|
+ const isWider = currentRatio > designRatio;
|
|
|
+ const ratioDiff = Math.abs(currentRatio - designRatio);
|
|
|
+
|
|
|
+ // 针对长页面(高度较大)的特殊处理
|
|
|
+ if (currentHeight > 1200) { // 如果页面非常高
|
|
|
+ // console.log("检测到长页面,使用高度适配");
|
|
|
+ // 根据高度计算缩放
|
|
|
+ const heightScale = currentHeight / DESIGN_HEIGHT;
|
|
|
+ return Math.min(heightScale, 1); // 不超过原始大小
|
|
|
+ }
|
|
|
+
|
|
|
+ // 正常比例适配逻辑
|
|
|
+ if (isWider) {
|
|
|
+ // 当前屏幕比设计稿宽(矮胖屏)
|
|
|
+ if (ratioDiff <= 0.05) {
|
|
|
+ return 0.95;
|
|
|
+ } else if (ratioDiff <= 0.1) {
|
|
|
+ return 0.9;
|
|
|
+ } else if (ratioDiff <= 0.15) {
|
|
|
+ return 0.85;
|
|
|
+ } else if (ratioDiff <= 0.2) {
|
|
|
+ return 0.8;
|
|
|
+ } else {
|
|
|
+ return 0.75; // 极端宽屏
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 当前屏幕比设计稿窄(瘦高屏)
|
|
|
+ if (ratioDiff <= 0.05) {
|
|
|
+ return 1;
|
|
|
+ } else if (ratioDiff <= 0.1) {
|
|
|
+ return 0.95;
|
|
|
+ } else if (ratioDiff <= 0.15) {
|
|
|
+ return 0.9;
|
|
|
+ } else {
|
|
|
+ return 0.85; // 极端窄屏
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const getLeftTime = (end) => {
|
|
|
+ if (!end) return "";
|
|
|
+
|
|
|
+ const endTime = new Date(end).getTime();
|
|
|
+ const now = Date.now();
|
|
|
+
|
|
|
+ // 如果活动已结束
|
|
|
+ if (now >= endTime) {
|
|
|
+ return "00:00:00:00";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算剩余毫秒数
|
|
|
+ const remainingTime = endTime - now;
|
|
|
+
|
|
|
+ // 计算天数、小时、分钟、秒
|
|
|
+ const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24));
|
|
|
+ const hours = Math.floor((remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
|
+ const minutes = Math.floor((remainingTime % (1000 * 60 * 60)) / (1000 * 60));
|
|
|
+ const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000);
|
|
|
+
|
|
|
+ // 格式化:天数:小时:分钟:秒
|
|
|
+ return `${days.toString().padStart(2, '0')}:${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
|
|
+ };
|
|
|
+
|
|
|
+ const timeElementRef = useRef(null);
|
|
|
+ const timerRef = useRef(null);
|
|
|
+ // 不用react useState钩子防止频繁更新
|
|
|
+ useEffect(() => {
|
|
|
+ if (!end_time) return;
|
|
|
+
|
|
|
+ // 使用 setTimeout 确保 DOM 已挂载
|
|
|
+ const initTimer = () => {
|
|
|
+ if (!timeElementRef.current) {
|
|
|
+ // 如果还没挂载,等待下一帧
|
|
|
+ setTimeout(initTimer, 0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("timeElementRef 已挂载:", timeElementRef.current);
|
|
|
+
|
|
|
+ const updateTimeDisplay = () => {
|
|
|
+ const newTime = getLeftTime(end_time);
|
|
|
+ if (timeElementRef.current) {
|
|
|
+ timeElementRef.current.textContent = newTime;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ updateTimeDisplay();
|
|
|
+ timerRef.current = setInterval(updateTimeDisplay, 1000);
|
|
|
+ };
|
|
|
+
|
|
|
+ initTimer();
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ if (timerRef.current) {
|
|
|
+ clearInterval(timerRef.current);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }, [end_time]);
|
|
|
+
|
|
|
+ const [openRule, setOpenRule] = useState(false);
|
|
|
+ const ActRule = ({ open, onClose }) => {
|
|
|
+ const [show, setShow] = useState(open);
|
|
|
+ const texts = [
|
|
|
+ "1.All users can participate in the lucky draw once for free dunng the event.",
|
|
|
+ "2.You can get a chance to win a prize by completing a deposit.",
|
|
|
+ "3.The highest prize in a single lucky draw is $777."
|
|
|
+ ];
|
|
|
+
|
|
|
+
|
|
|
+ const handleClose = () => {
|
|
|
+ setShow(false);
|
|
|
+ setTimeout(() => {
|
|
|
+ onClose();
|
|
|
+ }, 500);
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Modal open={show} onClose={handleClose} closeAfterTransition>
|
|
|
+ <Fade in={show} timeout={500}>
|
|
|
+ <div className={styles.rule_container}>
|
|
|
+ <div className={styles.rule_popup}>
|
|
|
+ <div className={styles.rule_title}>
|
|
|
+ Activity Rules
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.rule_texts}>
|
|
|
+ {
|
|
|
+ texts.map((item, index) => {
|
|
|
+ return (
|
|
|
+ <div className={styles.rule_text} key={index}>
|
|
|
+ <span>{item}</span>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.rule_btnClaim} onClick={handleClose}>
|
|
|
+ <span>CLAIM · </span>
|
|
|
+ <img src={icon_gift2} alt="" />
|
|
|
+ <span>OPENS X1</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Fade >
|
|
|
+ </Modal >
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (free_times === undefined || !open) return;
|
|
|
+ const hasProcessed = localStorageGet("actRule_processed");
|
|
|
+ if (free_times > 0 && !hasProcessed) {
|
|
|
+ localStorageSet("actRule_processed", true);
|
|
|
+ setOpenRule(true);
|
|
|
+ }
|
|
|
+ }, [free_times, open]);
|
|
|
+
|
|
|
+ const [openDetail, setOpenDetail] = useState(false);
|
|
|
+ const ActDetail = ({ open, onClose }) => {
|
|
|
+ const [show, setShow] = useState(open);
|
|
|
+ const texts = [
|
|
|
+ "1.Deposit to get 125% bonus.",
|
|
|
+ "2.Each pack can be bought twrice daily and expires after countdown.",
|
|
|
+ "3.Pack value is based on historical performance.",
|
|
|
+ "4.Event lasts 3 days (purchases unavailable afterward)",
|
|
|
+ "5.Expioiting bugs may result in penalties",
|
|
|
+ "6.(e.g..ban/reward deduction)"
|
|
|
+ ];
|
|
|
+
|
|
|
+
|
|
|
+ const handleClose = () => {
|
|
|
+ setShow(false);
|
|
|
+ setTimeout(() => {
|
|
|
+ onClose();
|
|
|
+ }, 500);
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Modal open={show} onClose={handleClose} closeAfterTransition>
|
|
|
+ <Fade in={show} timeout={500}>
|
|
|
+ <div className={styles.detail_container}>
|
|
|
+ <div className={styles.detail_popup}>
|
|
|
+ <img src={detailGift} alt="" className={styles.detailGift} />
|
|
|
+
|
|
|
+ <div className={styles.detail_title}>
|
|
|
+ Event Details
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.detail_texts}>
|
|
|
+ {
|
|
|
+ texts.map((item, index) => {
|
|
|
+ return (
|
|
|
+ <div className={styles.detail_text} key={index}>
|
|
|
+ <span>{item}</span>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.detail_btnOK} onClick={handleClose}>
|
|
|
+ OK
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Fade >
|
|
|
+ </Modal >
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
+ if (!!!status) return <></>;
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <Modal open={open} onClose={onCheckClose} closeAfterTransition>
|
|
|
+ <Fade in={open} timeout={500}>
|
|
|
+ <div className={styles.container}>
|
|
|
+ <div className={styles.activity} style={{ transform: `scale(${getAdaptScale()})` }}>
|
|
|
+ <img src={img_theme} alt="" className={styles.themeImg} />
|
|
|
+ <div className={styles.layout}>
|
|
|
+ <div className={styles.msgBar}>
|
|
|
+ <div className={styles.left}>
|
|
|
+ <img src={icon_clock} alt="" />
|
|
|
+ {/* <span>{timeLeft}</span> */}
|
|
|
+ <span ref={timeElementRef}></span>
|
|
|
+ </div>
|
|
|
+ <div className={styles.right}>
|
|
|
+ <img src={icon_gift2} alt="" />
|
|
|
+ <span>OPENS</span>
|
|
|
+ <span className={styles.nums}>X{spinTimes}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.selectArea}>
|
|
|
+ <div className={styles.items}>
|
|
|
+ {itemsState.map((item, idx) => {
|
|
|
+ const isSelected = idx === selectedIndex;
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={idx}
|
|
|
+ className={styles.item}
|
|
|
+ onClick={(e) => handleClickItem(e, idx)}
|
|
|
+ style={{
|
|
|
+ transform: item.isAnimating
|
|
|
+ ? `rotate(${item.rotationAngle}deg)`
|
|
|
+ : 'none',
|
|
|
+ transition: item.isAnimating
|
|
|
+ ? 'transform 0.2s ease'
|
|
|
+ : 'transform 0.3s ease',
|
|
|
+ zIndex: isSelected ? 2 : "unset",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {/* 金币飞行的起点 */}
|
|
|
+ <div
|
|
|
+ ref={el => coinRefs.current[idx] = el}
|
|
|
+ className={styles.coinSource}
|
|
|
+ style={{
|
|
|
+ position: 'absolute',
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ opacity: 0
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 锁和开的礼物图 - 互斥显示 */}
|
|
|
+ {item.isOpened ? (
|
|
|
+ <img
|
|
|
+ src={giftOpen}
|
|
|
+ alt=""
|
|
|
+ className={styles.open}
|
|
|
+ style={{ opacity: 1 }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <img
|
|
|
+ src={giftLock}
|
|
|
+ alt=""
|
|
|
+ className={styles.lock}
|
|
|
+ style={{ opacity: 1 }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 金额显示 - 互斥显示 */}
|
|
|
+ {item.showReward && (
|
|
|
+ <div
|
|
|
+ className={isSelected ? styles.selectReward : styles.reward}
|
|
|
+ style={{
|
|
|
+ opacity: item.showReward ? 1 : 0,
|
|
|
+ transition: 'opacity 0.5s ease'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ ${rewardData[idx]}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 遮罩 */}
|
|
|
+ {item.showMask && (
|
|
|
+ <div
|
|
|
+ className={styles.mask}
|
|
|
+ style={{
|
|
|
+ opacity: item.showMask ? 1 : 0,
|
|
|
+ transition: 'opacity 0.3s ease'
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className={styles.msgPanel}>
|
|
|
+ {recharge_rules?.map((rule, index) => {
|
|
|
+ return (
|
|
|
+ <div className={styles.item} key={index}>
|
|
|
+ <div className={styles.itemBox}>
|
|
|
+ <img src={icon_cash} alt="" className={styles.icon_cash} />
|
|
|
+ <span className={styles.price}>${rule?.amount}</span>
|
|
|
+ </div>
|
|
|
+ <span className={styles.plus}>+</span>
|
|
|
+ <div className={styles.itemBox}>
|
|
|
+ <img src={icon_gift} alt="" className={styles.icon_gift} />
|
|
|
+ <span className={styles.spinTimes}>SPIN+{rule?.times}</span>
|
|
|
+ </div>
|
|
|
+ <div className={styles.buy} onClick={() => handleBuyClick(rule)} >
|
|
|
+ ${rule?.gear?.money}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <img src={img_close} alt="" className={styles.close} onClick={onCheckClose} />
|
|
|
+
|
|
|
+ <svg className={styles.question} onClick={() => setOpenDetail(true)} >
|
|
|
+ <use xlinkHref={`${symbol}#icon_question`} />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </Fade>
|
|
|
+ </Modal >
|
|
|
+
|
|
|
+ <ActRule open={openRule} onClose={() => setOpenRule(false)} />
|
|
|
+ <ActDetail open={openDetail} onClose={() => setOpenDetail(false)} />
|
|
|
+ </>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default WHolidaysAct;
|