Kaynağa Gözat

feat: 增加红包雨页面

Before 9 ay önce
ebeveyn
işleme
4cfbb42d72

+ 47 - 5
messages/br.json

@@ -84,7 +84,10 @@
     "registerGoogletext": "Registre-se com Google+",
     "domainName": "© BCWIN777.bet todos direitos reservados",
     "childTips": "This product is intended for users over the age of 18"
-
+  },
+  "confirmPwdPage": {
+    "title": "Change the Contrasinha",
+    "complete": "Complete"
   },
   "ResetPhonePage": {
     "enterCorrectphone":"Please enter a valid mobile phone numbe",
@@ -194,6 +197,7 @@
     "R$":"R$",
     "TotalPago":"Total pago",
     "Não":"Não pago",
+    "receive": "don't receive",
     "TRANSFERIR":"TRANSFERIR PARA A CARTEIRA",
     "Valor":"Valor mínimo de transferência de  10 BRL",
     "title1":"Compartilhe com sua comunidade social",
@@ -293,18 +297,56 @@
   },
   "code": {
     "200": "successful",
-    "1005": "Invalid username or password.",
+    "400": "Parameter error",
+    "401": "privilege grant failed",
+    "403":"Request Rejected",
+    "500": "System error",
+    "800":"Resource locking in progress",
+    "1000": "unknown error",
+    "1001":"The resource already exists",
+    "1002": "The resource does not exist",
+    "1003":"Business logic does not pass",
+    "1004": "Status code error",
+    "1005": "Wrong password",
+    "1006": "Insufficient inventory prevents purchasing",
+    "1007":"SKU resource does not exist",
+    "1008": "Phone number is incorrectly formatted",
     "1009": "There is no such user",
-    "1010": "The phone number is already connected to another client.",
-    "1008": "Phone number is incorrectly formatted"
+    "1010": "The user is registered",
+    "1011":"Too many errors are prohibited",
+    "1012": "User banned",
+    "1013": "Password conflicts",
+    "1014": "The verification code is incorrect",
+    "1015": "The balance is insufficient",
+    "1016": "Mismatched identity information",
+    "1017": "The invitation code is incorrect",
+    "1018": "The withdrawal channel is closed",
+    "1200":"Insufficient inventory",
+    "1201": "",
+    "1202":"",
+    "1300": "",
+    "1301":"",
+    "1400": "",
+    "1401":"",
+    "1402": "",
+    "1403":"",
+    "1404": "",
+    "1405":"",
+    "1406": "",
+    "1407":"",
+    "1501": "",
+    "1502":"",
+    "1503": ""
   },
-  "这是占位符": "",
   "form": {
     "phone":"Phone Number",
     "phoneReg": "The phone number cannot be empty",
     "phoneMinReg": "Please enter the correct phone number",
     "password": "Password",
     "passwordReg": "The password cannot be empty",
+    "newPwd": "Please enter a new password",
+    "checkPwd": "Please enter the new password again",
+    "checkPwdReg": "The password is inconsistent twice",
     "passwordMinReg": "The password cannot be less than 6 digits and more than 20 digits",
     "birthday": "Birthday",
     "birthdayReg": "The birthday cannot be empty",

+ 5 - 2
messages/en.json

@@ -197,6 +197,7 @@
     "R$":"R$",
     "TotalPago":"Total pago",
     "Não":"Não pago",
+    "receive": "don't receive",
     "TRANSFERIR":"TRANSFERIR PARA A CARTEIRA",
     "Valor":"Valor mínimo de transferência de  10 BRL",
     "title1":"Compartilhe com sua comunidade social",
@@ -296,7 +297,6 @@
   },
   "code": {
     "200": "successful",
-
     "400": "Parameter error",
     "401": "privilege grant failed",
     "403":"Request Rejected",
@@ -315,6 +315,10 @@
     "1010": "The user is registered",
     "1011":"Too many errors are prohibited",
     "1012": "User banned",
+    "1013": "Password conflicts",
+    "1014": "The verification code is incorrect",
+    "1015": "The balance is insufficient",
+    "1016": "Mismatched identity information",
     "1017": "The invitation code is incorrect",
     "1018": "The withdrawal channel is closed",
     "1200":"Insufficient inventory",
@@ -334,7 +338,6 @@
     "1502":"",
     "1503": ""
   },
-  "这是占位符": "",
   "form": {
     "phone":"Phone Number",
     "phoneReg": "The phone number cannot be empty",

BIN
public/9f/3xtime.png


BIN
public/9f/6xtime.png


BIN
public/9f/close.png


BIN
public/9f/red-header.png


BIN
public/9f/red-title.png


BIN
public/9f/wallet.png


+ 26 - 4
src/api/home.ts

@@ -215,6 +215,7 @@ export interface NoticeRep {
     notice_type: string;
     status: number;
     content: Content;
+    is_read: boolean;
 }
 export interface PromotionRep extends Omit<BannerRep, "content"> {
     content: Content;
@@ -236,14 +237,35 @@ export const searchGameListApi = (props: SearchProps) => {
 };
 
 //获取公告系统
-export interface GlobalNoticeRep extends Omit<BannerRep, "content"> {
+export interface GlobalNoticeRep extends Omit<NoticeRep, "content"> {
     content?: Content;
-    send_time?: number;
-    send_user_time?: number;
+    send_time: number;
+    send_user_time: number;
 }
 export const getGlobalNoticeApi = () => {
-    return server.post<NoticeRep[]>({
+    return server.post<NoticeRep[], { summery: { unread: number } }>({
         url: "/v1/api/front/notice_list",
         data: {},
     });
 };
+// 系统通知信息回执
+// POST /v1/api/front/notice_user_receipt
+// 接口ID:224339957
+// 接口地址:https://app.apifox.com/link/project/4790544/apis/api-224339957
+export const updateGlobalNoticeApi = (notice_id: number) => {
+    return server.post({
+        url: "/v1/api/front/notice_user_receipt",
+        data: { notice_id },
+    });
+};
+// 用户站内信回执
+// POST /v1/api/front/letter_user_receipt
+// 接口ID:224339829
+// 接口地址:https://app.apifox.com/link/project/4790544/apis/api-224339829
+
+export const updateUserNoticeApi = (letter_id: number) => {
+    return server.post({
+        url: "/v1/api/front/letter_user_receipt",
+        data: { letter_id },
+    });
+};

+ 6 - 0
src/api/summary.ts

@@ -46,6 +46,12 @@ export interface UserCommissionStatistics {
      * 提现佣金
      */
     withdrawal_commissions?: number;
+    /**
+     * 是否能领取
+     */
+    enable_receive: boolean;
+    max_value: number;
+    min_value: number;
 }
 export const getCommissionApi = () => {
     return server.post<UserCommissionStatistics>({

+ 278 - 86
src/app/[locale]/(TabBar)/(entire)/promo/Popup.tsx

@@ -1,94 +1,286 @@
 "use client";
-import Money1 from "/public/9f/money1.png";
-
-console.log(`🚀🚀🚀🚀🚀-> in Popup.tsx on 5`, Money1.src);
-class CanvasDrawer {
-    private element: HTMLCanvasElement;
-    private context: CanvasRenderingContext2D;
-    private img = new Image();
-
-    width = 77;
-    height = 47;
-
-    redpackId = 1; // 红包标识
-    x = 100; // 红包的横坐标
-    y = 200; // 红包的竖坐标
-    speed = 10; // 下降的速度,创建后则确定
-    angle = 2; // 渲染的角度
-    ratio = 0.8; // 放大的系数
-
-    constructor(element: HTMLCanvasElement) {
-        this.element = element;
-        this.context = element.getContext("2d")!;
-        this.element.width = document.getElementById("app")!.clientWidth;
-        this.element.height = document.getElementById("app")!.clientHeight;
-
-        const random = Math.random();
-        const angle = ((random * 30 + 10) * Math.PI) / 180; // 随机一个旋转偏移角度
-        this.angle = random < 0.5 ? angle : 0 - angle; // 随机向左或者向右旋转
-        this.ratio = random * 0.4 + 0.8; // 随机放大的效果,0.8~1.2倍之间
-
-        this.init();
-    }
-    init() {
-        setInterval(() => {
-            this.draw();
-        }, 50);
-    }
-    draw() {
-        const { width, height, context } = this;
-        if (this.y < this.element.height) {
-            context.clearRect(0, 0, this.element.width, this.element.height);
-            const nextX = this.x;
-            const nextY = this.y + this.speed;
-
-            const img = new Image();
-            img.src = "/9f/money1.png";
-            // img.onload = () => {
-            context.save();
-            context.beginPath();
-            context.rect(nextX, nextY, width, height);
-            context.translate(nextX + width / 2, nextY + height / 2);
-            context.scale(this.ratio, this.ratio);
-            context.rotate(this.angle);
-            context.drawImage(img, nextX, nextY, width, height);
-            context.strokeStyle = "#fff";
-            context.stroke();
-            context.restore();
-            // };
-
-            this.y = nextY;
-            // this.context?.clearRect(0, 0, width, height);
-        } else {
-            console.log(`🚀🚀🚀🚀🚀-> in Popup.tsx on 70`);
-        }
-    }
+
+import { Mask } from "antd-mobile";
+import clsx from "clsx";
+import Image from "next/image";
+import { Fragment, useEffect, useRef, useState } from "react";
+import styles from "./style.module.scss";
+
+const randomX = (len: number) => {
+    return Math.floor(Math.random() * len);
+};
+const mockData = Array(500)
+    .fill(0)
+    .map((item) => {
+        return {
+            phone: `55****${(randomX(99) + "").padEnd(2, "0")}`,
+            num: `${(Math.random() * 20).toFixed(2)}`,
+            time: "11:00",
+        };
+    });
+function getRandom(min: number, max: number) {
+    const floatRandom = Math.random();
+
+    const difference = max - min;
+
+    // 介于 0 和差值之间的随机数
+    const random = Math.round(difference * floatRandom);
+
+    return random + min;
 }
+/**
+ * @description 描述
+ */
+type DescProps = {
+    onClose: () => void;
+};
+const Desc = (props: DescProps) => {
+    const { onClose } = props;
+    const [activeTab, setActiveTab] = useState(0);
+    const tabs = [{ text: "Vezes de evento participado" }, { text: "Consulta de registo levado" }];
+    return (
+        <div className={"absolute top-[30%] w-[100%]"}>
+            <div className={"absolute -top-[1.3rem] left-1/2 w-[3.3rem] -translate-x-1/2"}>
+                <img src="/9f/red-header.png" alt="" />
+                <div
+                    className={
+                        "h-[0.2rem] w-[0.2rem] " + " absolute bottom-[20px] right-[30px]" + " "
+                    }
+                    onClick={onClose}
+                >
+                    <Image src={"/9f/close.png"} alt={"close"} width={25} height={25} />
+                </div>
+            </div>
+            <div
+                className={"mx-auto w-[2.9rem] bg-[#ff9417] px-[0.16rem] pb-[0.12rem] pt-[0.44rem]"}
+            >
+                <img src="/9f/red-title.png" alt="" className={"mx-auto w-[90%]"} />
+                <div className={"mt-[0.0694rem] rounded-[3px] bg-primary-color"}>
+                    <div className={"flex h-[0.54rem] justify-between text-center text-[0.15rem]"}>
+                        {tabs.map((item, index) => {
+                            return (
+                                <Fragment key={index}>
+                                    <span
+                                        onClick={() => setActiveTab(index)}
+                                        className={`flex h-[100%] items-center ${index === activeTab ? "bg-[#8b3500] text-[#5f2600]" : ""}`}
+                                    >
+                                        {item.text}
+                                    </span>
+                                </Fragment>
+                            );
+                        })}
+                    </div>
+                    {/*  动态内容 */}
+                    <div className={"h-[3rem] overflow-y-scroll p-[0.1rem]"}>
+                        <div
+                            className={
+                                "flex items-center rounded-[0.1rem] bg-[#d45300] p-[10px] font-bold"
+                            }
+                        >
+                            <Image
+                                className={"h-[0.43rem] w-[0.7rem]"}
+                                width={80}
+                                height={40}
+                                src="/9f/wallet.png"
+                                alt=""
+                            />
+                            <div className={"text-center"}>
+                                <h2> Tempo regressivo </h2>
+                                <p className={"text-[#fe0]"}>Começa amanhã às 11:00</p>
+                            </div>
+                        </div>
+
+                        {!activeTab ? (
+                            <>
+                                <Image
+                                    src={"/9f/6xtime.png"}
+                                    className={"mt-[0.1rem] h-[0.7rem] w-[100%] rounded-[0.1rem]"}
+                                    width={600}
+                                    height={100}
+                                    alt={"time"}
+                                ></Image>
+
+                                <Image
+                                    src={"/9f/3xtime.png"}
+                                    className={"mt-[0.1rem] h-[0.55rem] w-[100%] rounded-[0.1rem]"}
+                                    width={600}
+                                    height={80}
+                                    alt={"time"}
+                                ></Image>
+                            </>
+                        ) : (
+                            <>
+                                <div
+                                    className={"mt-[0.1rem] rounded-[0.1rem] bg-[#d45300] p-[10px]"}
+                                >
+                                    <p className={"text-[#fe0]"}>Registro persoal</p>
+                                    <div
+                                        className={
+                                            "grid grid-cols-3 text-center" +
+                                            " items-center text-[0.1rem]"
+                                        }
+                                    >
+                                        <span>Nome do jogo</span>
+                                        <span>Coleta cumulativa</span>
+                                        <span>Participação total</span>
+                                    </div>
+
+                                    <div
+                                        className={
+                                            "grid grid-cols-3 text-center" +
+                                            " items-center text-[0.12rem] font-bold"
+                                        }
+                                    >
+                                        <span>55****26</span>
+                                        <span>R$ 100.00</span>
+                                        <span>1</span>
+                                    </div>
+                                </div>
+                                <div
+                                    className={"mt-[0.1rem] rounded-[0.1rem] bg-[#d45300] p-[10px]"}
+                                >
+                                    <p className={"text-[#fe0]"}>Lista dos vencedores</p>
+                                    <div
+                                        className={
+                                            "grid grid-cols-3 text-center" +
+                                            " items-center text-[0.1rem]"
+                                        }
+                                    >
+                                        <span>ID do papel</span>
+                                        <span>Valor obtido</span>
+                                        <span>Hora obtida</span>
+                                    </div>
+                                    <div className={`h-[1rem] overflow-hidden`}>
+                                        {mockData.map((item, index) => {
+                                            return (
+                                                <div
+                                                    key={index}
+                                                    className={`grid grid-cols-3 items-center text-center text-[0.12rem] font-bold ${styles.scrollAnimation}`}
+                                                >
+                                                    <span>{item.phone}</span>
+                                                    <span>R$ {item.num}</span>
+                                                    <span>{item.time}</span>
+                                                </div>
+                                            );
+                                        })}
+                                    </div>
+                                </div>
+                            </>
+                        )}
+
+                        <ul className={"mt-[0.1rem] text-[0.1rem]"}>
+                            <li>
+                                ·Cada sessão de chuva de dinheiro é distribuída gratuitamente por
+                                R$100.000.
+                            </li>
+                            <li>
+                                ·Valor máximo de queda em dinheiro: Cada sessão de chuva de dinheiro
+                                é distribuída gratuitamente.
+                            </li>
+                            <li>·Membros recarregados podem reivindicar gratuitamente.</li>
+                            <li>
+                                ·O dinheiro recebido pode ser utilizado para jogar ou sacar
+                                diretamente.
+                            </li>
+                            <li>
+                                ·Quanto for maior o nível de associação VIP, será maior o valor
+                                recebido.
+                            </li>
+                        </ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+/**
+ * @description 可领取红包
+ */
+const ReceivePackage = () => {
+    const [visible, setVisible] = useState(false);
+    const cardClass = clsx(visible ? styles.cardReverse : "", styles.cardBack);
+    return (
+        <div className={"absolute left-1/2 top-[40%] -translate-x-1/2"}>
+            <div className={styles.card}>
+                <div className={styles.cardInner}>
+                    <div className={styles.cardFront}>
+                        <p onClick={() => setVisible(true)}>Front Side</p>
+                    </div>
+                    <div className={cardClass}>
+                        <p>Back Side</p>
+                    </div>
+                </div>
+            </div>
+        </div>
+    );
+};
+
+const images = Array(10)
+    .fill(0)
+    .map(() => ({ src: `/9f/money${Math.floor(Math.random() * 3) + 1}.png` }));
+
+const FallAnimation = () => {
+    const fallContentRef = useRef<HTMLDivElement>(null);
+
+    const totalPackets = 200;
+    const timer = useRef<NodeJS.Timer>(null);
+    const total = useRef(0);
+    const createPacket = (xPoint: number) => {
+        const packetWrapperEl = document.createElement("div");
+        packetWrapperEl.classList.add(styles.packetWrapper);
+        packetWrapperEl.style.left = xPoint + "px";
+        packetWrapperEl.style.width = `77px`;
+        packetWrapperEl.style.height = `44px`;
+        packetWrapperEl.style.transform = `scale(0.8) `;
+
+        const packetEl = document.createElement("img");
+        packetEl.classList.add(styles.packet);
+
+        packetEl.style.width = `${getRandom(44, 77)}`;
+        packetEl.style.height = `${getRandom(44, 77)}`;
+
+        packetEl.src = `/9f/money${Math.floor(Math.random() * 3) + 1}.png`;
 
+        setInterval(() => {
+            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)}) `;
+        }, 1000);
+
+        packetWrapperEl.appendChild(packetEl);
+        fallContentRef.current?.appendChild(packetWrapperEl);
+    };
+
+    useEffect(() => {
+        // @ts-ignore
+        timer.current = setInterval(() => {
+            if (total.current >= totalPackets - 1) {
+                clearInterval(Number(timer.current));
+                return;
+            }
+            total.current += 1;
+            createPacket(Math.random() * fallContentRef.current?.clientWidth! || 500);
+        }, 200);
+        return () => {
+            clearInterval(Number(timer.current));
+        };
+    }, []);
+    return (
+        <div
+            ref={fallContentRef}
+            style={{ height: "100dvh" }}
+            className={"h-dvh overflow-hidden"}
+        ></div>
+    );
+};
 const Popup = () => {
-    const images = [
-        { src: "/9f/money2.png" },
-        { src: "/9f/money1.png" },
-        { src: "/9f/money3.png" },
-    ];
+    const [maskVisible, setMaskVisible] = useState(true);
     return (
-        <div></div>
-        // <Mask visible={true}>
-        //     <AnimatePresence>
-        //         {images.map((image, index) => {
-        //             return (
-        //                 <motion.img
-        //                     key={image.src}
-        //                     src={image.src}
-        //                     initial={{ y: 300, opacity: 0 }}
-        //                     animate={{ y: 0, opacity: 1 }}
-        //                     exit={{ y: -300, opacity: 0 }}
-        //                 />
-        //             );
-        //         })}
-        //     </AnimatePresence>
-        //     {/*<canvas id={"canvas"} className={"fixed left-0 top-0"}></canvas>*/}
-        // </Mask>
+        <Mask visible={maskVisible}>
+            <FallAnimation />
+
+            <Desc onClose={() => setMaskVisible(false)} />
+
+            {/*<ReceivePackage />*/}
+        </Mask>
     );
 };
 

+ 93 - 0
src/app/[locale]/(TabBar)/(entire)/promo/style.module.scss

@@ -0,0 +1,93 @@
+@keyframes smoothscroll {
+  0% {
+    transform: translateY(0);
+  }
+
+  100% {
+   transform: translateY(-20rem);
+  }
+}
+
+.scrollAnimation {
+  padding: 3px 0;
+  animation: smoothscroll 30s linear infinite;
+}
+
+
+/* From Uiverse.io by vamsidevendrakumar */
+.card {
+  width: 300px;
+  height: 200px;
+  perspective: 1000px;
+}
+
+.cardInner {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  transform-style: preserve-3d;
+  transition: transform 0.999s;
+}
+
+.card:hover .cardInner {
+  transform: rotateY(180deg);
+}
+
+.cardFront,
+.cardBack {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  backface-visibility: hidden;
+}
+
+.cardFront {
+  background-color: #6A2C70;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  border: 10px solid #6A2C70;
+  border-radius: 10px;
+  justify-content: center;
+  font-size: 24px;
+  transform: rotateY(0deg);
+}
+
+.cardBack {
+  background-color: #F08A5D;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  border: 10px solid #F08A5D;
+  border-radius: 10px;
+  justify-content: center;
+  font-size: 24px;
+  transform: rotateY(180deg);
+}
+
+.packetWrapper {
+  position: absolute;
+  top: 0;
+  left: 0;
+  transform: translateY(-100%);
+  animation: down 8s linear infinite;
+  font-size: 0;
+}
+
+.packet{
+  width: 100%;
+  height: 100%;
+  position: relative;
+  transition: all 3s ease;
+  transform: rotate(0);
+}
+
+@keyframes down {
+  0% {
+    transform: translateY(-100%);
+  }
+
+  100% {
+    transform: translateY(100vh);
+  }
+}

+ 12 - 1
src/app/[locale]/(TabBar)/(ordinary)/profile/component/ItemCom/index.tsx

@@ -1,5 +1,6 @@
 "use client";
 import { Link } from "@/i18n";
+import { useGlobalNoticeStore } from "@/stores/useGlobalNoticeStore";
 import { Badge } from "antd-mobile";
 import { useTranslations } from "next-intl";
 import { FC } from "react";
@@ -16,6 +17,10 @@ export interface ItemComProps {
 
 const ItemCom: FC<ItemComProps> = ({ type = "login" }) => {
     const t = useTranslations("ProfilePage");
+
+    const { unread } = useGlobalNoticeStore((state) => ({
+        unread: state.unread,
+    }));
     const links = [
         { label: "gratis", desc: "gratisDesc", icon: "", url: "/", content: null },
         {
@@ -46,7 +51,13 @@ const ItemCom: FC<ItemComProps> = ({ type = "login" }) => {
         { label: "league", desc: "", icon: "", url: "/", content: null },
         { label: "instant", desc: "", icon: "", url: "/", content: null },
         { label: "transactions", desc: "", icon: "", url: "/transactions", content: null },
-        { label: "message", desc: "", icon: "", url: "/notification", content: Badge.dot },
+        {
+            label: "message",
+            desc: "",
+            icon: "",
+            url: "/notification",
+            content: unread ? Badge.dot : null,
+        },
         { label: "initial", desc: "", icon: "", url: "/", content: null },
     ];
     return (

+ 5 - 1
src/app/[locale]/(TabBar)/[[...share]]/@actionWidget/Service.tsx

@@ -1,6 +1,7 @@
 "use client";
 import { ServiceTypes } from "@/api/customservice";
 import { Link } from "@/i18n";
+import { useGlobalNoticeStore } from "@/stores/useGlobalNoticeStore";
 import { useSocialStore } from "@/stores/useSocials";
 import { Badge } from "antd-mobile";
 import { useTranslations } from "next-intl";
@@ -22,6 +23,9 @@ const ServiceWidget: FC<Props> = (props) => {
     }, []);
     const t = useTranslations("HomePage");
 
+    const { unread } = useGlobalNoticeStore((state) => ({
+        unread: state.unread,
+    }));
     return (
         <>
             {defaultService && (
@@ -47,7 +51,7 @@ const ServiceWidget: FC<Props> = (props) => {
                     " absolute bottom-[1.44rem] right-[0.12rem] z-50 justify-center rounded-[50%] bg-gradient-to-b from-[#0575e6] to-[#00f260]"
                 }
             >
-                <Badge content={null} style={{ "--top": "12px" }}>
+                <Badge content={!!unread ? unread : null} style={{ "--top": "12px" }}>
                     <i className={"iconfont icon-duanxinguanli text-[0.3rem] text-[#fff]"}></i>
                 </Badge>
             </Link>

+ 3 - 4
src/app/[locale]/(TabBar)/[[...share]]/@popupWidget/page.tsx

@@ -4,14 +4,13 @@ import HomeMessage from "../_home/HomeMessage";
 import HomePromotion from "../_home/HomePromotion";
 const getPromotions = async () => {
     return server
-        .request<PromotionRep[]>({
+        .request<PromotionRep[], { summery: { showType: 1 | 2 } }>({
             url: "/v1/api/front/pop_list",
             method: "POST",
             next: { revalidate: 0 },
         })
         .then((res) => {
-            if (res.code === 200) return res.data;
-            return [];
+            if (res.code === 200) return res;
         });
 };
 
@@ -37,7 +36,7 @@ const Page = async () => {
             {/*站内信*/}
             <HomeMessage notices={notices} />
             {/* 站内弹窗广告  */}
-            <HomePromotion data={promotions} />
+            <HomePromotion data={promotions?.data || []} type={promotions?.summery.showType || 1} />
         </>
     );
 };

+ 3 - 3
src/app/[locale]/(TabBar)/[[...share]]/_home/HomeMessage.tsx

@@ -9,12 +9,11 @@ interface Props {
 }
 
 const HomeMessage: FC<Props> = (props) => {
-    const { notices } = props;
-
-    const { sourceMap, setSourceMap, hasValue } = useGlobalNoticeStore((state) => ({
+    const { sourceMap, setSourceMap, notices, hasValue } = useGlobalNoticeStore((state) => ({
         sourceMap: state.sourceMap,
         setSourceMap: state.setSourceMap,
         hasValue: state.hasValue,
+        notices: state.notices,
     }));
 
     return (
@@ -22,6 +21,7 @@ const HomeMessage: FC<Props> = (props) => {
             <AnimatePresence>
                 {notices.map(
                     (item, index) =>
+                        !item.is_read &&
                         !hasValue(item.id) &&
                         item.is_window !== 2 && (
                             <motion.div

+ 22 - 6
src/app/[locale]/(TabBar)/[[...share]]/_home/HomePromotion.tsx

@@ -4,29 +4,45 @@ import Box from "@/components/Box";
 import { CenterPopup } from "antd-mobile";
 import { FC, useEffect, useState } from "react";
 
+import dayjs from "dayjs";
 import { Pagination } from "swiper/modules";
 import { Swiper, SwiperSlide } from "swiper/react";
 
 interface Props {
-    data: PromotionRep[];
+    data?: PromotionRep[];
+    type?: 1 | 2; // 每天只弹一次 /  每次登录都弹
 }
 
 const HomePromotion: FC<Props> = (props) => {
-    const { data } = props;
-
+    const { data, type } = props;
     const [visible, setVisible] = useState(false);
 
     useEffect(() => {
         const isClosePromotion = sessionStorage.getItem("isClosePromotion");
-        setVisible(!isClosePromotion);
+        // 如果 type 为 1  && 有isNow为true 表示已经弹出,
+        const shouldShowPromotion = () => {
+            if (type === 1) {
+                return !dayjs().isSame(isClosePromotion, "days");
+            } else if (type === 2) {
+                return !isClosePromotion;
+            }
+            return false;
+        };
+        setVisible(shouldShowPromotion());
     }, []);
 
     const closeHandler = () => {
         setVisible(false);
-        sessionStorage.setItem("isClosePromotion", "true");
+        if (type === 1) {
+            sessionStorage.setItem("isClosePromotion", "2024-10-22");
+        }
+
+        if (type === 2) {
+            sessionStorage.setItem("isClosePromotion", "true");
+        }
     };
 
-    if (data.length === 0) return null;
+    if (data && data.length === 0) return null;
     return (
         <div>
             <CenterPopup visible={visible} onMaskClick={closeHandler}>

+ 1 - 1
src/app/[locale]/(enter)/components/Form/index.tsx

@@ -343,7 +343,7 @@ const FormComponent: FC<Props> = (props) => {
                                 { min: 11, message: t("form.cardReg") },
                             ]}
                         >
-                            <Input placeholder={t("form.card")} maxLength={20} type={"number"} />
+                            <Input placeholder={t("form.card")} maxLength={11} type={"number"} />
                         </Form.Item>
 
                         <Form.Item

+ 30 - 9
src/app/[locale]/(navbar)/notification/components/Notices.tsx

@@ -1,34 +1,55 @@
 "use client";
-import { GlobalNoticeRep } from "@/api/home";
+import { GlobalNoticeRep, updateGlobalNoticeApi, updateUserNoticeApi } from "@/api/home";
 import Box from "@/components/Box";
 import Empty from "@/components/Empty";
 import { timeFormat } from "@/utils/methods";
-import { Collapse } from "antd-mobile";
+import { Badge, Collapse } from "antd-mobile";
 import { useLocale } from "next-intl";
+import { useState } from "react";
 import style from "./style.module.scss";
 interface Props {
     data: GlobalNoticeRep[];
+    type: "system" | "user";
 }
 const SystemMessage = (props: Props) => {
-    const { data } = props;
+    const { data, type } = props;
     const locale = useLocale();
+    const [noticesData, setNoticesData] = useState<GlobalNoticeRep[]>(data);
+
     const collapseChange = (active: string | null) => {
-        console.log(`🚀🚀🚀🚀🚀-> in Notices.tsx on 16`, active);
+        if (!active) return;
+        const isRead = noticesData.find((item) => item.id === +active)?.is_read;
+        if (isRead) return;
+        const newNotices = noticesData.map((item) => {
+            if (item.id === +active && !item.is_read) {
+                return { ...item, is_read: true };
+            } else {
+                return item;
+            }
+        });
+        if (type === "system") {
+            updateGlobalNoticeApi(+active).then((r) => {});
+        } else {
+            updateUserNoticeApi(+active);
+        }
+        setNoticesData(newNotices);
     };
-    if (!data.length) return <Empty />;
+    if (!noticesData.length) return <Empty />;
     return (
         <div className={style.messageCollapse}>
             <Collapse accordion onChange={collapseChange}>
-                {data.map((notice, index) => (
+                {noticesData.map((notice, index) => (
                     <Collapse.Panel
                         key={`${notice.id}`}
                         title={
                             <section>
                                 <header className={"flex items-center"}>
                                     <h6 className={""}>{notice.content?.title}</h6>
-                                    <div
-                                        className={`ml-[0.1rem] h-[0.06rem] w-[0.06rem] rounded bg-[#f0dc00]`}
-                                    ></div>
+                                    <Badge
+                                        className={`ml-[0.1rem] h-[0.06rem] w-[0.06rem]`}
+                                        style={{ "--color": "#f0dc00" }}
+                                        content={!notice.is_read ? Badge.dot : ""}
+                                    ></Badge>
                                 </header>
                                 <p className={"text-[12px] text-[#64676d]"}>
                                     {notice.send_time

+ 2 - 2
src/app/[locale]/(navbar)/notification/page.tsx

@@ -27,13 +27,13 @@ const Notification = async () => {
             id: 1,
             name: t("systemMessage"),
             content: 0,
-            render: <Notices data={systemNotices.data} />,
+            render: <Notices data={systemNotices.data} type={"system"} />,
         },
         {
             id: 2,
             name: t("personalMessage"),
             content: 0,
-            render: <Notices data={userNotices.data} />,
+            render: <Notices data={userNotices.data} type={"user"} />,
         },
     ];
     return (

+ 62 - 65
src/app/[locale]/affiliate/summary/page.tsx

@@ -4,8 +4,6 @@ import {
     getRegisterCountApi,
     getTotalCountApi,
     getWithdrawalApi,
-    UserAgentToDayInfo,
-    UserCommissionStatistics,
 } from "@/api/summary";
 import { useUserInfoStore } from "@/stores/useUserInfoStore";
 import { useRequest } from "ahooks";
@@ -34,84 +32,72 @@ const App: FC<Props> = (props) => {
     const shareUrl = `${BASE_URL}/${locale}/${userInfo ? userInfo.referrer_code : "xxxxxx"}`;
     // 轮询时间
     const TIME = 180000;
-    const [todayData, setTodayData] = useState<UserAgentToDayInfo>({
-        commissar: 0,
-        effective_amount: 0,
-        recharge_user_count: 0,
-        reg_count: 0,
-    });
     const getUserMoneyHandler = () => {
         if (token) {
-            return getRegisterCountApi();
+            return getRegisterCountApi().then((res) => {
+                if (res.code === 200) return res.data;
+            });
         }
-        return Promise.reject();
+        return Promise.resolve({
+            commissar: 0,
+            effective_amount: 0,
+            recharge_user_count: 0,
+            reg_count: 0,
+        });
     };
 
-    useRequest(getUserMoneyHandler, {
+    const { data: todayData } = useRequest(getUserMoneyHandler, {
         pollingInterval: TIME,
         pollingWhenHidden: true,
         pollingErrorRetryCount: 3,
         staleTime: 5000,
         refreshDeps: [token],
-        onError: (error) => {},
-        onSuccess: (res) => {
-            if (res.data) {
-                setTodayData(res.data);
-            }
-        },
     });
 
-    const [totalData, setTotalData] = useState<UserAgentToDayInfo>({
-        commissar: 0,
-        effective_amount: 0,
-        recharge_user_count: 0,
-        reg_count: 0,
-    });
     const getTotalCount = () => {
         if (token) {
-            return getTotalCountApi();
+            return getTotalCountApi().then((res) => {
+                if (res.code === 200) return res.data;
+            });
         }
-        return Promise.reject();
+        return Promise.resolve({
+            commissar: 0,
+            effective_amount: 0,
+            recharge_user_count: 0,
+            reg_count: 0,
+        });
     };
 
-    useRequest(getTotalCount, {
+    const { data: totalData } = useRequest(getTotalCount, {
         pollingInterval: TIME,
         pollingWhenHidden: true,
         pollingErrorRetryCount: 3,
         staleTime: 5000,
         refreshDeps: [token],
-        onError: (error) => {},
-        onSuccess: (res) => {
-            if (res.data) {
-                setTotalData(res.data);
-            }
-        },
     });
 
-    const [commissionData, setCommissionData] = useState<UserCommissionStatistics>({
-        commissar: 0,
-        level: 0,
-        withdrawal_commissions: 0,
-    });
     const getCommission = () => {
         if (token) {
-            return getCommissionApi();
+            return getCommissionApi().then((res) => {
+                if (res.code === 200) return res.data;
+            });
         }
-        return Promise.reject();
+        return Promise.resolve({
+            commissar: 0,
+            level: 0,
+            withdrawal_commissions: 0,
+            enable_receive: false,
+            min_value: 0,
+            max_value: 0,
+        });
     };
 
-    const { run: commissionRun } = useRequest(getCommission, {
+    const { data: commissionData, run: commissionRun } = useRequest(getCommission, {
         pollingInterval: TIME,
         pollingWhenHidden: true,
         pollingErrorRetryCount: 3,
         staleTime: 5000,
         refreshDeps: [token],
-        onError: (error) => {},
-        onSuccess: (res) => {
-            if (res.data) {
-                setCommissionData(res.data);
-            }
-        },
     });
 
     const handleSlide: any = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
@@ -144,14 +130,25 @@ const App: FC<Props> = (props) => {
     };
 
     const withdrawalHandler = async () => {
-        await getWithdrawalApi({ amount: 10 });
-        // 间隔时间
-        setTimeout(() => {
-            commissionRun();
-        }, 100);
-    };
+        if (commissionData?.commissar) return;
 
-    // @ts-ignore
+        if (
+            commissionData?.enable_receive &&
+            commissionData.commissar! > Math.min(commissionData.min_value, 10)
+        ) {
+            const available =
+                commissionData.max_value > commissionData.commissar!
+                    ? commissionData.max_value
+                    : commissionData.commissar;
+            await getWithdrawalApi({ amount: available! });
+            // 间隔时间
+            setTimeout(() => {
+                commissionRun();
+            }, 100);
+        } else {
+            Toast.show(t("receive"));
+        }
+    };
     return (
         <div className="content">
             <div className="summary referral-router-view">
@@ -165,24 +162,24 @@ const App: FC<Props> = (props) => {
                     <div className="cardMian">
                         <ul className="statistics-card">
                             <li>
-                                <p className="num">{todayData.reg_count}</p>
+                                <p className="num">{todayData?.reg_count}</p>
                                 <p> {t("Inscrições")} </p>
                             </li>
                             <li>
-                                <p className="num">{todayData.recharge_user_count}</p>
+                                <p className="num">{todayData?.recharge_user_count}</p>
                                 <p> {t("Novos")} </p>
                             </li>
                             <li>
                                 <p className="num">
                                     <span className={"mr-[5px]"}>{t("R$")}</span>
-                                    {todayData.effective_amount}
+                                    {todayData?.effective_amount}
                                 </p>
                                 <p> {t("Aposta")} </p>
                             </li>
                             <li>
                                 <p className="num">
                                     <span className={"mr-[5px]"}>{t("R$")}</span>
-                                    {todayData.commissar}
+                                    {todayData?.commissar}
                                 </p>
                                 <p> {t("Comissão")} </p>
                             </li>
@@ -199,24 +196,24 @@ const App: FC<Props> = (props) => {
                     <div className="cardMian">
                         <ul className="statistics-card">
                             <li>
-                                <p className="num">{totalData.reg_count}</p>
+                                <p className="num">{totalData?.reg_count}</p>
                                 <p> {t("Inscrições")} </p>
                             </li>
                             <li>
-                                <p className="num">{totalData.recharge_user_count}</p>
+                                <p className="num">{totalData?.recharge_user_count}</p>
                                 <p> {t("Jogadores")} </p>
                             </li>
                             <li>
                                 <p className="num">
                                     <span className={"mr-[5px]"}>{t("R$")}</span>
-                                    {totalData.effective_amount}
+                                    {totalData?.effective_amount}
                                 </p>
                                 <p> {t("ApostaTotal")} </p>
                             </li>
                             <li>
                                 <p className="num">
                                     <span className={"mr-[5px]"}>{t("R$")}</span>
-                                    {totalData.commissar}
+                                    {totalData?.commissar}
                                 </p>
                                 <p>RS {t("Comissão")} </p>
                             </li>
@@ -234,7 +231,7 @@ const App: FC<Props> = (props) => {
                         <div className="vip">
                             <div className="level">
                                 <span className="iconfont icon-vip"></span>
-                                <span className="levelNum">{commissionData.level}</span>
+                                <span className="levelNum">{commissionData?.level}</span>
                             </div>
                             <div>
                                 {t("Nível")}
@@ -247,7 +244,7 @@ const App: FC<Props> = (props) => {
                                     <p className="num">
                                         <span>{t("R$")}</span>
                                         <span className="cash">
-                                            {commissionData.withdrawal_commissions}
+                                            {commissionData?.withdrawal_commissions}
                                         </span>
                                     </p>
                                     <p> {t("TotalPago")} </p>
@@ -255,14 +252,14 @@ const App: FC<Props> = (props) => {
                                 <li>
                                     <p className="num">
                                         <span>{t("R$")}</span>
-                                        <span className="cash">{commissionData.commissar}</span>
+                                        <span className="cash">{commissionData?.commissar}</span>
                                     </p>
                                     <p> {t("Não")} </p>
                                 </li>
                             </ul>
                             <div className="wallet">
                                 <div className="btn" onClick={withdrawalHandler}>
-                                    {t("TRANSFERIR")}{" "}
+                                    {t("TRANSFERIR")}
                                 </div>
                                 <div className="tip">
                                     <span className="iconfont icon-tishi1"></span>

+ 0 - 6
src/app/[locale]/layout.tsx

@@ -35,12 +35,6 @@ export const metadata = {
     },
 };
 
-console.log(
-    `🚀🚀🚀🚀🚀-> in layout.tsx on 38`,
-    process.env.NODE_ENV,
-    process.env.NEXT_PUBLIC_BASE_URL
-);
-
 export default async function LocaleLayout({
     children,
     params: { locale },

+ 31 - 19
src/components/Footer/index.tsx

@@ -1,12 +1,16 @@
 "use client";
-import { usePathname, useRouter } from "@/i18n";
+import { getGlobalNoticeApi } from "@/api/home";
+import { Link, usePathname, useRouter } from "@/i18n";
 import { getToken } from "@/utils/Cookies";
 import { Badge } from "antd-mobile";
 import clsx from "clsx";
 import { useTranslations } from "next-intl";
-import { FC, ReactNode, useEffect, useState } from "react";
+import { ChangeEvent, FC, ReactNode } from "react";
 import "./style.scss";
 
+import { useGlobalNoticeStore } from "@/stores/useGlobalNoticeStore";
+import { useRequest } from "ahooks";
+
 /**
  * @description 底部Tab组件
  * @param children 插槽内容
@@ -16,6 +20,7 @@ export interface FooterProps {
     children?: ReactNode;
 }
 
+const whitRouter = ["/deposit", "/profile"];
 const tabList = [
     {
         iconSpanName: "icon-home",
@@ -48,36 +53,43 @@ const Footer: FC = () => {
     const t = useTranslations("navBar");
 
     const pathname = usePathname();
-    const [tabActiveName, setTabActiveName] = useState("/");
-
-    useEffect(() => {
-        setTabActiveName(pathname);
-    }, [pathname]);
 
     const router = useRouter();
 
-    const goPage = (path = "/") => {
+    const goPage = (event: ChangeEvent<any>, path = "/") => {
+        event.preventDefault();
         if (!token && (path == "/deposit" || path == "/profile")) {
             router.push(`/login?redirect=${path}`);
             return;
         }
-        setTabActiveName(path);
         router.push(path);
     };
-    // const { data, run, cancel } = useRequest(, {
-    //   pollingInterval: 3000,
-    // });
+
+    const { unread, setNotices } = useGlobalNoticeStore((state) => ({
+        unread: state.unread,
+        setNotices: state.setNotices,
+    }));
+
+    useRequest(getGlobalNoticeApi, {
+        pollingInterval: 5000,
+        pollingErrorRetryCount: 3,
+        pollingWhenHidden: false,
+        onSuccess: (data) => {
+            setNotices(data?.data || [], data?.summery.unread || 0);
+        },
+    });
 
     return (
         <footer className={"footer-placeholder"}>
             <div className="footer-box">
-                <ul>
+                <div className={"footer-item"}>
                     {tabList.map((item, index) => {
                         return (
-                            <li
-                                className={clsx(item.path == tabActiveName && "active")}
-                                onClick={() => goPage(item.path)}
+                            <Link
+                                href={item.path}
+                                onClick={(event) => goPage(event, item.path)}
                                 key={index}
+                                className={`footer-item-column ${item.path === pathname ? "active" : ""}`}
                             >
                                 <div className="icon-box">
                                     {index == 2 ? (
@@ -86,7 +98,7 @@ const Footer: FC = () => {
                                             <i className="icon-rs"></i>
                                         </>
                                     ) : (
-                                        <Badge content={index === 4 ? Badge.dot : null}>
+                                        <Badge content={index === 4 && unread ? Badge.dot : null}>
                                             <span
                                                 className={clsx("iconfont", item.iconSpanName)}
                                             ></span>
@@ -95,10 +107,10 @@ const Footer: FC = () => {
                                     {index == 3 && <b className="icon-hot"></b>}
                                 </div>
                                 <label>{t(item.label)}</label>
-                            </li>
+                            </Link>
                         );
                     })}
-                </ul>
+                </div>
             </div>
         </footer>
     );

+ 2 - 2
src/components/Footer/style.scss

@@ -6,7 +6,7 @@
   max-width: $-body-width;
   width: 100%;
   height: $-footer-height;
-  ul {
+  .footer-item {
     width: 100%;
     height: 100%;
     padding-bottom: 0.12rem;
@@ -14,7 +14,7 @@
     flex-direction: row;
     align-items: flex-end;
 
-    li {
+    .footer-item-column {
       position: relative;
       z-index: 10;
       width: calc(100% / 5);

+ 12 - 1
src/stores/useGlobalNoticeStore.ts

@@ -1,17 +1,24 @@
-import { NoticeRep } from "@/api/home";
+import { NoticeRep, updateGlobalNoticeApi } from "@/api/home";
+import { Result } from "@/types";
 import { create } from "zustand";
 
 interface State {
     sourceMap: Map<number, NoticeRep>;
+    unread: number; // 未读数量
+    notices: NoticeRep[]; // 总数据
 }
 
 interface Action {
     setSourceMap: (key: number, value: NoticeRep) => void;
     hasValue: (key: number) => boolean;
+    setNotices: (notices: NoticeRep[], unread: number) => void;
+    setReadNotices: (id: number) => Promise<Result<any>>;
 }
 
 const initialState: State = {
     sourceMap: new Map(),
+    unread: 0,
+    notices: [],
 };
 
 export const useGlobalNoticeStore = create<State & Action>()((set, get) => {
@@ -25,6 +32,10 @@ export const useGlobalNoticeStore = create<State & Action>()((set, get) => {
             }),
 
         hasValue: (key) => get().sourceMap.has(key),
+        setNotices: (notices, unread: number) => set((state) => ({ ...state, notices, unread })),
+        setReadNotices: (id: number) => {
+            return updateGlobalNoticeApi(id);
+        },
         reset: () => set(initialState),
     };
 });