| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- import { useState, useRef, useEffect } from "react";
- import { Swiper, SwiperSlide } from "swiper/react";
- import type { Swiper as SwiperType } from "swiper/types";
- // import { Navigation } from "swiper/modules";
- import Image from "next/image";
- import Link from "next/link";
- import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
- import type { HomeProduct,HomeSection } from "@/types/home/type";
- interface newArrivalSectionProps{
- trendingSection:HomeSection |undefined;
- }
- // // 商品数据,每个卡片携带videoUrl视频地址
- // type ProductItem = {
- // id: number;
- // img: string;
- // title: string;
- // price: number;
- // videoUrl: string;
- // };
- // const productList: ProductItem[] = [
- // {
- // id: 1,
- // img: "/img/hair-video-1.jpg",
- // title: "14-36 In Water Wave Human Hair HD Inv...",
- // price: 146.1,
- // videoUrl: "/video/hair-demo-1.mp4",
- // },
- // {
- // id: 2,
- // img: "/img/hair-video-2.jpg",
- // title: "14-36 In Water Wave Human Hair HD Inv...",
- // price: 146.1,
- // videoUrl: "/video/hair-demo-2.mp4",
- // },
- // {
- // id: 3,
- // img: "/img/hair-video-3.jpg",
- // title: "14-36 In Water Wave Human Hair HD Inv...",
- // price: 146.1,
- // videoUrl: "/video/hair-demo-3.mp4",
- // },
- // {
- // id: 4,
- // img: "/img/hair-video-4.jpg",
- // title: "14-36 In Water Wave Human Hair HD Inv...",
- // price: 146.1,
- // videoUrl: "/video/hair-demo-4.mp4",
- // },
- // ];
- const SLIDES_PER_VIEW = 6.5;
- const SLIDES_PER_GROUP = 6;
- const SPACE_BETWEEN = 24;
- const Trending = ({ trendingSection }: newArrivalSectionProps) => {
- const trending: HomeProduct[] = trendingSection?.products ?? [];
- // 视频弹窗状态
- const [openVideoModal, setOpenVideoModal] = useState(false);
- const [currentVideoSrc, setCurrentVideoSrc] = useState("");
- const videoRef = useRef<HTMLVideoElement>(null);
- // 弹窗打开自动播放
- useEffect(() => {
- if (openVideoModal && videoRef.current) {
- videoRef.current.play().catch((err) => console.log("播放拦截", err));
- }
- }, [openVideoModal]);
- const swiperRef = useRef<SwiperType | null>(null);
- const [activePage, setActivePage] = useState(0);
- const [isBeginning, setIsBeginning] = useState(true);
- const [isEnd, setIsEnd] = useState(trending.length <= SLIDES_PER_VIEW);
- if (!trending || trending.length === 0) return null;
- // const productList = trending;
- // 打开弹窗并赋值视频地址
- const handlePlayClick = (videoUrl: string) => {
- setCurrentVideoSrc(videoUrl);
- setOpenVideoModal(true);
- };
- // 关闭弹窗,暂停视频
- const closeModal = () => {
- setOpenVideoModal(false);
- if (videoRef.current) {
- videoRef.current.pause();
- videoRef.current.currentTime = 0;
- }
- };
- const pageCount = Math.ceil(trending.length / SLIDES_PER_VIEW);
- const syncSwiperState = (swiper: SwiperType) => {
- const pageIndex = Math.min(swiper.snapIndex, Math.max(pageCount - 1, 0));
- setActivePage(pageIndex);
- setIsBeginning(swiper.isBeginning);
- setIsEnd(swiper.isEnd);
- };
- const handlePrev = () => {
- swiperRef.current?.slidePrev();
- };
- const handleNext = () => {
- swiperRef.current?.slideNext();
- };
- const handlePaginationClick = (pageIndex: number) => {
- const swiper = swiperRef.current;
- if (!swiper) return;
- swiper.slideTo(pageIndex * SLIDES_PER_GROUP);
- };
- return (
- <div className=" relative w-full mx-auto">
- <h2 className="text-4xl text-center font-bold leading-ly-46 mt-12 mb-8">{trendingSection?.title}</h2>
- <div className="pl-12">
- <Swiper
- slidesPerView={SLIDES_PER_VIEW}
- slidesPerGroup={SLIDES_PER_GROUP}
- spaceBetween={SPACE_BETWEEN}
- speed={300}
- watchOverflow
- onSwiper={(swiper) => {
- swiperRef.current = swiper;
- syncSwiperState(swiper);
- }}
- onSlideChange={syncSwiperState}
- onReachBeginning={syncSwiperState}
- onReachEnd={syncSwiperState}
- onFromEdge={syncSwiperState}
- className="w-full"
- >
- {trending.map((item, index) => (
- <SwiperSlide key={`${item.id}-${index}`}>
- <div className=" overflow-hidden flex flex-col">
- {/* 图片区域 + 中间播放按钮 */}
- <div className="relative rounded-xl w-full aspect-[4/5] overflow-hidden rounded-xl">
- <img
- src={item.image_url}
- alt={item.name}
- className="w-full h-full object-cover"
- />
- {/* 居中圆形播放按钮 */}
- <button
- onClick={() =>
- handlePlayClick(item.videoUrl || "/video/hair-demo-1.mp4")
- }
- className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
- w-24 h-24 rounded-full bg-white/60 backdrop-blur-sm
- flex items-center justify-center transition hover:bg-white/80"
- >
- <svg
- width="36"
- height="36"
- viewBox="0 0 24 24"
- fill="none"
- stroke="#222"
- strokeWidth="2"
- >
- <polygon points="5 3 19 12 5 21 5 3" />
- </svg>
- </button>
- </div>
- {/* 底部文字价格区域 */}
- <div className="p-3 flex flex-col rounded-xl mt-3 bg-[#f0f8f5] gap-3">
- <Link href={`/${item.urlKey}.html`} className="text-sm text-gray-800 truncate">
- {item.name}
- </Link>
- {/* 价格 + 购物车图标 */}
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-1">
- <span className="font-semibold">${item.min_price}</span>
- <span className="text-xs text-gray-500 line-through">
- ${item.price}
- </span>
- </div>
- {/* 购物车图标 */}
- <OpenAddToCartModalButton
- productId={item.id}
- icon={
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="32"
- height="32"
- viewBox="0 0 32 32"
- fill="none"
- >
- <rect
- x="0"
- y="0"
- width="32"
- height="32"
- rx="8"
- fill="#036141"
- ></rect>
- <path
- stroke="rgba(255, 255, 255, 1)"
- stroke-width="1.3333333333333333"
- stroke-linejoin="round"
- stroke-linecap="round"
- d="M6.40039 7.79993L8.03372 7.79993L8.73372 10.5999M8.73372 10.5999L11.0671 19.9333L23.2004 19.9333L25.5337 10.5999L8.73372 10.5999Z"
- ></path>
- <ellipse
- cx="11.066040515899658"
- cy="23.199928283691406"
- rx="1.4000248908996582"
- ry="1.4000015258789062"
- stroke="rgba(255, 255, 255, 1)"
- stroke-width="1.3333333333333333"
- stroke-linejoin="round"
- stroke-linecap="round"
- ></ellipse>
- <ellipse
- cx="23.1998291015625"
- cy="23.199928283691406"
- rx="1.4000244140625"
- ry="1.4000015258789062"
- stroke="rgba(255, 255, 255, 1)"
- stroke-width="1.3333333333333333"
- stroke-linejoin="round"
- stroke-linecap="round"
- ></ellipse>
- <path
- stroke="rgba(255, 255, 255, 1)"
- stroke-width="1.3333333333333333"
- stroke-linejoin="round"
- stroke-linecap="round"
- d="M15.2656 15.2665L18.999 15.2665"
- ></path>
- <path
- stroke="rgba(255, 255, 255, 1)"
- stroke-width="1.3333333333333333"
- stroke-linejoin="round"
- stroke-linecap="round"
- d="M17.1348 17.1332L17.1348 13.3999"
- ></path>
- </svg>
- }
- />
- </div>
- </div>
- </div>
- </SwiperSlide>
- ))}
- </Swiper>
- </div>
- {/* ====================== 全屏视频遮罩弹窗 ====================== */}
- {openVideoModal && (
- <div
- className="fixed inset-0 z-[9999] bg-black/95 flex items-center justify-center p-4"
- onClick={closeModal} // 点击遮罩空白关闭
- >
- {/* 弹窗内层,阻止冒泡 */}
- <div
- className="relative w-full max-w-4xl"
- onClick={(e) => e.stopPropagation()}
- >
- {/* 右上角关闭按钮 */}
- <button
- onClick={closeModal}
- className="absolute -top-12 right-0 text-white text-3xl hover:text-gray-300"
- >
- ✕
- </button>
- {/* 视频播放器 */}
- <video
- ref={videoRef}
- src={currentVideoSrc}
- controls
- className="w-full rounded-lg"
- />
- </div>
- </div>
- )}
- {!isBeginning && (
- <button
- type="button"
- aria-label="Previous"
- onClick={handlePrev}
- className="
- absolute
- left-18
- top-1/2
- z-10
- flex
- h-10
- w-10
- -translate-y-1/2
- items-center
- justify-center
- "
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="40"
- height="40"
- viewBox="0 0 40 40"
- fill="none"
- className="rotate-180"
- >
- <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
- <path
- d="M17 26L23 20L17 14"
- stroke="rgba(255, 255, 255, 1)"
- strokeWidth="1.5"
- strokeLinejoin="round"
- strokeLinecap="square"
- />
- </svg>
- </button>
- )}
- {/* Next - 最后一页不显示 */}
- {!isEnd && (
- <button
- type="button"
- aria-label="Next"
- onClick={handleNext}
- className="
- absolute
- right-6
- top-1/2
- z-10
- flex
- h-10
- w-10
- -translate-y-1/2
- items-center
- justify-center
- "
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="40"
- height="40"
- viewBox="0 0 40 40"
- fill="none"
- >
- <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
- <path
- d="M17 26L23 20L17 14"
- stroke="rgba(255, 255, 255, 1)"
- strokeWidth="1.5"
- strokeLinejoin="round"
- strokeLinecap="square"
- />
- </svg>
- </button>
- )}
- {/* Pagination */}
- {pageCount > 1 && (
- <div
- className="
- mt-4
- flex
- items-center
- justify-center
- gap-2
- "
- >
- {Array.from({
- length: pageCount,
- }).map((_, index) => (
- <button
- key={index}
- type="button"
- aria-label={`Go to page ${index + 1}`}
- onClick={() => handlePaginationClick(index)}
- className={`
- h-2
- w-10
- rounded-full
- transition-all
- duration-300
- ${activePage === index ? "bg-black" : "bg-[#DBDBDB]!"}
- `}
- />
- ))}
- </div>
- )}
- <div className="relative w-full aspect-[4/3] mt-8">
- <Image
- src={
- "https://cdn.asteriahair.com/uploads/202607/01/20260701165110_3fff435adf276b89.jpg"
- }
- alt={"cscscs"}
- fill
- className="object-cover rounded-md"
- />
- </div>
- </div>
- );
- };
- export default Trending;
|