WheelModal.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. "use client";
  2. import { getWheelApi, WheelsType } from "@/api/cashWheel";
  3. import Progress from "@/app/[locale]/(navbar)/cashback/@cashbackInfo/components/Progress";
  4. import { useRouter } from "@/i18n/routing";
  5. import { getToken } from "@/utils/Cookies";
  6. import { percentage, timeFormat } from "@/utils/methods";
  7. import { LuckyWheel } from "@lucky-canvas/react";
  8. import { Mask } from "antd-mobile";
  9. import Image from "next/image";
  10. import { FC, forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
  11. import styles from "./WheelModal.module.scss";
  12. type Props = {
  13. onAfterHandler?: () => void;
  14. };
  15. export type WheelModalProps = {
  16. onClose: () => void;
  17. onOpen: (value: WheelsType) => void;
  18. };
  19. /**
  20. * 轮盘组件
  21. */
  22. const blocks = [
  23. {
  24. padding: "0",
  25. imgs: [
  26. {
  27. src: "/wheels/wheel.png",
  28. width: "100%",
  29. height: "100%",
  30. rotate: true,
  31. },
  32. ],
  33. },
  34. ];
  35. const prizes = [
  36. {
  37. fonts: [
  38. {
  39. text: "5000",
  40. top: "20%",
  41. fontColor: "#a47716",
  42. fontWeight: "bold",
  43. type: "bonus",
  44. fontSize: "0.1528rem",
  45. },
  46. ],
  47. },
  48. {
  49. fonts: [{ text: "", top: "20%", fontColor: "#a47716", fontWeight: "bold", type: "empty" }],
  50. imgs: [
  51. {
  52. src: "/wheels/prizes-empty.png",
  53. top: "20%",
  54. width: "0.2778rem",
  55. },
  56. ],
  57. },
  58. {
  59. fonts: [
  60. {
  61. text: "50",
  62. top: "20%",
  63. fontColor: "#a47716",
  64. fontWeight: "bold",
  65. type: "bonus",
  66. fontSize: "0.1528rem",
  67. },
  68. ],
  69. },
  70. {
  71. fonts: [
  72. { text: "", top: "30%", fontColor: "#a47716", fontWeight: "bold", type: "balance" },
  73. ],
  74. imgs: [
  75. {
  76. src: "/wheels/prizes-money.png",
  77. top: "20%",
  78. width: "0.3472rem",
  79. },
  80. ],
  81. },
  82. {
  83. fonts: [
  84. {
  85. text: "1000",
  86. top: "20%",
  87. fontColor: "#a47716",
  88. fontWeight: "bold",
  89. type: "bonus",
  90. fontSize: "0.1528rem",
  91. },
  92. ],
  93. },
  94. {
  95. imgs: [
  96. {
  97. src: "/wheels/prizes-empty.png",
  98. top: "20%",
  99. width: "0.2778rem",
  100. height: "0.2778rem",
  101. },
  102. ],
  103. },
  104. {
  105. fonts: [
  106. {
  107. text: "1",
  108. top: "20%",
  109. fontColor: "#a47716",
  110. fontWeight: "bold",
  111. type: "bonus",
  112. fontSize: "0.1528rem",
  113. },
  114. ],
  115. },
  116. {
  117. fonts: [
  118. {
  119. text: "",
  120. top: "20%",
  121. fontColor: "#a47716",
  122. fontWeight: "bold",
  123. type: "balance",
  124. fontSize: "0.1528rem",
  125. },
  126. ],
  127. imgs: [
  128. {
  129. src: "/wheels/prizes-money.png",
  130. top: "20%",
  131. width: "0.3472rem",
  132. },
  133. ],
  134. },
  135. ];
  136. const defaultConfig = {
  137. offsetDegree: 20,
  138. };
  139. /**
  140. * type: isRotate 是否可旋转, 分享页面不需要旋转, 而是跳转
  141. */
  142. export interface WheelProps {
  143. isRotate: boolean;
  144. wheel: Partial<WheelsType>;
  145. onRotateEnd?: () => void;
  146. }
  147. export const WheelClient: FC<WheelProps> = (props) => {
  148. const { isRotate, wheel, onRotateEnd } = props;
  149. const wheelRef = useRef<any>();
  150. /*是否旋转*/
  151. const rotating = useRef<boolean>(false);
  152. const router = useRouter();
  153. const current = { ...(wheel?.activities?.[0] || {}), ...(wheel?.activate || {}) };
  154. const [buttonText, setButtonText] = useState<string>("0"); // 0 -> false 1:true
  155. // 当前中奖
  156. const currentWin = useRef<any>({});
  157. const startRotate = (key: string) => {
  158. if (!isRotate) {
  159. router.push("/register");
  160. return;
  161. }
  162. // 正在旋转中
  163. if (rotating.current) return;
  164. if (!current.can) return;
  165. // 如果包含还没领取的奖励
  166. if (wheel.not_receive && wheel.not_receive.length > 0) return;
  167. // 当前活动不存在
  168. if (!current.id) return;
  169. // 开始旋转中
  170. wheelRef.current?.play();
  171. // 点击抽奖按钮会触发star回调
  172. rotating.current = true;
  173. // getWheelRunApi({ activity_id: current.id })
  174. // .then((res) => {
  175. // setTimeout(() => {
  176. // wheelRef.current?.stop(res.data.index);
  177. //
  178. // currentWin.current = res.data;
  179. // }, 2000);
  180. // })
  181. // .catch(() => {
  182. // wheelRef.current?.init();
  183. // });
  184. };
  185. const endRotate = (prize: any) => {
  186. rotating.current = false;
  187. if (currentWin.current.amount > 0) {
  188. setButtonText(`+${currentWin.current.amount}`);
  189. } else {
  190. setButtonText(`${current.can || 0}`);
  191. }
  192. setTimeout(() => {
  193. onRotateEnd && onRotateEnd();
  194. }, 2000);
  195. };
  196. useEffect(() => {
  197. setButtonText(`${current.can || 0}`);
  198. }, [wheel]);
  199. return (
  200. <div className={"relative w-[100%]"}>
  201. <img src={"/wheels/wheel-bg.png"} className={"h-[5.5556rem] w-[100%] object-contain"} />
  202. <img
  203. src={"/wheels/title.png"}
  204. className={"absolute left-[17%] top-[52%] z-10 w-[63%] object-contain"}
  205. />
  206. {/*定位到中心圆*/}
  207. <div className={"absolute bottom-0 h-[2.5rem] w-[100%]"}>
  208. {/*光圈图片 */}
  209. <div className={"flex h-[100%] w-[100%] justify-center p-[0.0556rem]"}>
  210. <img src="/wheels/light-1.png" alt="" className={"mr-[0.0556rem] h-[100%]"} />
  211. </div>
  212. {/*转盘*/}
  213. <div
  214. className={
  215. "absolute bottom-[0] flex h-[100%] w-[100%] justify-center p-[0.1389rem]"
  216. }
  217. >
  218. <div
  219. className={
  220. "relative mr-[0.0694rem] mt-[0.05rem] h-[2.1rem] w-[2.1rem] rounded-[50%]"
  221. }
  222. >
  223. <div className={"absolute bottom-[50%] z-50 w-[100%] translate-y-1/2"}>
  224. <div
  225. className={"flex justify-center"}
  226. onClick={() => startRotate("desktop")}
  227. >
  228. <Image
  229. src={"/wheels/pointer.png"}
  230. className={"h-[0.6944rem] w-[0.625rem] object-contain"}
  231. width={90}
  232. height={120}
  233. alt={"start"}
  234. />
  235. <span
  236. className={
  237. "absolute bottom-[40%] translate-y-1/2 text-[#ffdb0e]"
  238. }
  239. >
  240. {buttonText}
  241. </span>
  242. </div>
  243. </div>
  244. <LuckyWheel
  245. ref={wheelRef}
  246. width="2.1rem"
  247. height="2.1rem"
  248. blocks={blocks}
  249. defaultConfig={defaultConfig}
  250. prizes={prizes}
  251. onEnd={(prize: any) => endRotate(prize)}
  252. />
  253. </div>
  254. </div>
  255. </div>
  256. </div>
  257. );
  258. };
  259. export const LeftListClient = () => {
  260. const allHistory: any[] = Array.from({ length: 100 }).map((item) => ({
  261. phone_number: `55****${Math.floor(Math.random() * 10000)
  262. .toString()
  263. .padStart(4, "0")}`,
  264. receive_time: Date.now(),
  265. }));
  266. return (
  267. <>
  268. <div className={`${styles.winList} ${styles.swipernoswiping} ${styles.type2}`}>
  269. {allHistory &&
  270. allHistory.length > 0 &&
  271. allHistory?.map((item, index) => {
  272. return (
  273. <div className={styles.item} key={index}>
  274. <span className={`${styles.name} ${styles.omitWrap}`}>
  275. {item.phone_number}
  276. </span>
  277. <span className={styles.tipText}>
  278. {timeFormat(item.receive_time, "br", undefined, true)}
  279. </span>
  280. <div className={styles.value}>
  281. <span className={styles.addCash}>+100</span>
  282. <span className={styles.unit}> R$</span>
  283. </div>
  284. </div>
  285. );
  286. })}
  287. </div>
  288. </>
  289. );
  290. };
  291. const DetailClient = (props: { wheel: Partial<WheelsType>; onUnload: () => void }) => {
  292. const { wheel, onUnload } = props;
  293. const router = useRouter();
  294. const goPage = () => {
  295. router.push("/cashWheel");
  296. onUnload();
  297. };
  298. return (
  299. <div className={`${styles.cashMain} ${styles.cashMain} ${styles.type1}`}>
  300. <div className={styles.haveCash}>
  301. <img src="/wheel/cash.png" alt="" className={styles.cashImg} />
  302. <div>
  303. {" "}
  304. R$ <span className={styles.cash}>{wheel.activate?.amount}</span>
  305. </div>
  306. <span className={styles.withdraw}>
  307. <img src="/wheel/pix.png" alt="" /> SACAR{" "}
  308. </span>
  309. </div>
  310. <div className={styles.progress}>
  311. <div className={styles.num}> {percentage(wheel.activate?.amount || 0, 100)}%</div>
  312. {/*<div className={styles.bar}>*/}
  313. {/* <span style={{ width: "calc(97.15% - 1rem)" }}></span>*/}
  314. {/*</div>*/}
  315. <Progress num={percentage(wheel.activate?.amount || 0, 100)} />
  316. </div>
  317. <div className={styles.needCash}>
  318. Ainda e necessário{" "}
  319. <span className={styles.needCashNum}>
  320. {(100 - (wheel.activate?.amount || 0)).toFixed(2)}
  321. </span>{" "}
  322. para realizar do saque{" "}
  323. </div>
  324. <div
  325. className={
  326. "h-[0.34rem] w-[100%] rounded-[0.0694rem] bg-[#fb8b05] text-[#fff]" +
  327. " flex items-center justify-center"
  328. }
  329. onClick={goPage}
  330. >
  331. Reivindique mais para sacar
  332. </div>
  333. <div className={"mt-[10px] h-[400px] w-[100%] overflow-hidden"}>
  334. <LeftListClient />
  335. </div>
  336. </div>
  337. );
  338. };
  339. const WheelModal = forwardRef<WheelModalProps, Props>(function RedPacketModal(props, ref) {
  340. const [visible, setVisible] = useState(false);
  341. const [detailsVisible, setDetailsVisible] = useState(false);
  342. const [wheel, setWheel] = useState<Partial<WheelsType>>({});
  343. useImperativeHandle(ref, () => {
  344. return {
  345. onClose: () => setVisible(false),
  346. onOpen: (wheelValue: WheelsType) => {
  347. setVisible(true);
  348. setWheel(wheelValue);
  349. },
  350. };
  351. });
  352. const getWheel = () => {
  353. if (!getToken()) return Promise.resolve(undefined);
  354. return getWheelApi().then((res) => {
  355. setWheel(res.data);
  356. return res.data;
  357. });
  358. };
  359. // useEffect(() => {
  360. // getWheel().then((res) => {
  361. // if (
  362. // res &&
  363. // res.activities &&
  364. // res.activities.length > 0 &&
  365. // res.activate.can &&
  366. // !res.not_receive?.length
  367. // ) {
  368. // setVisible(true);
  369. // }
  370. // });
  371. // }, []);
  372. const onRotateEnd = () => {
  373. setTimeout(() => {
  374. setVisible(false);
  375. setDetailsVisible(true);
  376. getWheel();
  377. }, 1000);
  378. };
  379. const onUnload = () => {
  380. setDetailsVisible(false);
  381. };
  382. return (
  383. <>
  384. <Mask visible={detailsVisible} getContainer={null}>
  385. <div className={"absolute top-[10%] z-50 w-[100%] p-[0.1389rem]"}>
  386. <div className={"rounded-[0.0694rem] bg-[#232327FF] p-[0.0694rem]"}>
  387. <div className={"flex items-center"}>
  388. <div className={"flex flex-1"}>
  389. <Image
  390. src={"/wheels/prizes-money.png"}
  391. width={40}
  392. height={40}
  393. alt={"moeny"}
  394. ></Image>
  395. <span className={"ml-[0.0694rem]"}>Receba R$ 100 de graca</span>
  396. </div>
  397. <span
  398. className={"iconfont icon-guanbi"}
  399. onClick={() => setDetailsVisible(false)}
  400. ></span>
  401. </div>
  402. <DetailClient wheel={wheel} onUnload={onUnload} />
  403. </div>
  404. </div>
  405. </Mask>
  406. <Mask visible={visible} destroyOnClose={true} getContainer={null}>
  407. <div
  408. className={"absolute right-[0.2083rem] top-[18%] z-50"}
  409. onClick={() => setVisible(false)}
  410. >
  411. <span className={"iconfont icon-guanbi"}></span>
  412. </div>
  413. <WheelClient isRotate={true} wheel={wheel} onRotateEnd={onRotateEnd} />
  414. </Mask>
  415. </>
  416. );
  417. });
  418. export default WheelModal;