RedPacketModal.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. "use client";
  2. import { lredPacketApi, receiveRedPacketApi, redPacketApi } from "@/api/promo";
  3. import { getToken } from "@/utils/Cookies";
  4. import { Mask } from "antd-mobile";
  5. import Image from "next/image";
  6. import { forwardRef, Fragment, useEffect, useImperativeHandle, useRef, useState } from "react";
  7. import styles from "./redpacked.module.scss";
  8. const randomX = (len: number) => {
  9. return Math.floor(Math.random() * len);
  10. };
  11. const mockData = Array(500)
  12. .fill(0)
  13. .map((item) => {
  14. return {
  15. phone: `55****${(randomX(99) + "").padEnd(2, "0")}`,
  16. num: `${(Math.random() * 20).toFixed(2)}`,
  17. time: "11:00",
  18. };
  19. });
  20. function getRandom(min: number, max: number) {
  21. const floatRandom = Math.random();
  22. const difference = max - min;
  23. // 介于 0 和差值之间的随机数
  24. const random = Math.round(difference * floatRandom);
  25. return random + min;
  26. }
  27. /**
  28. * @description 描述
  29. */
  30. type DescProps = {
  31. onClose: () => void;
  32. };
  33. const Desc = (props: DescProps) => {
  34. const { onClose } = props;
  35. const [activeTab, setActiveTab] = useState(0);
  36. const tabs = [{ text: "Vezes de evento participado" }, { text: "Consulta de registo levado" }];
  37. return (
  38. <div className={"absolute top-[30%] w-[100%]"}>
  39. <div className={"absolute -top-[1.3rem] left-1/2 w-[3.3rem] -translate-x-1/2"}>
  40. <img src="/9f/red-header.png" alt="" />
  41. <div
  42. className={
  43. "h-[0.2rem] w-[0.2rem] " + " absolute bottom-[20px] right-[30px]" + " "
  44. }
  45. onClick={onClose}
  46. >
  47. <Image src={"/9f/close.png"} alt={"close"} width={25} height={25} />
  48. </div>
  49. </div>
  50. <div
  51. className={"mx-auto w-[2.9rem] bg-[#ff9417] px-[0.16rem] pb-[0.12rem] pt-[0.44rem]"}
  52. >
  53. <img src="/9f/red-title.png" alt="" className={"mx-auto w-[90%]"} />
  54. <div className={"mt-[0.0694rem] rounded-[3px] bg-primary-color"}>
  55. <div className={"flex h-[0.54rem] justify-between text-center text-[0.15rem]"}>
  56. {tabs.map((item, index) => {
  57. return (
  58. <Fragment key={index}>
  59. <span
  60. onClick={() => setActiveTab(index)}
  61. className={`flex h-[100%] items-center ${index === activeTab ? "bg-[#8b3500] text-[#5f2600]" : ""}`}
  62. >
  63. {item.text}
  64. </span>
  65. </Fragment>
  66. );
  67. })}
  68. </div>
  69. {/* 动态内容 */}
  70. <div className={"h-[3rem] overflow-y-scroll p-[0.1rem]"}>
  71. <div
  72. className={
  73. "flex items-center rounded-[0.1rem] bg-[#d45300] p-[10px] font-bold"
  74. }
  75. >
  76. <Image
  77. className={"h-[0.43rem] w-[0.7rem]"}
  78. width={80}
  79. height={40}
  80. src="/9f/wallet.png"
  81. alt=""
  82. />
  83. <div className={"text-center"}>
  84. <h2> Tempo regressivo </h2>
  85. <p className={"text-[#fe0]"}>Começa amanhã às 11:00</p>
  86. </div>
  87. </div>
  88. {!activeTab ? (
  89. <>
  90. <Image
  91. src={"/9f/6xtime.png"}
  92. className={"mt-[0.1rem] h-[0.7rem] w-[100%] rounded-[0.1rem]"}
  93. width={600}
  94. height={100}
  95. alt={"time"}
  96. ></Image>
  97. <Image
  98. src={"/9f/3xtime.png"}
  99. className={"mt-[0.1rem] h-[0.55rem] w-[100%] rounded-[0.1rem]"}
  100. width={600}
  101. height={80}
  102. alt={"time"}
  103. ></Image>
  104. </>
  105. ) : (
  106. <>
  107. <div
  108. className={"mt-[0.1rem] rounded-[0.1rem] bg-[#d45300] p-[10px]"}
  109. >
  110. <p className={"text-[#fe0]"}>Registro persoal</p>
  111. <div
  112. className={
  113. "grid grid-cols-3 text-center" +
  114. " items-center text-[0.1rem]"
  115. }
  116. >
  117. <span>Nome do jogo</span>
  118. <span>Coleta cumulativa</span>
  119. <span>Participação total</span>
  120. </div>
  121. <div
  122. className={
  123. "grid grid-cols-3 text-center" +
  124. " items-center text-[0.12rem] font-bold"
  125. }
  126. >
  127. <span>55****26</span>
  128. <span>R$ 100.00</span>
  129. <span>1</span>
  130. </div>
  131. </div>
  132. <div
  133. className={"mt-[0.1rem] rounded-[0.1rem] bg-[#d45300] p-[10px]"}
  134. >
  135. <p className={"text-[#fe0]"}>Lista dos vencedores</p>
  136. <div
  137. className={
  138. "grid grid-cols-3 text-center" +
  139. " items-center text-[0.1rem]"
  140. }
  141. >
  142. <span>ID do papel</span>
  143. <span>Valor obtido</span>
  144. <span>Hora obtida</span>
  145. </div>
  146. <div className={`h-[1rem] overflow-hidden`}>
  147. {mockData.map((item, index) => {
  148. return (
  149. <div
  150. key={index}
  151. className={`grid grid-cols-3 items-center text-center text-[0.12rem] font-bold ${styles.scrollAnimation}`}
  152. >
  153. <span>{item.phone}</span>
  154. <span>R$ {item.num}</span>
  155. <span>{item.time}</span>
  156. </div>
  157. );
  158. })}
  159. </div>
  160. </div>
  161. </>
  162. )}
  163. <ul className={"mt-[0.1rem] text-[0.1rem]"}>
  164. <li>
  165. ·Cada sessão de chuva de dinheiro é distribuída gratuitamente por
  166. R$100.000.
  167. </li>
  168. <li>
  169. ·Valor máximo de queda em dinheiro: Cada sessão de chuva de dinheiro
  170. é distribuída gratuitamente.
  171. </li>
  172. <li>·Membros recarregados podem reivindicar gratuitamente.</li>
  173. <li>
  174. ·O dinheiro recebido pode ser utilizado para jogar ou sacar
  175. diretamente.
  176. </li>
  177. <li>
  178. ·Quanto for maior o nível de associação VIP, será maior o valor
  179. recebido.
  180. </li>
  181. </ul>
  182. </div>
  183. </div>
  184. </div>
  185. </div>
  186. );
  187. };
  188. const FallAnimation = (props: any) => {
  189. const { onClose } = props;
  190. const fallContentRef = useRef<HTMLDivElement>(null);
  191. const totalPackets = 200;
  192. const timer = useRef<NodeJS.Timer>(null);
  193. const total = useRef(0);
  194. const createPacket = (xPoint: number) => {
  195. const packetWrapperEl = document.createElement("div");
  196. packetWrapperEl.classList.add(styles.packetWrapper);
  197. packetWrapperEl.style.left = xPoint + "px";
  198. packetWrapperEl.style.width = `77px`;
  199. packetWrapperEl.style.height = `44px`;
  200. packetWrapperEl.style.transform = `scale(0.8) `;
  201. const packetEl = document.createElement("img");
  202. packetEl.classList.add(styles.packet);
  203. packetEl.style.width = `${getRandom(44, 77)}`;
  204. packetEl.style.height = `${getRandom(44, 77)}`;
  205. packetEl.src = `/9f/money${Math.floor(Math.random() * 3) + 1}.png`;
  206. setInterval(() => {
  207. packetEl.style.transform = `rotate(${getRandom(0, 180)}deg) translateX(${Math.random() > 0.5 ? getRandom(0, 180) : -getRandom(0, 180)}px) scale(${getRandom(0.4, 1.2)}) `;
  208. }, 1000);
  209. packetWrapperEl.appendChild(packetEl);
  210. fallContentRef.current?.appendChild(packetWrapperEl);
  211. };
  212. useEffect(() => {
  213. // @ts-ignore
  214. timer.current = setInterval(() => {
  215. if (total.current >= totalPackets - 1) {
  216. clearInterval(Number(timer.current));
  217. return;
  218. }
  219. total.current += 1;
  220. createPacket(Math.random() * fallContentRef.current?.clientWidth! || 500);
  221. }, 200);
  222. return () => {
  223. clearInterval(Number(timer.current));
  224. };
  225. }, []);
  226. return (
  227. <div
  228. ref={fallContentRef}
  229. style={{ height: "100dvh" }}
  230. className={"h-dvh overflow-hidden"}
  231. onClick={onClose}
  232. ></div>
  233. );
  234. };
  235. const HbyInfoDetail = (props: any) => {
  236. const { iconImg, onCloseHby } = props;
  237. return (
  238. <div
  239. className={`absolute left-1/2 top-[50%] w-[90%] -translate-x-1/2 -translate-y-1/2 ${styles.promoRules}`}
  240. >
  241. {/* <Image src={"/hby/close.png"} alt={"close"} width={25} height={25} onClick={onCloseHby} className={styles.closeIcon}/> */}
  242. <div onClick={onCloseHby} className={styles.closeIcon}></div>
  243. <Image src={iconImg} alt={"detail"} width={672} height={1044} />
  244. {/* <div className={`h-[0.15rem] text-[#ffd800] text-[0.20rem] text-center ${styles.promoTitle}`}>Dinheiro como chuva</div>
  245. <div className={styles.titleWrap}>
  246. <span>R$200.00</span>
  247. <span> por vez, </span>
  248. <span>Máx queda </span>
  249. <span>R$7.777</span>
  250. </div>
  251. <div className={styles.tips}>
  252. <img src="/hby/tip-icon.png" alt="tips" className={styles.tipsIcon}/>
  253. <div className={styles.tipsTime}>Começa às 23:00</div>
  254. </div>
  255. <div className={styles.times1}>
  256. <img src="/hby/time1.png"/>
  257. </div>
  258. <div className={styles.times2}>
  259. <img src="/hby/time2.png"/>
  260. </div>
  261. <ul className={styles.rulelist}>
  262. <li className={styles.ruleItem}>
  263. Cada sessão de chuva de dinheiro é distribuída gratuitamente com <span>R$200.000</span>
  264. </li>
  265. <li className={styles.ruleItem}>
  266. Valor máximo de queda em dinheiro:Cada sessäo de chuva de dinheiro é distribuida gratuitamente com
  267. </li>
  268. <li className={styles.ruleItem}>
  269. Membros recarregados podem reivindicar gratuitamente
  270. </li>
  271. <li className={styles.ruleItem}>
  272. O dinheiro recebido pode ser utilizado para jogar ou sacado diretamente
  273. </li>
  274. <li className={styles.ruleItem}>
  275. Quanto maior o nivel de associacäo VP, maior o valor recebido
  276. </li>
  277. </ul> */}
  278. </div>
  279. );
  280. };
  281. const HbyInfo = (props: any) => {
  282. const { iconImg, onCloseHby, onReciveRed } = props;
  283. if (!iconImg) return;
  284. return (
  285. <div
  286. className={`absolute left-1/2 top-[50%] -translate-x-1/2 -translate-y-1/2 ${styles.redclose}`}
  287. >
  288. {/* <Image src={"/hby/close.png"} alt={"close"} width={20} height={20} onClick={onCloseHby} className={styles.closeIcon}/> */}
  289. <div onClick={onCloseHby} className={styles.closeIcon}></div>
  290. <img
  291. src={iconImg}
  292. alt={"icon"}
  293. width={559}
  294. height={687}
  295. onClick={onReciveRed}
  296. className={styles.redIcon}
  297. />
  298. {/* <div className={styles.title}>Chuva de dinheiro</div>
  299. <div className={styles.desc}>
  300. <ul className={styles.desclist}>
  301. <li className={styles.descitem}> Membros recarregados podem reivindicar gratuitamente. </li>
  302. <div className={styles.line}></div>
  303. <li className={styles.descitem}> Valor máximo de queda em dinheiro: R$7.777 </li>
  304. </ul>
  305. </div>
  306. <div className={styles.openBtn} onClick={onReciveRed}>AGARRAR</div> */}
  307. </div>
  308. // <div data-v-f333135e="" className={`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 ${styles.redopen}`}>
  309. // <Image src={"/hby/close.png"} alt={"close"} width={20} height={20} onClick={onCloseHby} className={styles.closeIcon}/>
  310. // <div className={styles.title}>Chuva de dinheiro</div>
  311. // <div className={styles.cash}>1.96</div>
  312. // <div className={styles.tips}>Valor máximo de queda em dinheiro:Cada sessäo de chuva de dinheiro é </div>
  313. // </div>
  314. );
  315. };
  316. const HbyInfo2 = (props: any) => {
  317. const { iconImg, onCloseHby, redAmount } = props;
  318. console.log(`🚀🚀🚀🚀🚀-> in PopupHby.tsx on 364`, iconImg);
  319. return (
  320. <div
  321. className={`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 ${styles.redopen}`}
  322. >
  323. <Image
  324. src={"/hby/close.png"}
  325. alt={"close"}
  326. width={30}
  327. height={30}
  328. onClick={onCloseHby}
  329. className={styles.closeIcon}
  330. />
  331. {/* <div onClick={onCloseHby} className={styles.closeIcon}></div> */}
  332. <Image src={iconImg} alt={"icon"} width={559} height={687} />
  333. <div className={styles.cash}>{redAmount}</div>
  334. {/* <div className={styles.title}>Chuva de dinheiro</div>
  335. <div className={styles.cash}>{redAmount}</div>
  336. <div className={styles.tips}>Valor máximo de queda em dinheiro:Cada sessäo de chuva de dinheiro é </div> */}
  337. </div>
  338. );
  339. };
  340. type Props = {};
  341. export type RedPacketModalProps = {
  342. onClose: () => void;
  343. onOpen: (index?: number) => void;
  344. };
  345. /**
  346. * @description 红包的三种状态
  347. * is_start 可领取 展示红包领取组件
  348. * is_receive 已领取 展示领取详情组件
  349. * is_end 可展示 展示详情界面
  350. */
  351. enum Status {
  352. is_start,
  353. is_receive,
  354. is_end,
  355. }
  356. const RedPacketModal = forwardRef<RedPacketModalProps, Props>(function RedPacketModal(props, ref) {
  357. const [visible, setVisible] = useState(false);
  358. const [iconLists, setIconLists] = useState<any>([]);
  359. const [isShowRed, setIsShowRed] = useState(false);
  360. const [isShowReciveRed, setIsShowReciveRed] = useState(false);
  361. const [isShowRedDetail, setIsShowRedDetail] = useState(false);
  362. const [redPacketInfo, setRedPacketInfo] = useState<any>({});
  363. const [redAmount, setRedAmount] = useState<any>(0);
  364. const activeIndex = useRef<number | null>(null);
  365. const token = getToken();
  366. useImperativeHandle(ref, () => {
  367. return {
  368. onClose: () => setVisible(false),
  369. onOpen: (index?: number) => {
  370. if (index !== null && index !== undefined) {
  371. activeIndex.current = index;
  372. }
  373. getRedPacketInfo().then((res) => {
  374. setVisible(true);
  375. });
  376. },
  377. };
  378. });
  379. useEffect(() => {
  380. // getRedPacketInfo();
  381. }, []);
  382. const getRedPacketInfo = async () => {
  383. try {
  384. let actList: any = [];
  385. if (token) {
  386. let redPacketInfo = await lredPacketApi();
  387. actList = redPacketInfo.data?.red_packets || [];
  388. } else {
  389. let redPacketInfo = await redPacketApi();
  390. actList = redPacketInfo.data;
  391. }
  392. // 是否有已开始但是没领过的红包
  393. let packets = actList.filter((aItem: any) => {
  394. return !!aItem.is_start && !aItem.is_receive;
  395. });
  396. let current = null;
  397. if (activeIndex.current !== null && activeIndex.current !== undefined) {
  398. current = packets[activeIndex.current];
  399. } else {
  400. current = findCurrentActivity(packets);
  401. }
  402. let isShowRed = packets.length > 0;
  403. let isShowRedDetail = packets.length === 0;
  404. //
  405. // let redInfo = isShowRed ? isHasStartAct[0] : {};
  406. // if (activeIndex.current !== undefined && activeIndex.current !== null) {
  407. // redInfo = isHasStartAct[activeIndex.current];
  408. // }
  409. // let curAct = isShowRedDetail ? findCurrentActivity(actList) : actList[0];
  410. let iconList = JSON.parse(current.icon);
  411. setIconLists(iconList);
  412. setRedPacketInfo(current);
  413. setIsShowRed(isShowRed);
  414. setIsShowRedDetail(isShowRedDetail);
  415. } catch (error) {
  416. console.log("redPacketInfo===>error:", error);
  417. }
  418. };
  419. // 筛选出离当前时间最近的活动
  420. const findCurrentActivity = (activities: any) => {
  421. const now = Math.floor(Date.now() / 1000); // 获取当前时间的时间戳(单位:秒)
  422. let closestActivity = null;
  423. let isInRange = false;
  424. // 遍历活动数据
  425. for (let i = 0; i < activities.length; i++) {
  426. const activity = activities[i];
  427. // 检查当前时间是否在活动的时间范围内
  428. if (now >= activity.start_time && now <= activity.end_time) {
  429. isInRange = true;
  430. return activity; // 如果在范围内,直接返回当前活动
  431. }
  432. // 如果不在范围内,记录离当前时间最近的活动
  433. if (
  434. !closestActivity ||
  435. Math.abs(now - activity.end_time) < Math.abs(now - closestActivity.end_time)
  436. ) {
  437. closestActivity = activity;
  438. }
  439. }
  440. // 如果不在任何活动范围内,返回离当前时间最近的活动
  441. return closestActivity;
  442. };
  443. const onReciveRed = async () => {
  444. try {
  445. let paramsData = {
  446. id: redPacketInfo?.id,
  447. index: redPacketInfo?.index,
  448. };
  449. let receiveRedPacketInfo = await receiveRedPacketApi(paramsData);
  450. let redNum = receiveRedPacketInfo.data;
  451. setIsShowRed(false);
  452. setIsShowReciveRed(true);
  453. setRedAmount(redNum?.amount);
  454. } catch (error) {
  455. console.log("redPacketInfo===>error:", error);
  456. }
  457. };
  458. return (
  459. <Mask visible={visible} destroyOnClose={true}>
  460. <FallAnimation onClose={() => setVisible(false)} />
  461. {isShowRedDetail && (
  462. <HbyInfoDetail onCloseHby={() => setVisible(false)} iconImg={iconLists[0]} />
  463. )}
  464. {isShowRed && (
  465. <HbyInfo
  466. onCloseHby={() => setVisible(false)}
  467. onReciveRed={onReciveRed}
  468. iconImg={iconLists[1]}
  469. />
  470. )}
  471. {isShowReciveRed && (
  472. <HbyInfo2
  473. onCloseHby={() => setVisible(false)}
  474. redAmount={redAmount}
  475. iconImg={iconLists[2]}
  476. />
  477. )}
  478. </Mask>
  479. );
  480. });
  481. export default RedPacketModal;