Before пре 11 месеци
родитељ
комит
69ed911c78

+ 3 - 1
messages/br.json

@@ -11,12 +11,14 @@
     "aml": "AML Policy",
     "terms": "Terms & Conditions",
     "kyc":  "KYC Policy ",
+
     "self": "Self Exclusion Policy",
     "underage":  "Underage Gaming Policy (18+ to play clause)",
     "responsible": "Responsible Gaming Policy",
     "esportes":  "Esportes Regras",
     "search": "Procure Jogos ou Provedores",
-    "searchTips": "A pesquisa requer pelo menos 3 caracteres",
+    "searchTips": "A pesquisa requer pelo menos 1 caracteres",
+    "searchButton": "Carregar Mais",
 
     "licencia":  "LICENÇA",
     "desc": "XQEsoft.com  é operada conjunta pela Dubet n.v. e pela MLsoft, número de registro da empresa 142919, com endereço registrado em Zuikertuintjeweg Z/N (Zuikertuin Tower) Curação e é licenciada e autorizada pelo governo de Curação. A XQEsoft.com  opera sob a Master License of Gaming Services Provider, N.V. Número da Licença: ",

+ 36 - 18
src/api/home.ts

@@ -1,5 +1,6 @@
 import { server } from "@/utils/server";
 
+export type ActionType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | undefined;
 /**
  * entity.HomeCategory
  */
@@ -177,7 +178,7 @@ export interface BannerRep {
     /**
      * 点击功能, 1:无功能,2:跳转外部链接,3:跳转页面,4:跳转弹窗,5:跳转打开游戏,6:跳转Live Chat客服,7:跳转内部文档
      */
-    action_type?: number;
+    action_type?: ActionType;
     /**
      * '轮播地址({"en": "https://www.baidu.com", "zh": "https://www.baidu.com"})',
      */
@@ -195,27 +196,44 @@ export interface BannerRep {
      */
     sort?: number;
 }
+
+//获取banner
 export const getBannerApi = (data: any) => {
     return server.post<any>({
         url: "/v1/api/front/banner_list",
         data,
     });
 };
-export interface NoticeRep {
-    /**
-     * 点击参数
-     */
-    action_params?: string;
-    /**
-     * 点击功能, 1:无功能,2:跳转外部链接,3:跳转页面,4:跳转弹窗,5:跳转打开游戏,6:跳转Live Chat客服,7:跳转内部文档
-     */
-    action_type?: number;
-    /**
-     * 文本信息
-     */
-    content?: string;
-    /**
-     * 排序
-     */
-    sort?: number;
+export interface NoticeRep extends BannerRep {}
+
+export interface PromotionRep extends Omit<BannerRep, "content"> {
+    content: { title: string; image: string };
 }
+export type SearchProps = {
+    search_game_name: string;
+    current_page: number;
+    page_size: number;
+    use_page: boolean;
+};
+type Page = {
+    page: {
+        is_end: boolean; // 是否最后一页 ture:是 false:否
+    };
+};
+// 获取游戏列表-(跳转 搜索)
+export const searchGameListApi = (props: SearchProps) => {
+    return server.post<GameListRep[], Page>({
+        url: "/v1/api/front/game_list_search",
+        data: props,
+    });
+};
+//获取公告系统
+export interface GlobalNoticeRep extends Omit<BannerRep, "content"> {
+    content?: { image?: string; lang?: string; text?: string; title?: string; word?: string };
+}
+export const getGlobalNoticeApi = () => {
+    return server.post<GlobalNoticeRep[]>({
+        url: "/v1/api/front/notice_list",
+        data: {},
+    });
+};

+ 25 - 11
src/app/[locale]/(extend)/_home/HomeCard.tsx

@@ -1,20 +1,34 @@
+"use client";
+import { BannerRep } from "@/api/home";
+import Box from "@/components/Box";
 import { FC, PropsWithChildren } from "react";
+import { Swiper, SwiperSlide } from "swiper/react";
 
-interface Props {}
-
-const urls = [
-    "https://9f.com/images/homePage/card/firstdeposit3.png",
-    "https://9f.com/images/homePage/card/cashwheel3.png",
-    "https://9f.com/images/homePage/card/telegram3.png",
-];
+interface Props {
+    banners: BannerRep[];
+}
 
 const HomeCard: FC<PropsWithChildren<Props>> = (props) => {
+    const { banners = [] } = props;
     return (
-        <div className={"my-[0.08rem] grid grid-cols-3 justify-between gap-x-2"}>
-            {urls.map((url, index) => (
-                <img src={url} alt="" className={"rounded-[0.04rem]"} key={index} />
+        <Swiper slidesPerView={3} spaceBetween={10}>
+            {banners?.map((banner, index) => (
+                <SwiperSlide key={index} className={"my-[0.08rem]"}>
+                    <Box
+                        className={"h-[1.0903rem] w-[1.2153rem]"}
+                        none
+                        action={banner.action_type}
+                        actionData={banner.action_params}
+                    >
+                        <img
+                            src={banner.content!}
+                            alt=""
+                            className={"h-[100%] w-[100%] rounded-[0.04rem]"}
+                        />
+                    </Box>
+                </SwiperSlide>
             ))}
-        </div>
+        </Swiper>
     );
 };
 

+ 0 - 2
src/app/[locale]/(extend)/_home/HomeGames.tsx

@@ -22,7 +22,6 @@ const HomeGames = (props: Props) => {
     const todoHandler = (item: Category) => {
         router.push(`/gameList/${item.line_config_amount}`);
     };
-
     return (
         <div>
             {groupGames.map((group, index) => {
@@ -38,7 +37,6 @@ const HomeGames = (props: Props) => {
                     );
                 } else {
                     // 厂商
-                    return null;
                     return (
                         <SwiperGroup
                             key={index}

+ 9 - 4
src/app/[locale]/(extend)/_home/HomeMessage.tsx

@@ -1,4 +1,5 @@
 "use client";
+import { useGlobalNoticeStore } from "@/stores/useGlobalNoticeStore";
 import { AnimatePresence, motion } from "framer-motion";
 import { FC, useEffect, useState } from "react";
 
@@ -6,8 +7,14 @@ interface Props {}
 
 const HomeMessage: FC<Props> = (props) => {
     const [visible, setVisible] = useState(true);
-
+    const { getGlobalNotice, topNotices } = useGlobalNoticeStore((state) => ({
+        globalNotices: state.globalNotices,
+        getGlobalNotice: state.getGlobalNotice,
+        topNotices: state.topNotices,
+    }));
     useEffect(() => {
+        getGlobalNotice();
+
         const isCloseMessage = sessionStorage.getItem("isCloseMessage");
         setVisible(!isCloseMessage);
     }, []);
@@ -27,9 +34,7 @@ const HomeMessage: FC<Props> = (props) => {
                         exit={{ opacity: 0 }}
                         transition={{ type: "spring" }}
                     >
-                        <div className={"flex-1"}>
-                            lorem ipsum dolor sit amet, consectetur adipiscing elit.
-                        </div>
+                        <div className={"flex-1"}>{topNotices?.content?.text}</div>
                         <div>
                             <i className={"iconfont icon-guanbi"} onClick={closeHandler}></i>
                         </div>

+ 4 - 1
src/app/[locale]/(extend)/_home/HomeNoticeBar.tsx

@@ -1,5 +1,6 @@
 "use client";
 import { NoticeRep } from "@/api/home";
+import Box from "@/components/Box";
 import { Swiper } from "antd-mobile";
 import { FC } from "react";
 interface Props {
@@ -20,7 +21,9 @@ const HomeNoticeBar: FC<Props> = (props) => {
             >
                 {notices.map((notice, index) => (
                     <Swiper.Item key={index}>
-                        <div>{notice.content}</div>
+                        <Box none action={notice.action_type} actionData={notice.action_params}>
+                            {notice.content}
+                        </Box>
                     </Swiper.Item>
                 ))}
             </Swiper>

+ 75 - 0
src/app/[locale]/(extend)/_home/HomePromotion.tsx

@@ -0,0 +1,75 @@
+"use client";
+import { PromotionRep } from "@/api/home";
+import Box from "@/components/Box";
+import { CenterPopup } from "antd-mobile";
+import { FC, useEffect, useState } from "react";
+
+import { Pagination } from "swiper/modules";
+import { Swiper, SwiperSlide } from "swiper/react";
+
+interface Props {
+    data: PromotionRep[];
+}
+
+const HomePromotion: FC<Props> = (props) => {
+    const { data } = props;
+
+    const [visible, setVisible] = useState(false);
+
+    useEffect(() => {
+        const isClosePromotion = sessionStorage.getItem("isClosePromotion");
+        setVisible(!isClosePromotion);
+    }, []);
+
+    const closeHandler = () => {
+        setVisible(false);
+        sessionStorage.setItem("isClosePromotion", "true");
+    };
+
+    return (
+        <div>
+            <CenterPopup visible={visible} onMaskClick={closeHandler}>
+                <div className={"promotion-swiper relative max-w-[3.139rem]"}>
+                    <div
+                        onClick={closeHandler}
+                        className={
+                            "iconfont icon-guanbi absolute right-[0.1389rem] top-[0.0347rem]" +
+                            " z-20"
+                        }
+                    ></div>
+                    <Swiper
+                        loop
+                        pagination={{ clickable: true }}
+                        spaceBetween={10}
+                        scrollbar={{ draggable: true }}
+                        modules={[Pagination]}
+                        slidesPerView={1}
+                        grabCursor={true}
+                        navigation={true}
+                        className={"min-h-[2.8472rem] rounded-[0.0694rem] bg-[#1c1c1c]"}
+                    >
+                        {data?.map((promotion, index) => (
+                            <SwiperSlide key={index}>
+                                <Box
+                                    action={promotion.action_type}
+                                    actionData={promotion.action_params}
+                                >
+                                    <header className={"pb-[0.0694rem] leading-[0.2778rem]"}>
+                                        {promotion.content.title}
+                                    </header>
+                                    <img
+                                        className={"h-[2.2222rem] w-[100%] pb-[0.1111rem]"}
+                                        src={promotion.content.image}
+                                        alt={promotion.content.title}
+                                    />
+                                </Box>
+                            </SwiperSlide>
+                        ))}
+                    </Swiper>
+                </div>
+            </CenterPopup>
+        </div>
+    );
+};
+
+export default HomePromotion;

+ 109 - 11
src/app/[locale]/(extend)/_home/HomeSearch.tsx

@@ -1,32 +1,82 @@
 "use client";
+import { GameListRep, searchGameListApi, SearchProps } from "@/api/home";
 import Box from "@/components/Box";
-import { Mask, SearchBar } from "antd-mobile";
+import { useSearchStore } from "@/stores/useSearchStore";
+import { debounce } from "@/utils/methods";
+import { Button, createErrorBlock, Mask, SearchBar } from "antd-mobile";
 import { useTranslations } from "next-intl";
-import { FC, PropsWithChildren, useState } from "react";
+import { FC, PropsWithChildren, useRef, useState } from "react";
+import GroupCard from "../../../../components/Card/GroupCard";
 interface Props {}
 
 interface ContentProps {
     closeHandler: () => void;
 }
+
+const ErrorBlock = createErrorBlock({
+    empty: <div className={"iconfont icon-meiyoushuju text-[0.5556rem] text-[#666]"}></div>,
+    default: <div></div>,
+});
 const Content: FC<ContentProps> = (props) => {
     const t = useTranslations("HomePage");
     const { closeHandler } = props;
-    const changeHandler = (value: string) => {};
+    const [games, setGames] = useState<GameListRep[]>([]);
+    const searchStore = useSearchStore();
+    const [visible, setVisible] = useState(false);
+    const params = useRef<SearchProps>({
+        current_page: 1,
+        page_size: 12,
+        use_page: true,
+        search_game_name: "",
+    });
+
+    const getGames = async (): Promise<GameListRep[] | undefined> => {
+        return searchGameListApi(params.current).then((res) => {
+            setVisible(!res.page.is_end);
+            if (res.data) {
+                return res.data;
+            }
+        });
+    };
+    const isPending = () =>
+        games.length === 0 &&
+        params.current.search_game_name === "" &&
+        params.current.current_page === 1;
+    const setSearchValueInit = (value: string) => {
+        params.current.search_game_name = value;
+        getGames().then((data) => data && setGames(data));
+    };
+    const func = debounce(setSearchValueInit, 500);
+    const changeHandler = (value: string) => {
+        if (value) func(value);
+    };
+    const onCancel = () => {
+        closeHandler();
+        params.current.current_page = 1;
+        params.current.search_game_name = "";
+        setGames([]);
+        setVisible(false);
+    };
+
+    const nextHandler = async () => {
+        params.current.current_page++;
+        return getGames().then((data) => data && setGames((games) => games.concat(data)));
+    };
 
-    // @ts-ignore
     return (
-        <Box>
-            <section className={"home-search"}>
+        <Box className={"flex h-[100dvh] flex-col"}>
+            <div className={"home-search flex-shrink-0"}>
                 <SearchBar
                     className={"rounded-[0.05rem] bg-[#191919]"}
                     style={{
                         "--background": "transparent",
                         "--height": "0.3rem",
                     }}
+                    aria-hidden="true"
                     placeholder={t("search")}
                     showCancelButton={() => true}
                     onChange={changeHandler}
-                    onCancel={closeHandler}
+                    onCancel={onCancel}
                 />
                 <div
                     className={
@@ -35,15 +85,63 @@ const Content: FC<ContentProps> = (props) => {
                 >
                     {t("searchTips")}
                 </div>
-            </section>
+
+                {/*<div className={"flex items-center justify-between"}>*/}
+                {/*    <header> Histórico de busca </header>*/}
+                {/*    <i className="iconfont icon-guanbi cursor-pointer text-[0.08rem]"></i>*/}
+                {/*</div>*/}
+                {/*<div className={"mt-[0.05rem] flex"}>*/}
+                {/*    {searchStore.history.map((item, index) => {*/}
+                {/*        return (*/}
+                {/*            <div*/}
+                {/*                className={*/}
+                {/*                    "flex items-center bg-[#1a1a1a]" +*/}
+                {/*                    " text-[#b3b3b3]" +*/}
+                {/*                    " rounded-[0.04rem] px-[0.05rem] py-[0.03rem]"*/}
+                {/*                }*/}
+                {/*                key={index}*/}
+                {/*            >*/}
+                {/*                <span className={"mr-[0.2rem]"}>{item}</span>*/}
+                {/*                <i className="iconfont icon-guanbi cursor-pointer text-[0.08rem]"></i>*/}
+                {/*            </div>*/}
+                {/*        );*/}
+                {/*    })}*/}
+                {/*</div>*/}
+            </div>
+            <div className={"mt-[0.13rem] flex-1 overflow-y-scroll"}>
+                {games.length ? (
+                    <GroupCard data={games} row={1} />
+                ) : (
+                    <ErrorBlock
+                        status={isPending() ? "default" : "empty"}
+                        description={""}
+                        title={isPending() ? "" : "no data"}
+                    />
+                )}
+                <div className={"mt-[0.1rem]"}>
+                    {visible && (
+                        <Button
+                            fill={"none"}
+                            color={"default"}
+                            loading="auto"
+                            onClick={async () => {
+                                await nextHandler();
+                            }}
+                            block={true}
+                            style={{ "--background-color": "#1a1a1a", "--text-color": "#c4c4c4" }}
+                        >
+                            {t("searchButton")}
+                        </Button>
+                    )}
+                </div>
+            </div>
         </Box>
     );
 };
 const HomeSearch: FC<PropsWithChildren<Props>> = (props) => {
     const t = useTranslations("HomePage");
-    const [visible, setVisible] = useState(true);
+    const [visible, setVisible] = useState(false);
     const handler = () => {
-        console.log(`🎯🎯🎯🎯🎯-> in HomeSearch.tsx on 9`, "");
         setVisible(true);
     };
     return (
@@ -58,7 +156,7 @@ const HomeSearch: FC<PropsWithChildren<Props>> = (props) => {
                 <i className={"iconfont icon-sousuo1 mr-[0.0833rem] text-[0.16rem]"}></i>
                 <span className={"text-[0.12rem]"}>{t("search")}</span>
             </div>
-            <Mask visible={visible} opacity={0.9}>
+            <Mask visible={visible} opacity={0.9} getContainer={null}>
                 <Content closeHandler={() => setVisible(false)} />
             </Mask>
         </>

+ 9 - 6
src/app/[locale]/(extend)/_home/HomeSwiper.tsx

@@ -2,6 +2,7 @@
 import { FC } from "react";
 
 import { BannerRep } from "@/api/home";
+import Box from "@/components/Box";
 import { Autoplay, Pagination } from "swiper/modules";
 import { Swiper, SwiperSlide } from "swiper/react";
 interface Props {
@@ -10,7 +11,7 @@ interface Props {
 const HomeSwiper: FC<Props> = (props) => {
     const { banners } = props;
     return (
-        <div style={{ height: "1.86rem" }}>
+        <div style={{ height: "1.86rem" }} className={"home-banner"}>
             <Swiper
                 autoplay={{ delay: 2500 }}
                 pagination={{ clickable: true }}
@@ -22,11 +23,13 @@ const HomeSwiper: FC<Props> = (props) => {
             >
                 {banners.map((banner, index) => (
                     <SwiperSlide key={index}>
-                        <img
-                            src={banner.content}
-                            style={{ height: "1.86rem", width: "100%" }}
-                            alt={"banner"}
-                        />
+                        <Box none action={banner.action_type} actionData={banner.action_params}>
+                            <img
+                                src={banner.content}
+                                style={{ height: "1.86rem", width: "100%" }}
+                                alt={"banner"}
+                            />
+                        </Box>
                     </SwiperSlide>
                 ))}
             </Swiper>

+ 57 - 0
src/app/[locale]/(extend)/layout-copy-1.tsx

@@ -0,0 +1,57 @@
+"use client";
+import { motion, useAnimation } from "framer-motion";
+
+const AnimatedDivs = () => {
+    const controls = useAnimation();
+
+    // Define animation variants
+    const variants = {
+        hidden: { x: 0 },
+        visible: { x: 0 },
+    };
+    const variants2 = {
+        hidden: { x: -385 },
+        visible: { x: 0 },
+    };
+
+    // Define a sequence to stagger animations
+
+    const handler = () => {
+        controls.start((i) => ({
+            x: 385,
+            transition: { duration: 1 },
+        }));
+    };
+
+    return (
+        <div className={"flex"}>
+            {/* First animated div */}
+            <motion.section
+                initial="hidden"
+                animate={controls}
+                variants={variants2}
+                style={{
+                    width: "385px",
+                    height: "100px",
+                    backgroundColor: "red",
+                }}
+            />
+            {/* Second animated div */}
+            <motion.section
+                initial="hidden"
+                animate={controls}
+                variants={variants}
+                onClick={handler}
+                className={"border-1"}
+                style={{
+                    width: "100%",
+                    height: "100px",
+                    flexShrink: 0,
+                    backgroundColor: "blue",
+                }}
+            />
+        </div>
+    );
+};
+
+export default AnimatedDivs;

+ 1 - 1
src/app/[locale]/(extend)/layout.tsx

@@ -9,7 +9,7 @@ export default async function LocaleLayout({
     params: { locale: string };
 }) {
     return (
-        <div className="">
+        <div className={"relative"}>
             <Layout>{children}</Layout>
         </div>
     );

+ 45 - 8
src/app/[locale]/(extend)/page.tsx

@@ -1,5 +1,5 @@
 "use server";
-import { BannerRep, GroupType, NoticeRep } from "@/api/home";
+import { BannerRep, GroupType, NoticeRep, PromotionRep } from "@/api/home";
 import Box from "@/components/Box";
 import HomeActions from "./_home/HomeActions";
 import HomeCard from "./_home/HomeCard";
@@ -7,6 +7,7 @@ import HomeGames from "./_home/HomeGames";
 import HomeMessage from "./_home/HomeMessage";
 import HomeNoticeBar from "./_home/HomeNoticeBar";
 import HomePrize from "./_home/HomePrize";
+import HomePromotion from "./_home/HomePromotion";
 import HomeSearch from "./_home/HomeSearch";
 import HomeSwiper from "./_home/HomeSwiper";
 import HomeTabs from "./_home/HomeTabs";
@@ -17,9 +18,10 @@ const getGames = async (): Promise<GroupType[]> => {
     try {
         const data = await fetch(`${BASE_URL}/v1/api/front/game_list`, {
             method: "POST",
-            body: JSON.stringify({}),
+            body: JSON.stringify({ template_key: "g_temp_9" }),
             next: { revalidate: TIME },
         }).then((res) => res.json());
+        console.log(`🎯🎯🎯🎯🎯-> in page.tsx on 24`, data.data);
         return data.data;
     } catch (err) {
         return [];
@@ -56,19 +58,54 @@ const getNotices = async (): Promise<NoticeRep[]> => {
         return [];
     }
 };
+const getActivities = async (): Promise<BannerRep[]> => {
+    try {
+        const response = await fetch(`${BASE_URL}/v1/api/front/activity_home`, {
+            method: "POST",
+            body: JSON.stringify({}),
+            headers: {
+                language: "zh",
+            },
+            next: { revalidate: TIME },
+        }).then((res) => res.json());
+        return response.data;
+    } catch (err) {
+        return [];
+    }
+};
+
+const getPromotions = async (): Promise<PromotionRep[]> => {
+    try {
+        const response = await fetch(`${BASE_URL}/v1/api/front/pop_list`, {
+            method: "POST",
+            body: JSON.stringify({}),
+            headers: {
+                language: "zh",
+            },
+            next: { revalidate: TIME },
+        }).then((res) => res.json());
+        return response.data;
+    } catch (err) {
+        return [];
+    }
+};
 
 export default async function Page() {
-    const [group = [], banners = [], notices = []] = await Promise.all([
-        getGames(),
-        getBanners(),
-        getNotices(),
-    ]);
+    const [group = [], banners = [], notices = [], activities = [], promotions = []] =
+        await Promise.all([
+            getGames(),
+            getBanners(),
+            getNotices(),
+            getActivities(),
+            getPromotions(),
+        ]);
     return (
         <div>
             <HomeMessage />
+            <HomePromotion data={promotions} />
             <Box>
                 <HomeSwiper banners={banners}></HomeSwiper>
-                <HomeCard></HomeCard>
+                <HomeCard banners={activities}></HomeCard>
                 <HomeNoticeBar notices={notices} />
                 <HomeSearch />
                 <HomePrize></HomePrize>

+ 30 - 0
src/app/[locale]/error.tsx

@@ -0,0 +1,30 @@
+"use client";
+
+import { useEffect } from "react";
+
+export default function Error({
+    error,
+    reset,
+}: {
+    error: Error & { digest?: string };
+    reset: () => void;
+}) {
+    useEffect(() => {
+        // Log the error to an error reporting service
+        console.error(error);
+    }, [error]);
+
+    return (
+        <div className={"flex flex-col items-center justify-center"}>
+            <h2>Something went wrong!</h2>
+            <button
+                onClick={
+                    // Attempt to recover by trying to re-render the segment
+                    () => reset()
+                }
+            >
+                Try again
+            </button>
+        </div>
+    );
+}

+ 10 - 3
src/app/globals.scss

@@ -11,13 +11,20 @@
 
 
 :root{
-  --swiper-pagination-bullet-width: 0.23rem;
-  --swiper-pagination-bullet-height: 0.05rem;
-  --swiper-pagination-bullet-border-radius: 0.03rem;
   --swiper-pagination-color: #fff;
   --swiper-pagination-bullet-active-bg: #fff;
   --swiper-pagination-bullet-inactive-color: hsla(0, 0%, 100%, .8);
 }
+.home-banner{
+  --swiper-pagination-bullet-width: 0.23rem;
+  --swiper-pagination-bullet-height: 0.05rem;
+  --swiper-pagination-bullet-border-radius: 0.03rem;
+}
+.promotion-swiper{
+  --swiper-pagination-bullet-width: 0.0833rem;
+  --swiper-pagination-bullet-height: 0.0833rem;
+  //--swiper-pagination-bottom: 0;
+}
 /// antd-mobile
 :root:root{
   --adm-color-background: transparent ;

+ 43 - 2
src/components/Box/index.tsx

@@ -1,3 +1,6 @@
+"use client";
+import { ActionType } from "@/api/home";
+import { useRouter } from "@/i18n";
 import clsx from "clsx";
 import { CSSProperties, FC, PropsWithChildren } from "react";
 import { twMerge } from "tailwind-merge";
@@ -9,6 +12,11 @@ interface Props {
     none?: boolean;
     className?: string;
     style?: CSSProperties;
+    Tag?: "section" | "div";
+
+    // 点击功能, 1:无功能,2:跳转外部链接,3:跳转页面,4:跳转弹窗,5:跳转打开游戏,6:跳转Live Chat客服,7:跳转内部文档
+    action?: ActionType;
+    actionData?: unknown;
 }
 
 /**
@@ -18,6 +26,7 @@ interface Props {
  * todo 3: 可设置背景
  */
 const Box: FC<PropsWithChildren<Props>> = (props) => {
+    const router = useRouter();
     const {
         className,
         children,
@@ -27,6 +36,9 @@ const Box: FC<PropsWithChildren<Props>> = (props) => {
         pr = true,
         none = false,
         style,
+        Tag = "div",
+        action = undefined,
+        actionData = undefined,
     } = props;
 
     const cls = clsx(
@@ -37,10 +49,39 @@ const Box: FC<PropsWithChildren<Props>> = (props) => {
         },
         className
     );
+
+    const handler = () => {
+        if (!action) return;
+        switch (action) {
+            case 1:
+                return;
+            case 2:
+                window.open(actionData as string);
+                break;
+            case 3:
+                router.push(actionData as string);
+                break;
+            case 4:
+                console.log(`🎯🎯🎯🎯🎯-> in index.tsx on 59`);
+                break;
+            case 5:
+                window.open(actionData as string);
+                break;
+            case 6:
+                console.log(`🎯🎯🎯🎯🎯-> in index.tsx on 65`);
+                break;
+            case 7:
+                console.log(`🎯🎯🎯🎯🎯-> in index.tsx on 68`);
+                break;
+            default:
+                console.log(`🎯🎯🎯🎯🎯-> in index.tsx on 71`);
+                break;
+        }
+    };
     return (
-        <div className={twMerge(cls)} style={style}>
+        <Tag className={twMerge(cls)} style={style} onClick={handler}>
             {children}
-        </div>
+        </Tag>
     );
 };
 

+ 50 - 56
src/components/Card/Card.tsx

@@ -1,12 +1,12 @@
 "use client";
 import { GameListRep, getGameDetailApi } from "@/api/home";
+import Box from "@/components/Box";
 import { useRouter } from "@/i18n";
 import { useGlobalStore } from "@/stores";
 import { brandList } from "@/utils/constant";
-import { Button, Modal, ModalBody, ModalContent, useDisclosure } from "@nextui-org/react";
-import { Toast } from "antd-mobile";
+import { Button, Popup, Toast } from "antd-mobile";
 import { useTranslations } from "next-intl";
-import { FC, PropsWithChildren, ReactNode, useEffect, useRef } from "react";
+import { FC, PropsWithChildren, ReactNode, useEffect, useRef, useState } from "react";
 import styles from "./style.module.scss";
 export interface CardProps {
     item?: GameListRep;
@@ -14,23 +14,23 @@ export interface CardProps {
 }
 const Card: FC<PropsWithChildren<CardProps>> = (props) => {
     const { render, item } = props;
-    const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
-    const appRef = useRef<Element>();
+    const appRef = useRef<HTMLElement>();
 
     const t = useTranslations("Game");
     const urlRef = useRef<string>("");
     const brandRef = useRef<string>("");
 
+    const [visible, setVisible] = useState(false);
+
     const state = useGlobalStore();
     const router = useRouter();
     const { token } = state;
     useEffect(() => {
-        appRef.current = document.querySelector("#app")!;
+        appRef.current! = document.querySelector("#app")!;
     }, []);
     const handler = (game: GameListRep) => {
-        onOpen();
+        setVisible(true);
         brandRef.current = brandList.find((item) => item.gid === game.game_id)?.brand ?? "";
-
         if (!token) return;
         const params = {
             id: game.id,
@@ -49,7 +49,6 @@ const Card: FC<PropsWithChildren<CardProps>> = (props) => {
         } else {
             Toast.show("数据错误");
         }
-        onClose();
     };
     return (
         <>
@@ -60,56 +59,51 @@ const Card: FC<PropsWithChildren<CardProps>> = (props) => {
                     <img src={item?.game_icon} alt={item?.game_name_cn} className={"h-1/1"} />
                 </div>
             )}
-            <Modal
-                isOpen={isOpen}
-                portalContainer={appRef.current}
-                placement={"bottom"}
-                onOpenChange={onOpenChange}
-                classNames={{
-                    header: "px-0 pb-0 ",
-                    wrapper: "w-[100vw]  m-auto",
-                    closeButton: "text-[20px] top-[0rem] right-0 text-[#fff] ",
-                    backdrop: "absolute top-0 left-0 sm:my-0 ",
-                    base: "my-0 my-0 sm:mx-[0] mx-0 sm:my-0 max-w-[4.02rem]  ",
-                    body: "py-[12px]",
+
+            <Popup
+                visible={visible}
+                onMaskClick={() => {
+                    setVisible(false);
+                }}
+                onClose={() => {
+                    setVisible(false);
                 }}
+                showCloseButton={true}
+                getContainer={() => document.getElementById("app")!}
+                bodyStyle={{ background: "#1c1c1c" }}
             >
-                <ModalContent>
-                    {(onClose) => (
-                        <>
-                            <ModalBody className={"w-[4rem] text-[0.1111rem]"}>
-                                <div className={"w-1/1 flex flex-1"}>
-                                    <div className={styles.cardWrap}>
-                                        <img src={item?.game_icon} alt={item?.game_name_cn} />
-                                    </div>
-                                    <div className={styles.cardWrapGmeInfo}>
-                                        <p className={"h-[0.6rem]"}>{item?.game_name_cn}</p>
+                <Box className={"w-1/1 flex w-[4.02rem] flex-1"}>
+                    <div className={styles.cardWrap} style={{ width: "1.1rem" }}>
+                        <img src={item?.game_icon} alt={item?.game_name_cn} />
+                    </div>
+                    <div className={styles.cardWrapGmeInfo}>
+                        <p className={"h-[0.6rem]"}>{item?.game_name_cn}</p>
 
-                                        <div className={"flex w-[2.2rem] justify-around"}>
-                                            {/*<Button*/}
-                                            {/*    onClick={playGameHandler}*/}
-                                            {/*    className={*/}
-                                            {/*        "h-[0.39rem] w-[0.89rem] rounded-[0.05rem] text-[0.15rem]" +*/}
-                                            {/*        " bg-[#3a3a3a]" +*/}
-                                            {/*        " font-bold"*/}
-                                            {/*    }*/}
-                                            {/*>*/}
-                                            {/*    {t("demo")}*/}
-                                            {/*</Button>*/}
-                                            <Button
-                                                onClick={playGameHandler}
-                                                className={`h-[0.39rem] w-[0.89rem] rounded-[0.05rem] bg-[#009d80] text-[0.15rem] font-bold`}
-                                            >
-                                                {t("join")}
-                                            </Button>
-                                        </div>
-                                    </div>
-                                </div>
-                            </ModalBody>
-                        </>
-                    )}
-                </ModalContent>
-            </Modal>
+                        <div className={"flex w-[2.2rem] justify-around"}>
+                            {/*<Button*/}
+                            {/*    onClick={playGameHandler}*/}
+                            {/*    className={*/}
+                            {/*        "h-[0.39rem] w-[0.89rem] rounded-[0.05rem] text-[0.15rem]" +*/}
+                            {/*        " bg-[#3a3a3a]" +*/}
+                            {/*        " font-bold"*/}
+                            {/*    }*/}
+                            {/*>*/}
+                            {/*    {t("demo")}*/}
+                            {/*</Button>*/}
+                            <Button
+                                onClick={playGameHandler}
+                                style={{
+                                    "--background-color": "#009d80",
+                                    "--border-color": "#009d80",
+                                }}
+                                className={`h-[0.39rem] w-[0.89rem] rounded-[0.05rem] bg-[#] text-[0.15rem] font-bold`}
+                            >
+                                {t("join")}
+                            </Button>
+                        </div>
+                    </div>
+                </Box>
+            </Popup>
         </>
     );
 };

+ 4 - 3
src/components/Card/GroupCard.tsx

@@ -10,13 +10,14 @@ export interface GroupProps extends CardProps {
 }
 
 const GroupCard: FC<GroupProps> = (props) => {
-    const { col = 4, row = 2, gapX = 5, data, ...other } = props;
+    const { col = 3, row = 1, gapX = 8, data, ...other } = props;
     const cls = clsx(
         "grid",
-        `grid-cols-${col} grid-rows-${row} gap-y-4 gap-x-${gapX}  justify-between`
+        `grid-cols-${col} grid-rows-${row} gap-y-4 gap-x-[0.2rem]  justify-between`
     );
     return (
-        <div className={"flex flex-wrap justify-between gap-y-4"}>
+        // <div className={"flex flex-wrap justify-between gap-y-4"}>
+        <div className={cls}>
             {data?.map((item, index) => <Card key={index} item={item} {...other} />)}
         </div>
     );

+ 1 - 1
src/components/Card/style.module.scss

@@ -2,7 +2,7 @@
 
 .cardWrap {
   position: relative;
-   width: 1.1rem;
+  //width: 1.1rem;
   height: 1.54rem;
   overflow: hidden;
   background: #212a36;

+ 39 - 47
src/components/Header/HerderTitle.tsx

@@ -1,9 +1,8 @@
 "use client";
-import { Modal, ModalBody, ModalContent, ModalHeader, useDisclosure } from "@nextui-org/react";
+import { CenterPopup } from "antd-mobile";
 import { useTranslations } from "next-intl";
-import { FC, PropsWithChildren, useEffect, useRef } from "react";
+import { FC, PropsWithChildren, useEffect, useRef, useState } from "react";
 import styles from "./style.module.scss";
-
 interface Props {}
 
 const luanguages = [
@@ -22,15 +21,17 @@ const luanguages = [
 ];
 
 const HeaderTitle: FC<PropsWithChildren<Props>> = (props) => {
-    const { isOpen, onOpen, onOpenChange } = useDisclosure();
+    // const { isOpen, onOpen, onOpenChange } = useDisclosure();
     const t = useTranslations("Header");
     const app = useRef<HTMLDivElement | null>(null);
+    const [visible, setVisible] = useState(false);
 
     useEffect(() => {
         app.current = document.querySelector("#app")!;
     }, []);
     const handler = () => {
-        onOpen();
+        // onOpen();
+        setVisible(!visible);
     };
     return (
         <div>
@@ -46,50 +47,41 @@ const HeaderTitle: FC<PropsWithChildren<Props>> = (props) => {
                 </div>
             </span>
 
-            <Modal
-                isOpen={isOpen}
-                portalContainer={app.current!}
-                placement={"center"}
-                onOpenChange={onOpenChange}
-                classNames={{
-                    header: "px-0 pb-0 text-[#fcde26]",
-                    closeButton: "text-[20px] top-[0.1111rem] ",
-                    backdrop: "absolute top-0 left-0 sm:my-0 ",
-                    base: "my-0 mx-1 sm:mx-[0.8rem] max-w-[4.02rem] w-[4.02rem] bg-[#4a4a4a]  ",
-                    body: " text-[0.1111rem]",
+            <CenterPopup
+                visible={visible}
+                onMaskClick={() => {
+                    setVisible(false);
+                }}
+                onClose={() => {
+                    setVisible(false);
                 }}
+                showCloseButton={true}
+                getContainer={null}
+                bodyStyle={{ background: "#1c1c1c" }}
             >
-                <ModalContent>
-                    {(onClose) => (
-                        <>
-                            <ModalBody>
-                                <ModalHeader>{t("locale")}</ModalHeader>
-                                <div>
-                                    {luanguages.map((item, index) => {
-                                        return (
-                                            <div
-                                                key={index}
-                                                className={
-                                                    "h-[0.39rem flex bg-[#2e2d2d]" +
-                                                    " my-[0.11rem] rounded-[0.04rem] p-[0.11rem] text-[#fff]"
-                                                }
-                                            >
-                                                <img
-                                                    src={item.icon}
-                                                    alt={item.value}
-                                                    className={"mr-[0.07rem] w-[0.3rem]"}
-                                                ></img>
-                                                <div className={"mr-[0.04rem]"}>{item.title}</div>
-                                                <div> {item.desc}</div>
-                                            </div>
-                                        );
-                                    })}
-                                </div>
-                            </ModalBody>
-                        </>
-                    )}
-                </ModalContent>
-            </Modal>
+                <div className={"w-[3.4722rem] rounded-[0.0694rem] bg-[#4a4a4a] p-[0.1389rem]"}>
+                    <header className={"text-[#fcde26]"}>{t("locale")}</header>
+                    {luanguages.map((item, index) => {
+                        return (
+                            <div
+                                key={index}
+                                className={
+                                    "h-[0.39rem flex bg-[#2e2d2d]" +
+                                    " my-[0.11rem] rounded-[0.04rem] p-[0.11rem] text-[#fff]"
+                                }
+                            >
+                                <img
+                                    src={item.icon}
+                                    alt={item.value}
+                                    className={"mr-[0.07rem] w-[0.3rem]"}
+                                ></img>
+                                <div className={"mr-[0.04rem]"}>{item.title}</div>
+                                <div> {item.desc}</div>
+                            </div>
+                        );
+                    })}
+                </div>
+            </CenterPopup>
         </div>
     );
 };

+ 50 - 47
src/components/Layout/index.tsx

@@ -136,53 +136,56 @@ const Layout: FC<PropsWithChildren<Props>> = (props) => {
     };
 
     return (
-        <Swiper
-            resistanceRatio={10}
-            initialSlide={1}
-            slidesPerView={"auto"}
-            onSlidePrevTransitionStart={startHandler}
-            onSlideNextTransitionEnd={endHandler}
-            slideToClickedSlide
-            onSwiper={(swiper) => {
-                swiperRef.current = swiper;
-            }}
-            runCallbacksOnInit={false}
-            allowTouchMove={false}
-        >
-            {/*<SwiperSlide*/}
-            {/*    lazy={true}*/}
-            {/*    style={{*/}
-            {/*        width: `${!mounted ? "0" : "70%"}`,*/}
-            {/*    }}*/}
-            {/*    className={"mx-w-[2.2222rem] bg-[rgb(31,31,31)]"}*/}
-            {/*>*/}
-            {/*    <section className="relative h-[100vh]">*/}
-            {/*        <Sidebar></Sidebar>*/}
-            {/*    </section>*/}
-            {/*</SwiperSlide>*/}
-            <SwiperSlide style={{ width: "100%" }}>
-                <section className="relative h-[100%]" ref={homeContainerRef}>
-                    <Header
-                        {...other}
-                        menuRender={() => (
-                            <div
-                                className={`absolute left-[0.1rem] top-[0.03rem] z-40 text-[#fff]`}
-                                ref={barRef}
-                                onClick={openSliderHandler}
-                            >
-                                <div className={styles.bar}></div>
-                                <div className={styles.bar} style={{ width: "0.1389rem" }}></div>
-                                <div className={styles.bar}></div>
-                            </div>
-                        )}
-                    ></Header>
-                    <main className={styles.main} id="maincontainer">
-                        {children}
-                    </main>
-                    <Footer></Footer>
-                </section>
-            </SwiperSlide>
-        </Swiper>
+        <div>
+            <Swiper
+                resistanceRatio={10}
+                initialSlide={1}
+                slidesPerView={"auto"}
+                onSlidePrevTransitionStart={startHandler}
+                onSlideNextTransitionEnd={endHandler}
+                slideToClickedSlide
+                onSwiper={(swiper) => {
+                    swiperRef.current = swiper;
+                }}
+                runCallbacksOnInit={false}
+                allowTouchMove={false}
+            >
+                {/*<SwiperSlide*/}
+                {/*    lazy={true}*/}
+                {/*    style={{ width: `${!mounted ? "0" : "70%"}` }}*/}
+                {/*    className={"mx-w-[2.2222rem] bg-[rgb(31,31,31)]"}*/}
+                {/*>*/}
+                {/*    <section className="relative h-[100vh]">*/}
+                {/*        <Sidebar></Sidebar>*/}
+                {/*    </section>*/}
+                {/*</SwiperSlide>*/}
+                <SwiperSlide style={{ width: "100%" }}>
+                    <section className="relative h-[100%]" ref={homeContainerRef}>
+                        <Header
+                            {...other}
+                            menuRender={() => (
+                                <div
+                                    className={`absolute left-[0.1rem] top-[0.03rem] z-40 text-[#fff]`}
+                                    ref={barRef}
+                                    onClick={openSliderHandler}
+                                >
+                                    <div className={styles.bar}></div>
+                                    <div
+                                        className={styles.bar}
+                                        style={{ width: "0.1389rem" }}
+                                    ></div>
+                                    <div className={styles.bar}></div>
+                                </div>
+                            )}
+                        ></Header>
+                        <main className={styles.main} id="maincontainer">
+                            {children}
+                        </main>
+                        <Footer></Footer>
+                    </section>
+                </SwiperSlide>
+            </Swiper>
+        </div>
     );
 };
 

+ 33 - 0
src/stores/useGlobalNoticeStore.ts

@@ -0,0 +1,33 @@
+import { getGlobalNoticeApi, GlobalNoticeRep } from "@/api/home";
+import { create } from "zustand";
+
+interface State {
+    globalNotices: GlobalNoticeRep[];
+    topNotices: GlobalNoticeRep;
+}
+
+interface Action {
+    setGlobalNotice: (state: GlobalNoticeRep[]) => void;
+    getGlobalNotice: () => void;
+}
+
+const initialState: State = {
+    globalNotices: [],
+    topNotices: {},
+};
+export const useGlobalNoticeStore = create<State & Action>()((set, get) => {
+    return {
+        ...initialState,
+        setGlobalNotice: (score) => {
+            set((state) => ({ globalNotices: score }));
+        },
+        getGlobalNotice: async () => {
+            const res = await getGlobalNoticeApi();
+            if (res.code === 200) {
+                set((state) => ({ globalNotices: res.data, topNotices: res.data[0] }));
+            } else {
+                set(initialState);
+            }
+        },
+    };
+});

+ 31 - 0
src/stores/useSearchStore.ts

@@ -0,0 +1,31 @@
+import { create } from "zustand";
+import { createJSONStorage, devtools, persist } from "zustand/middleware";
+
+interface State {
+    history: string[];
+}
+
+interface Action {
+    setHistory: (history: State["history"]) => void;
+}
+
+export const useSearchStore = create<State & Action>()(
+    devtools(
+        persist(
+            (set) => {
+                return {
+                    history: ["for"],
+                    setHistory: (value: State["history"]) =>
+                        set({
+                            history: value,
+                        }),
+                };
+            },
+            {
+                name: "searchHistory",
+                storage: createJSONStorage(() => localStorage),
+            }
+        ),
+        { name: "searchHistory" }
+    )
+);

+ 19 - 0
src/utils/methods/index.ts

@@ -0,0 +1,19 @@
+export function debounce(fn: Function, delay = 200, immediate = false) {
+    let timer: ReturnType<typeof setTimeout>;
+    return function (...args: any) {
+        const context = globalThis;
+        let result: unknown;
+        args = arguments;
+        if (timer) {
+            clearTimeout(timer);
+        }
+        if (immediate && !timer) {
+            result = fn.apply(context, args);
+        }
+        timer = setTimeout(() => {
+            result = fn.apply(context, args);
+        }, delay);
+
+        return result;
+    };
+}

+ 10 - 8
src/utils/server/axios.ts

@@ -93,19 +93,21 @@ export default class Request {
     /**
      * @description 单个实列请求
      */
-    request<T = unknown>(config: AxiosOptions): Promise<Result<T>> {
+    request<T = unknown, R = unknown>(config: AxiosOptions): Promise<Result<T> & R> {
         return new Promise((resolve, reject) => {
             this.axiosInstance
-                .request<any, AxiosResponse<Result>>(config)
+                .request<unknown, AxiosResponse<Result<T> & R>>(config)
                 .then((res) => {
                     const { transform } = config;
                     if (transform && transform.responseInterceptor) {
                         res = transform.responseInterceptor(res);
                     }
                     if (res && res.data && res.data.code === 200) {
-                        resolve(res?.data);
+                        resolve(res.data);
                     } else {
-                        Toast.show(res.data?.msg || "请求失败");
+                        if (res && res.data && res.data.msg) {
+                            Toast.show(res.data.msg || "请求失败");
+                        }
                         reject(res);
                     }
                 })
@@ -114,16 +116,16 @@ export default class Request {
                 });
         });
     }
-    get<T>(config: AxiosOptions): Promise<Result<T>> {
+    get<T, R = unknown>(config: AxiosOptions): Promise<Result<T> & R> {
         return this.request({ ...config, method: "get" });
     }
-    post<T>(config: AxiosOptions): Promise<Result<T>> {
+    post<T, R = unknown>(config: AxiosOptions): Promise<Result<T> & R> {
         return this.request({ ...config, method: "post" });
     }
-    put<T>(config: AxiosOptions): Promise<Result<T>> {
+    put<T, R = unknown>(config: AxiosOptions): Promise<Result<T> & R> {
         return this.request({ ...config, method: "put" });
     }
-    delete<T>(config: AxiosOptions): Promise<Result<T>> {
+    delete<T, R = unknown>(config: AxiosOptions): Promise<Result<T> & R> {
         return this.request({ ...config, method: "delete" });
     }
 }