瀏覽代碼

fix:
pwa 弹窗

ansoni 1 月之前
父節點
當前提交
d4e703c433
共有 1 個文件被更改,包括 731 次插入0 次删除
  1. 731 0
      src/app/[locale]/(doings)/store/page.tsx

+ 731 - 0
src/app/[locale]/(doings)/store/page.tsx

@@ -0,0 +1,731 @@
+"use client";
+import useDesktop from "@/hooks/useDesktop";
+import { server } from "@/utils/client";
+import { Local } from "@/utils/storage";
+import { Divider, Mask, Popup, ProgressBar, Rate } from "antd-mobile";
+import clsx from "clsx";
+import Image from "next/image";
+import { useSearchParams } from "next/navigation";
+import { FC, useEffect, useRef, useState } from "react";
+import { Swiper, SwiperSlide } from "swiper/react";
+import style from "./style.module.scss";
+interface StoreDetailsType {
+  /**
+   * 应用描述
+   */
+  description: string;
+  /**
+   * 应用图标地址
+   */
+  icon_url: string;
+  /**
+   * 安装器参数信息
+   */
+  installer_args: string;
+  /**
+   * 安装器类型:1添加到桌面(苹果)、2苹果商店安装、3pwa快捷安装
+   */
+  installer_type: number;
+  /**
+   * 应用名称
+   */
+  name: string;
+  /**
+   * 预览图片列表
+   */
+  preview_images: string[];
+  /**
+   * 应用标题
+   */
+  title: string;
+  /**
+   * 类型:1谷歌原版(有logo),2谷歌原版(无logo)
+   */
+  type: number;
+}
+const Header = () => {
+  return (
+    <div
+      className={
+        "sticky left-0 top-0 z-20 flex h-[0.4844rem] bg-[#fff] text-[#000]" +
+        " items-center shadow"
+      }
+    >
+      <Image
+        src={"/svg/google.svg"}
+        alt={""}
+        width={40}
+        height={40}
+        className={"mx-[10px]"}
+      />
+      <Image src={"/store/google-play.png"} alt={""} width={120} height={60} />
+    </div>
+  );
+};
+
+const Footer: FC<{ onPress: () => void }> = ({ onPress }) => {
+  const items = [
+    { name: "Jogos", icon: "game" },
+    { name: "Apps", icon: "app" },
+    { name: "Filmes", icon: "whtch" },
+    { name: "Livros", icon: "book" },
+    { name: "Crianças", icon: "child" },
+  ];
+  return (
+    <div
+      className={
+        "sticky bottom-0 left-0 grid h-[0.5444rem] grid-cols-5 items-center bg-[#fff] text-center" +
+        " border-t border-[#e8eaed] text-[0.125rem] text-[#5f6368]"
+      }
+    >
+      {items.map((item, index) => {
+        return (
+          <div
+            key={index}
+            className={clsx(index === 1 ? "text-[#01875f]" : "", "cursor-pointer")}
+            onClick={onPress}
+          >
+            <Image
+              src={`/store/${item.icon}.svg`}
+              alt={""}
+              className={"mx-auto"}
+              width={22}
+              height={22}
+            />
+            <p>{item.name}</p>
+          </div>
+        );
+      })}
+    </div>
+  );
+};
+
+const CardView: FC<{ details: StoreDetailsType | null; onPress: () => void }> = (props) => {
+  const { details, onPress } = props;
+
+  const textcls = clsx("text-[14px] font-medium text-[#5f6368]");
+
+  return (
+    <div className={"p-[0.1389rem] text-[#202124]"}>
+      <div className={"flex space-x-[24px]"}>
+        <div className={"flex-shrink-0"}>
+          {details?.icon_url ? (
+            <img
+              src={details?.icon_url}
+              alt=""
+              className={"h-[0.5rem] w-[0.5rem] object-contain"}
+            />
+          ) : (
+            <div></div>
+          )}
+        </div>
+
+        <div>
+          <p className={"text-[#202124]"}>{details?.name || ""}</p>
+          <p className={"cursor-pointer text-[#01875f]"}>{details?.title}</p>
+        </div>
+      </div>
+
+      <div
+        className={
+          "my-[0.1111rem] flex items-center py-[0.0833rem] text-center text-[0.1111rem]"
+        }
+      >
+        <div className={"px-[0.1667rem]"}>
+          <p className={"flex items-center font-semibold"}>
+            4,9<i className={"iconfont icon-star_full text-[0.0972rem]"}></i>
+          </p>
+          <p className={"text-[#5f6368]"}>46K avaliações</p>
+        </div>
+        <div
+          className={
+            "relative px-[0.1667rem] before:absolute before:top-1/4" +
+            ' before:content-[""]' +
+            " before:left-0 before:h-[0.1667rem] before:w-[1px] before:bg-[#e8eaed]"
+          }
+        >
+          <p className={"font-semibold"}>50 mil+</p>
+          <p className={"text-[#5f6368]"}>Downloads</p>
+        </div>
+        <div
+          className={
+            "relative px-[0.1667rem] before:absolute before:top-1/4" +
+            ' before:content-[""]' +
+            " before:left-0 before:h-[0.1667rem] before:w-[1px] before:bg-[#e8eaed]"
+          }
+        >
+          <p className={"inline-block border font-semibold"}>18+</p>
+          <p className={"text-[#5f6368]"}>Rated for 18+</p>
+        </div>
+      </div>
+
+      <div
+        className={
+          "cursor-pointer rounded-[10px] bg-[#01875f] py-[8px] text-center text-[#fff]"
+        }
+        onClick={onPress}
+      >
+        Rapid Instalar
+      </div>
+
+      <div
+        className={"my-[0.1389rem] flex justify-center space-x-[0.2083rem] text-[#01875f]"}
+      >
+        <div className={"flex items-center"} onClick={onPress}>
+          <i className={"iconfont icon-fenxiang1 mr-[8px] text-[0.1389rem]"} />
+          <span>Compartilhar</span>
+        </div>
+        <div className={"flex items-center"} onClick={onPress}>
+          <i className={"iconfont icon-shoucang" + " mr-[8px] text-[0.1389rem]"} />{" "}
+          <span>Adicionar à lista de desejos</span>
+        </div>
+      </div>
+
+      <div>
+        <Swiper
+          pagination={{
+            clickable: true,
+          }}
+          slidesPerView={"auto"}
+          spaceBetween={10}
+          autoplay={{
+            delay: 3000,
+          }}
+        >
+          {details?.preview_images?.map((url, index: number) => (
+            <SwiperSlide key={index} className={"max-w-fit"}>
+              <img src={url} alt={""} className={"h-[200px] rounded-[10px]"} />
+            </SwiperSlide>
+          ))}
+        </Swiper>
+      </div>
+
+      <div>
+        <div className={"flex justify-between py-[20px]"}>
+          <p>Sobre este jogo</p>
+          <i className={"iconfont icon-xiangyou2 text-[14px]"}></i>
+        </div>
+        <p className={textcls}>{details?.description}</p>
+
+        <div className={"my-[20px]"}>
+          <p>Atualizado em</p>
+          <p className={textcls}>6 de nov. de 2024</p>
+        </div>
+
+        <div className={"my-[20px] flex justify-between"}>
+          <p>Segurança dos dados</p>
+          <i className={"iconfont icon-xiangyou2 text-[14px]"}></i>
+        </div>
+        <p className={textcls}>
+          Sua segurança começa com o entendimento de como os desenvolvedores coletam e
+          compartilham seus dados. As práticas de segurança e privacidade de dados podem
+          variar de acordo com o uso, a região e a idade. O desenvolvedor forneceu as
+          informações a seguir, que podem ser atualizadas ao longo do tempo.
+        </p>
+        <div className="my-[20px] rounded-[8px] border border-[#dadce0] p-[20px] shadow">
+          <div className={"flex space-x-[20px]"}>
+            <img
+              src={
+                "https://play-lh.googleusercontent.com/iFstqoxDElUVv4T3KxkxP3OTcuFvWF5ZQQjT7aIxy4n2uaVigCCykxeG6EZV9FQ10X1itPj1oORm=s20-rw"
+              }
+              className={"h-[20px] w-[20px]"}
+            />
+            <div className={textcls}>
+              <p>Os dados não são compartilhados com terceiros</p>
+              <span className={"cursor-pointer underline"}>
+                                Entendimento detalhado
+                            </span>
+              Como os desenvolvedores declaram questões de compartilhamento de dados
+            </div>
+          </div>
+
+          <div className={"mt-[20px] flex space-x-[20px]"}>
+            <img
+              src={
+                "https://play-lh.googleusercontent.com/12USW7aflgz466ifDehKTnMoAep_VHxDmKJ6jEBoDZWCSefOC-ThRX14Mqe0r8KF9XCzrpMqJts=s20-rw"
+              }
+              className={"h-[20px] w-[20px]"}
+            />
+            <div className={textcls}>
+              <p>Este app pode coletar estes tipos de dados Local,</p>
+              <p> Atividade no app e Identificadores do dispositivo e outros</p>
+            </div>
+          </div>
+
+          <div className={"mt-[20px] flex space-x-[20px]"}>
+            <img
+              src={
+                "https://play-lh.googleusercontent.com/W5DPtvB8Fhmkn5LbFZki_OHL3ZI1Rdc-AFul19UK4f7np2NMjLE5QquD6H0HAeEJ977u3WH4yaQ=s20-rw"
+              }
+              className={"h-[20px] w-[20px]"}
+            />
+            <div className={textcls}>
+              <p>Os dados são criptografados em trânsito</p>
+            </div>
+          </div>
+
+          <div className={"mt-[20px] flex space-x-[20px]"}>
+            <img
+              src={
+                "https://play-lh.googleusercontent.com/ohRyQRA9rNfhp7xLW0MtW1soD8SEX45Oec7MyH3FaxtukWUG_6GKVpvh3JiugzryLi7Bia02HPw=s20-rw"
+              }
+              className={"h-[20px] w-[20px]"}
+            />
+            <div className={textcls}>
+              <p>Você pode solicitar a exclusão dos dados</p>
+            </div>
+          </div>
+
+          <p className={"ml-[10px] mt-[20px] cursor-pointer text-[14px] text-[#01875f]"}>
+            Mais detalhes
+          </p>
+        </div>
+      </div>
+      <div>
+        <div className={"py-[20px]"}>
+          <div className={"flex justify-between"}>
+            <p className={""}>Classificações e resenhas</p>
+            <i className={"iconfont icon-xiangyou2 text-[14px]"}></i>
+          </div>
+          <span className={"text-[12px]"}>As notas e avaliações são verificadas</span>
+          <i
+            className={
+              "iconfont icon-zhuyi ml-[10px] text-[12px] font-semibold text-[#5f6368]"
+            }
+          ></i>
+        </div>
+
+        <div className={"flex space-x-[30px]"}>
+          <div>
+            <p className={"text-[50px]"}>4,9</p>
+            <RateStar star={4.9} />
+            <p className={"text-[12px] text-[#5f6368]"}>1,91 mil avaliações</p>
+          </div>
+          <div className={"flex-1"}>
+            <Process num={5} precent={90} />
+            <Process num={4} precent={20} />
+            <Process num={3} precent={5} />
+            <Process num={2} precent={2} />
+            <Process num={1} precent={7} />
+          </div>
+        </div>
+
+        <Comment />
+
+        <p className={"mt-[10px] cursor-pointer text-[14px] font-semibold text-[#01875f]"}>
+          Ver todas as avaliações
+        </p>
+
+        <Change />
+
+        <Other onPress={onPress} />
+
+        <Divider
+          style={{
+            borderColor: "#e8eaed",
+          }}
+        />
+        <CardFooter />
+      </div>
+    </div>
+  );
+};
+
+const RateStar = ({ star, small = false }: { star: number; small?: boolean }) => {
+  const size = small ? "7px" : "10px";
+  return (
+    <>
+      <Rate
+        allowHalf
+        defaultValue={star}
+        style={{
+          "--active-color": "#01875f",
+          "--star-size": size,
+        }}
+      />
+    </>
+  );
+};
+// 评分
+const Process = ({ num, precent }: { num: number; precent: number }) => {
+  return (
+    <div className={"grid grid-cols-12 items-center"}>
+      <span className={"text-center text-[12px] text-[#5f6368]"}>{num}</span>
+      <ProgressBar
+        className={"col-span-11"}
+        percent={precent}
+        style={{
+          "--fill-color": "#01875f",
+          "--track-width": "10px",
+        }}
+      />
+    </div>
+  );
+};
+// 评论
+const Comment = () => {
+  const users = [
+    {
+      avatar: "/store/avatar1.jpg",
+      name: "Lara Liras",
+      precent: 5,
+      time: "5 de setembro de 2024",
+      desc:
+        "O jogo é bom , mais precisa de um incentivo, no geral eu gostei bastante Dá" +
+        " pra pessoa distrair e jogar é muito saudável ! Amei os porquinhos kkk",
+      sure: 103,
+    },
+    {
+      avatar: "/store/avatar2.jpg",
+      name: "Angelita Da Silva",
+      precent: 4,
+      time: "19 de agosto de 2024",
+      desc: "Gosto muito, o jogo é super divertido e o jackpot explode todos os dias",
+      sure: 22,
+    },
+    {
+      avatar: "/store/avatar3.png",
+      name: "Hkawng Nor",
+      precent: 5,
+      time: "9 de junho de 2024",
+      desc: "muito divertido e não fica trancando e cheio de anúncios muito bom mesmo",
+      sure: 32,
+    },
+  ];
+  return (
+    <>
+      {users.map((item, index) => (
+        <div key={index} className={"py-[16px]"}>
+          <div className={"flex justify-between"}>
+            <div className={"flex items-center space-x-[20px]"}>
+              <Image
+                src={item.avatar}
+                alt={item.name}
+                width={30}
+                height={30}
+                className={"rounded-full"}
+              />
+              <span className={"text-[14px]"}>{item.name}</span>
+            </div>
+            <i className={"iconfont icon-gengduo1"}></i>
+          </div>
+
+          <div className={"mt-[16px] flex items-center space-x-[10px]"}>
+            <RateStar star={item.precent} small />
+            <p className={"text-[12px] text-[#5f6368]"}>{item.time}</p>
+          </div>
+          <div className={"mt-[8px] text-[0.125rem] text-[#5f6368]"}>{item.desc}</div>
+          <p className={"mt-[16px] text-[0.1111rem] text-[#5f6368]"}>
+            Essa avaliação foi marcada como útil por {item.sure} pessoas
+          </p>
+          <div
+            className={"mt-[12px] flex space-x-[20px] text-[0.1111rem] text-[#5f6368]"}
+          >
+            <p>Você achou isso útil?</p>
+            <div></div>
+            <div
+              className={
+                "h-[20px] w-[50px] rounded-[12px] border border-[#dadce0]" +
+                " text-center" +
+                " leading-[20px]" +
+                " text-[#5f6368]"
+              }
+            >
+              Sim
+            </div>
+            <div
+              className={
+                "h-[20px] w-[50px] rounded-[12px] border border-[#dadce0]" +
+                " text-center" +
+                " leading-[20px]" +
+                " text-[#5f6368]"
+              }
+            >
+              Não
+            </div>
+          </div>
+        </div>
+      ))}
+    </>
+  );
+};
+// 新变化
+const Change = () => {
+  return (
+    <>
+      <div className={"flex justify-between py-[20px]"}>
+        <p>O que há de novo</p>
+        {/*<i className={"iconfont icon-xiangyou2 text-[14px]"}></i>*/}
+      </div>
+      <div className={"text-[14px] text-[#5f6368]"}>
+        <p>Olá, fãs de Fortune Tiger Slot!</p>
+        <p>Aqui está uma nova atualização:</p>
+        <p className={"my-[10px]"}>-Melhorias de desempenho</p>
+        <p>Aproveite e divirta-se!</p>
+      </div>
+    </>
+  );
+};
+
+const Other: FC<{ onPress: () => void }> = ({ onPress }) => {
+  const array = [
+    {
+      url: "https://play-lh.googleusercontent.com/yZ_nzWpg_cj6GYJv15YdsfWBKS6JzXGt69R8fCCj3AsapCSs5MGdr6haxPCk-Ae91g=s64-rw",
+      name: "Cash Craze: Casino Slots Games",
+      desc: "Casual Joy Games",
+      star: "4,5",
+    },
+    {
+      url: "https://play-lh.googleusercontent.com/vHA867MF5a-0zJfHgxgKUKs4GJb8mWMLNjGpTpufUpw1IN_EuqWGY9d-qrY4keq4hw8=s64-rw",
+      name: "Charge Buffalo Slot-TaDa Jogos",
+      desc: "FUFAFA TECHNOLOGY LTD CO.",
+      star: "4,5",
+    },
+    {
+      url: "https://play-lh.googleusercontent.com/Bi-dBVeyh-XZZ8ypCuklaHct8ClSitAAEOy3rCS3KOE50GBVsZ4ucseDKrLZExrAkfY=s64-rw",
+      name: "Jackpot Magic Slots",
+      desc: "Big Fish Games",
+      star: "2,6",
+    },
+    {
+      url: "https://play-lh.googleusercontent.com/3C9ipO88Z92NJuohq_u6Ce6sTKHAADCfrpyVaEWz3vDO4XBscTJwAiRM9WhzS__WOybZ=s64-rw",
+      name: "Diamond Slot - Slot Game",
+      desc: "International Games System Co., Ltd.",
+      star: "3,9",
+    },
+    {
+      url: "https://play-lh.googleusercontent.com/Kwa0EUhA_Z31MqQSr15DZyBrVZoAUlwlwGxE8tJoNFUPKbcrgcjSiDAAXFw9VMogJJc=s64-rw",
+      name: "Bônus Bingo Cassino-TaDa Jogo",
+      desc: "FUFAFA TECHNOLOGY LTD CO.",
+      star: "4,4",
+    },
+    {
+      url: "https://play-lh.googleusercontent.com/CSkRZuIZLOJZ8q0krA3qvnciiRTXZBU5Nx6nmTXM31ZUEilUf6Plz_va-IV_-pjWDgE=s64-rw",
+      name: "Infinity Slots - Casino Games",
+      desc: "Murka Games Limited",
+      star: "4,5",
+    },
+  ];
+  return (
+    <>
+      <div className={"flex justify-between py-[20px]"}>
+        <p>Jogos semelhantes</p>
+        <i className={"iconfont icon-xiangyou2 text-[14px]"}></i>
+      </div>
+      <div className={"grid grid-cols-2 gap-[20px]"}>
+        {array.map((item, i) => {
+          return (
+            <div
+              key={i}
+              className={"flex cursor-pointer gap-[10px] overflow-hidden"}
+              onClick={onPress}
+            >
+              <img src={item.url} className={"h-[56px] w-[56px] rounded-[10px]"} />
+              <div>
+                <p className={"whitespace-nowrap text-[14px]"}>{item.name}</p>
+                <p className={"whitespace-nowrap text-[12px]"}>{item.desc}</p>
+                <p className={"text-[12px] text-[#5f6368]"}>
+                  {item.star}
+                  <i className={"iconfont icon-star_full text-[14px]"}></i>
+                </p>
+              </div>
+            </div>
+          );
+        })}
+      </div>
+
+      <div className={"mt-[10px]"}>
+        <i className={"iconfont icon-tongyong-biaojiqizi mr-[10px]"}></i>Sinalizar como
+        impróprio
+      </div>
+    </>
+  );
+};
+
+const CardFooter = () => {
+  return (
+    <>
+      <div className={"mt-[36px] text-[16px] text-[#5f6368] [&>section>p]:cursor-pointer"}>
+        <section>
+          <p>Vales-presente</p>
+          <p>Resgatar</p>
+          <p>Política de reembolso</p>
+        </section>
+        <section className={"mt-[24px]"}>
+          <p>Crianças e família</p>
+          <p>Guia para a família</p>
+          <p>Compartilhamento em família</p>
+        </section>
+        <section className={"flex flex-wrap"}>
+          <p className={"mr-[24px] leading-[48px]"}>Termos de Serviço</p>
+          <p className={"mr-[24px] leading-[48px]"}>Privacidade</p>
+          <p className={"mr-[24px] leading-[48px]"}>Desenvolvedores</p>
+          <p className={"mr-[24px] leading-[48px]"}>Todos os preços incluem Tributo.</p>
+        </section>
+
+        <div className={"flex items-center"}>
+          <Image src={"/svg/br.svg"} alt={""} width={24} height={20} />
+          <span className={"ml-[0.0694rem]"}>Brasil (Português)</span>
+        </div>
+      </div>
+    </>
+  );
+};
+
+const getStoreApi = (data: { ch: string }) => {
+  return server.post<StoreDetailsType>({
+    url: "/v1/api/front/app/store/details",
+    data,
+  });
+};
+
+const InstallLoading: FC<{
+  details: StoreDetailsType | null;
+  visible: boolean;
+  open: () => void;
+  close: () => void;
+}> = ({ details, visible, open, close }) => {
+  const [count, setCount] = useState(0);
+  const { downloadHandler } = useDesktop("page");
+  const [isVerify, setIsVerify] = useState(true);
+  const [tipModal, setTipModal] = useState(false);
+
+  const timer = useRef<NodeJS.Timeout | number>(0);
+  const installHandler = () => {
+    switch (details?.installer_type) {
+      case 1:
+        setTipModal(true);
+        break;
+      case 2:
+        break;
+      default:
+        downloadHandler();
+    }
+    close();
+  };
+  useEffect(() => {
+    timer.current = setTimeout(() => {
+      if (count >= 100) {
+        clearTimeout(timer.current);
+        setIsVerify(false);
+        return;
+      } else {
+        setCount((count) => count + 1);
+      }
+    }, 80);
+  }, [count]);
+
+  return (
+    <>
+      <Popup
+        visible={tipModal}
+        getContainer={null}
+        onMaskClick={() => {
+          setTipModal(false);
+        }}
+        onClose={() => {
+          setTipModal(false);
+        }}
+        bodyStyle={{
+          borderTopLeftRadius: "8px",
+          borderTopRightRadius: "8px",
+        }}
+      >
+        <div className={"relative w-[100%] p-[20px]"}>
+          <Image
+            src={"/store/ios.png"}
+            className={"w-[100%] object-contain"}
+            alt={""}
+            width={400}
+            height={200}
+          />
+          <div
+            onClick={() => setTipModal(false)}
+            className={"absolute right-[20px] top-[30px] h-[50px] w-[50px]"}
+          ></div>
+        </div>
+      </Popup>
+
+      <Mask visible={visible} onMaskClick={open}>
+        <div className={style.installContainer}>
+          <div className={"flex"}>
+            <Image src={"/store/pwa_install.png"} alt={""} width={28} height={20} />
+            <span className={"ml-[10px] text-[#666]"}>Rapid Instalar</span>
+          </div>
+          {isVerify ? (
+            <>
+              <div className={style.loadingContainer}>
+                <div className={style.loading}></div>
+                <div className={style.loadingText}>{count}%</div>
+              </div>
+            </>
+          ) : (
+            <div className={"mt-[0.1389rem] flex w-[100%] flex-col items-center"}>
+              <div
+                className={
+                  "flex w-[0.8333rem] items-center justify-center rounded-[20px]" +
+                  " bg-[rgba(119,250,73,0.3)] p-[5px]"
+                }
+              >
+                <Image
+                  src={"/store/actived.png"}
+                  alt={""}
+                  className={"object-contain"}
+                  width={14}
+                  height={12}
+                />
+                <span
+                  className={
+                    "ml-[5px] text-[0.1111rem] font-semibold text-[#01875f]"
+                  }
+                >
+                                    Effective
+                                </span>
+              </div>
+              <div
+                className={
+                  "mt-[20px] w-[100%] cursor-pointer rounded-[0.1389rem] bg-[#028760] py-[5px]" +
+                  " text-[14px]" +
+                  " text-center" +
+                  " text-[#fff]"
+                }
+                onClick={installHandler}
+              >
+                Instalar Agora
+              </div>
+            </div>
+          )}
+        </div>
+      </Mask>
+    </>
+  );
+};
+
+const Page = () => {
+  const searchparams = useSearchParams();
+  const [details, setDetails] = useState<StoreDetailsType | null>(null);
+  const [visible, setVisible] = useState(false);
+  useEffect(() => {
+    getStoreApi({ ch: searchparams.get("ch")! }).then((res) => {
+      setDetails(res.data);
+      return res.data;
+    });
+
+    Local.setKey("channel_code", window.location.href);
+  }, []);
+  return (
+    <div className={"text-[#000]"}>
+      <InstallLoading
+        details={details}
+        visible={visible}
+        open={() => setVisible(true)}
+        close={() => setVisible(false)}
+      />
+      {details?.type === 1 ? <Header /> : null}
+      <CardView details={details} onPress={() => setVisible(true)} />
+      <Footer onPress={() => setVisible(true)} />
+    </div>
+  );
+};
+
+export default Page;