|
@@ -1,47 +1,127 @@
|
|
|
"use client";
|
|
|
+import useDesktop from "@/hooks/useDesktop";
|
|
|
import { server } from "@/utils/client";
|
|
|
-import { Divider, ProgressBar, Rate } from "antd-mobile";
|
|
|
+import { Divider, Mask, Popup, ProgressBar, Rate } from "antd-mobile";
|
|
|
import clsx from "clsx";
|
|
|
import Image from "next/image";
|
|
|
import { useSearchParams } from "next/navigation";
|
|
|
-import { useEffect } from "react";
|
|
|
+import { FC, useEffect, useRef, useState } from "react";
|
|
|
import { Autoplay } from "swiper/modules";
|
|
|
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 h-[0.4444rem] bg-[#fff] text-[#000] shadow"}>
|
|
|
- header
|
|
|
+ <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 = () => {
|
|
|
+ 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 h-[0.4444rem] bg-[#fff] text-[#000]"}>Footer</div>
|
|
|
+ <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")}
|
|
|
+ >
|
|
|
+ <Image
|
|
|
+ src={`/store/${item.icon}.svg`}
|
|
|
+ alt={""}
|
|
|
+ className={"mx-auto"}
|
|
|
+ width={22}
|
|
|
+ height={22}
|
|
|
+ />
|
|
|
+ <p>{item.name}</p>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-const CardView = () => {
|
|
|
- const images = Array.from({ length: 6 });
|
|
|
+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]"}>
|
|
|
- <div className={"flex"}>
|
|
|
+ <div className={"p-[0.1389rem] text-[#202124]"}>
|
|
|
+ <div className={"flex space-x-[24px]"}>
|
|
|
<div className={"flex-shrink-0"}>
|
|
|
- <img
|
|
|
- src="/doings/activity.png"
|
|
|
- alt=""
|
|
|
- className={"h-[0.5rem] w-[0.5rem] object-contain"}
|
|
|
- />
|
|
|
+ {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]"}>name</p>
|
|
|
- <p className={"cursor-pointer text-[#01875f]"}>
|
|
|
- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut, quasi.
|
|
|
- </p>
|
|
|
+ <p className={"text-[#202124]"}>{details?.name || ""}</p>
|
|
|
+ <p className={"cursor-pointer text-[#01875f]"}>{details?.title}</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
@@ -51,10 +131,10 @@ const CardView = () => {
|
|
|
}
|
|
|
>
|
|
|
<div className={"px-[0.1667rem]"}>
|
|
|
- <p className={"font-semibold"}>
|
|
|
- 4,9<i className={"iconfont icon-star_full"}></i>
|
|
|
+ <p className={"flex items-center font-semibold"}>
|
|
|
+ 4,9<i className={"iconfont icon-star_full text-[0.0972rem]"}></i>
|
|
|
</p>
|
|
|
- <p>46K avaliações</p>
|
|
|
+ <p className={"text-[#5f6368]"}>46K avaliações</p>
|
|
|
</div>
|
|
|
<div
|
|
|
className={
|
|
@@ -64,7 +144,7 @@ const CardView = () => {
|
|
|
}
|
|
|
>
|
|
|
<p className={"font-semibold"}>50 mil+</p>
|
|
|
- <p>Downloads</p>
|
|
|
+ <p className={"text-[#5f6368]"}>Downloads</p>
|
|
|
</div>
|
|
|
<div
|
|
|
className={
|
|
@@ -74,11 +154,16 @@ const CardView = () => {
|
|
|
}
|
|
|
>
|
|
|
<p className={"inline-block border font-semibold"}>18+</p>
|
|
|
- <p>Rated for 18+</p>
|
|
|
+ <p className={"text-[#5f6368]"}>Rated for 18+</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div className={"rounded-[10px] bg-[#01875f] py-[8px] text-center text-[#fff]"}>
|
|
|
+ <div
|
|
|
+ className={
|
|
|
+ "cursor-pointer rounded-[10px] bg-[#01875f] py-[8px] text-center text-[#fff]"
|
|
|
+ }
|
|
|
+ onClick={onPress}
|
|
|
+ >
|
|
|
Rapid Instalar
|
|
|
</div>
|
|
|
|
|
@@ -97,8 +182,6 @@ const CardView = () => {
|
|
|
|
|
|
<div>
|
|
|
<Swiper
|
|
|
- slidesPerView={3.2}
|
|
|
- spaceBetween={30}
|
|
|
pagination={{
|
|
|
clickable: true,
|
|
|
}}
|
|
@@ -109,11 +192,9 @@ const CardView = () => {
|
|
|
modules={[Autoplay]}
|
|
|
className="mySwiper"
|
|
|
>
|
|
|
- {images.map((prize, index: number) => (
|
|
|
+ {details?.preview_images?.map((url, index: number) => (
|
|
|
<SwiperSlide key={index}>
|
|
|
- <div className={"w-[1.1rem]"} style={{ borderRadius: ".1rem" }}>
|
|
|
- {index}
|
|
|
- </div>
|
|
|
+ <img src={url} alt={""} className={"h-[1.3rem] w-[100%]"} />
|
|
|
</SwiperSlide>
|
|
|
))}
|
|
|
</Swiper>
|
|
@@ -124,10 +205,7 @@ const CardView = () => {
|
|
|
<p>Sobre este jogo</p>
|
|
|
<i className={"iconfont icon-xiangyou2 text-[14px]"}></i>
|
|
|
</div>
|
|
|
- <p className={textcls}>
|
|
|
- {/*Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet deleniti eveniet*/}
|
|
|
- {/*natus voluptates? A aperiam laborum libero officiis quos veritatis?*/}
|
|
|
- </p>
|
|
|
+ <p className={textcls}>{details?.description}</p>
|
|
|
|
|
|
<div className={"my-[20px]"}>
|
|
|
<p>Atualizado em</p>
|
|
@@ -161,7 +239,7 @@ const CardView = () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div className={"mt-[20px] flex items-center space-x-[20px]"}>
|
|
|
+ <div className={"mt-[20px] flex space-x-[20px]"}>
|
|
|
<img
|
|
|
src={
|
|
|
"https://play-lh.googleusercontent.com/12USW7aflgz466ifDehKTnMoAep_VHxDmKJ6jEBoDZWCSefOC-ThRX14Mqe0r8KF9XCzrpMqJts=s20-rw"
|
|
@@ -174,7 +252,7 @@ const CardView = () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div className={"mt-[20px] flex items-center space-x-[20px]"}>
|
|
|
+ <div className={"mt-[20px] flex space-x-[20px]"}>
|
|
|
<img
|
|
|
src={
|
|
|
"https://play-lh.googleusercontent.com/W5DPtvB8Fhmkn5LbFZki_OHL3ZI1Rdc-AFul19UK4f7np2NMjLE5QquD6H0HAeEJ977u3WH4yaQ=s20-rw"
|
|
@@ -186,7 +264,7 @@ const CardView = () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div className={"mt-[20px] flex items-center space-x-[20px]"}>
|
|
|
+ <div className={"mt-[20px] flex space-x-[20px]"}>
|
|
|
<img
|
|
|
src={
|
|
|
"https://play-lh.googleusercontent.com/ohRyQRA9rNfhp7xLW0MtW1soD8SEX45Oec7MyH3FaxtukWUG_6GKVpvh3JiugzryLi7Bia02HPw=s20-rw"
|
|
@@ -204,9 +282,17 @@ const CardView = () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
<div>
|
|
|
- <div className={"flex justify-between py-[20px]"}>
|
|
|
- <p>Classificações e resenhas</p>
|
|
|
- <i className={"iconfont icon-xiangyou2 text-[14px]"}></i>
|
|
|
+ <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]"}>
|
|
@@ -311,7 +397,7 @@ const Comment = () => {
|
|
|
{users.map((item, index) => (
|
|
|
<div key={index} className={"py-[16px]"}>
|
|
|
<div className={"flex justify-between"}>
|
|
|
- <div className={"flex space-x-[20px]"}>
|
|
|
+ <div className={"flex items-center space-x-[20px]"}>
|
|
|
<Image
|
|
|
src={item.avatar}
|
|
|
alt={item.name}
|
|
@@ -321,18 +407,20 @@ const Comment = () => {
|
|
|
/>
|
|
|
<span className={"text-[14px]"}>{item.name}</span>
|
|
|
</div>
|
|
|
- <i className={"iconfont icon-fenxiang2"}></i>
|
|
|
+ <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-[14px] text-[#5f6368]"}>{item.desc}</div>
|
|
|
- <p className={"mt-[16px] text-[12px] text-[#5f6368]"}>
|
|
|
+ <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-[12px] text-[#5f6368]"}>
|
|
|
+ <div
|
|
|
+ className={"mt-[12px] flex space-x-[20px] text-[0.1111rem] text-[#5f6368]"}
|
|
|
+ >
|
|
|
<p>Você achou isso útil?</p>
|
|
|
<div></div>
|
|
|
<div
|
|
@@ -443,7 +531,8 @@ const Other = () => {
|
|
|
</div>
|
|
|
|
|
|
<div className={"mt-[10px]"}>
|
|
|
- <i className={"iconfont icon-star_full mr-[10px]"}></i>Sinalizar como impróprio
|
|
|
+ <i className={"iconfont icon-tongyong-biaojiqizi mr-[10px]"}></i>Sinalizar como
|
|
|
+ impróprio
|
|
|
</div>
|
|
|
</>
|
|
|
);
|
|
@@ -452,9 +541,7 @@ const Other = () => {
|
|
|
const CardFooter = () => {
|
|
|
return (
|
|
|
<>
|
|
|
- <div
|
|
|
- className={"mt-[36px] text-[0.0972rem] text-[#5f6368] [&>section>p]:cursor-pointer"}
|
|
|
- >
|
|
|
+ <div className={"mt-[36px] text-[16px] text-[#5f6368] [&>section>p]:cursor-pointer"}>
|
|
|
<section>
|
|
|
<p>Vales-presente</p>
|
|
|
<p>Resgatar</p>
|
|
@@ -480,35 +567,156 @@ const CardFooter = () => {
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
+
|
|
|
const getStoreApi = (data: { ch: string }) => {
|
|
|
- return server.post({
|
|
|
+ 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) => {
|
|
|
- return {
|
|
|
- type: 82,
|
|
|
- name: "关癸霖",
|
|
|
- title: "击踢脚线全单幸亏油门搂",
|
|
|
- icon_url: "https://avatars.githubusercontent.com/u/67110323",
|
|
|
- description: "亲回来点来五月。强量级整该矿列写再具。受办照此。",
|
|
|
- preview_images: [
|
|
|
- "https://loremflickr.com/400/400?lock=4729061696354976",
|
|
|
- "https://loremflickr.com/400/400?lock=2057580202751112",
|
|
|
- ],
|
|
|
- installer_type: 89,
|
|
|
- installer_args: "sit ea quis incididunt",
|
|
|
- };
|
|
|
+ setDetails(res.data);
|
|
|
+ return res.data;
|
|
|
});
|
|
|
- });
|
|
|
+ }, []);
|
|
|
return (
|
|
|
<div className={"text-[#000]"}>
|
|
|
- <Header />
|
|
|
- <CardView />
|
|
|
+ <InstallLoading
|
|
|
+ details={details}
|
|
|
+ visible={visible}
|
|
|
+ open={() => setVisible(true)}
|
|
|
+ close={() => setVisible(false)}
|
|
|
+ />
|
|
|
+ {details?.type === 1 ? <Header /> : null}
|
|
|
+ <CardView details={details} onPress={() => setVisible(true)} />
|
|
|
<Footer />
|
|
|
</div>
|
|
|
);
|