JinGangSwiper.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. "use client";
  2. import { useState, useRef } from "react";
  3. import { Swiper, SwiperSlide } from "swiper/react";
  4. import type { Swiper as SwiperType } from "swiper/types";
  5. // import { Navigation } from "swiper/modules";
  6. import Image from "next/image";
  7. import Link from "next/link";
  8. import type { HomeSectionImage,HomeSection } from "@/types/home/type";
  9. interface jinGangSectionProps{
  10. jinGangSection:HomeSection |undefined;
  11. }
  12. // interface jinGangSection{
  13. // id: number;
  14. // type: string;
  15. // name: string;
  16. // sort_order: number;
  17. // images:HomeSectionImage[];
  18. // }
  19. // const MOCK_DATA = [
  20. // [
  21. // {
  22. // name: "Hd Lace Wig",
  23. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
  24. // },
  25. // {
  26. // name: "Ready To Go",
  27. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
  28. // },
  29. // {
  30. // name: "Colored Wig",
  31. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
  32. // },
  33. // {
  34. // name: "U&V Part Wig",
  35. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
  36. // },
  37. // {
  38. // name: "New Arrival",
  39. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
  40. // },
  41. // {
  42. // name: "360 Wig",
  43. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095740_8181b0e039800e07.png",
  44. // },
  45. // ],
  46. // [
  47. // {
  48. // name: "13x4 Lace Wig",
  49. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
  50. // },
  51. // {
  52. // name: "Body Wave Wig",
  53. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
  54. // },
  55. // {
  56. // name: "Deep Wave Wig",
  57. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
  58. // },
  59. // {
  60. // name: "Straight Wig",
  61. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
  62. // },
  63. // {
  64. // name: "Curly Wig",
  65. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
  66. // },
  67. // {
  68. // name: "Bob Wig",
  69. // img: "https://cdn.asteriahair.com/uploads/202602/11/20260211095800_8181b0e039800e07.png",
  70. // },
  71. // ],
  72. // ];
  73. const SLIDES_PER_VIEW = 6.5;
  74. const SLIDES_PER_GROUP = 6;
  75. const SPACE_BETWEEN = 24;
  76. export default function JinGangSwiper({jinGangSection} :jinGangSectionProps ) {
  77. const jinGang: HomeSectionImage[] = jinGangSection?.images ?? [];
  78. const swiperRef = useRef<SwiperType | null>(null);
  79. const [activePage, setActivePage] = useState(0);
  80. const [isBeginning, setIsBeginning] = useState(true);
  81. const [isEnd, setIsEnd] = useState(jinGang.length <= SLIDES_PER_VIEW);
  82. const pageCount = Math.ceil(jinGang.length / SLIDES_PER_VIEW);
  83. const syncSwiperState = (swiper: SwiperType) => {
  84. const pageIndex = Math.min(swiper.snapIndex, Math.max(pageCount - 1, 0));
  85. setActivePage(pageIndex);
  86. setIsBeginning(swiper.isBeginning);
  87. setIsEnd(swiper.isEnd);
  88. };
  89. const handlePrev = () => {
  90. swiperRef.current?.slidePrev();
  91. };
  92. const handleNext = () => {
  93. swiperRef.current?.slideNext();
  94. };
  95. const handlePaginationClick = (pageIndex: number) => {
  96. const swiper = swiperRef.current;
  97. if (!swiper) return;
  98. swiper.slideTo(pageIndex * SLIDES_PER_GROUP);
  99. };
  100. return (
  101. <div className="relative w-full mt-6">
  102. {/* Swiper */}
  103. <div className="pl-12">
  104. <Swiper
  105. slidesPerView={SLIDES_PER_VIEW}
  106. slidesPerGroup={SLIDES_PER_GROUP}
  107. spaceBetween={SPACE_BETWEEN}
  108. speed={300}
  109. watchOverflow
  110. onSwiper={(swiper) => {
  111. swiperRef.current = swiper;
  112. syncSwiperState(swiper);
  113. }}
  114. onSlideChange={syncSwiperState}
  115. onReachBeginning={syncSwiperState}
  116. onReachEnd={syncSwiperState}
  117. onFromEdge={syncSwiperState}
  118. className="w-full"
  119. >
  120. {jinGang.map((item, index) => (
  121. <SwiperSlide key={`${item.title}-${index}`}>
  122. {item.link ? (
  123. <Link
  124. href={item.link}
  125. className="relative aspect-[3/4] w-full overflow-hidden rounded-md w-full"
  126. aria-label={item.title}
  127. >
  128. <Image
  129. src={item.image}
  130. alt={item.title}
  131. fill
  132. className="object-cover rounded-md"
  133. />
  134. </Link>
  135. ) : (
  136. <div className="relative aspect-[3/4] w-full overflow-hidden rounded-md w-full">
  137. <Image
  138. src={item.image}
  139. alt={item.title}
  140. fill
  141. className="object-cover rounded-md"
  142. />
  143. </div>
  144. )}
  145. </SwiperSlide>
  146. ))}
  147. </Swiper>
  148. </div>
  149. {!isBeginning && (
  150. <button
  151. type="button"
  152. aria-label="Previous"
  153. onClick={handlePrev}
  154. className="
  155. absolute
  156. left-18
  157. top-1/2
  158. z-10
  159. flex
  160. h-10
  161. w-10
  162. -translate-y-1/2
  163. items-center
  164. justify-center
  165. "
  166. >
  167. <svg
  168. xmlns="http://www.w3.org/2000/svg"
  169. width="40"
  170. height="40"
  171. viewBox="0 0 40 40"
  172. fill="none"
  173. className="rotate-180"
  174. >
  175. <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
  176. <path
  177. d="M17 26L23 20L17 14"
  178. stroke="rgba(255, 255, 255, 1)"
  179. strokeWidth="1.5"
  180. strokeLinejoin="round"
  181. strokeLinecap="square"
  182. />
  183. </svg>
  184. </button>
  185. )}
  186. {/* Next - 最后一页不显示 */}
  187. {!isEnd && (
  188. <button
  189. type="button"
  190. aria-label="Next"
  191. onClick={handleNext}
  192. className="
  193. absolute
  194. right-6
  195. top-1/2
  196. z-10
  197. flex
  198. h-10
  199. w-10
  200. -translate-y-1/2
  201. items-center
  202. justify-center
  203. "
  204. >
  205. <svg
  206. xmlns="http://www.w3.org/2000/svg"
  207. width="40"
  208. height="40"
  209. viewBox="0 0 40 40"
  210. fill="none"
  211. >
  212. <circle cx="20" cy="20" r="20" fill="#000000" fillOpacity="0.5" />
  213. <path
  214. d="M17 26L23 20L17 14"
  215. stroke="rgba(255, 255, 255, 1)"
  216. strokeWidth="1.5"
  217. strokeLinejoin="round"
  218. strokeLinecap="square"
  219. />
  220. </svg>
  221. </button>
  222. )}
  223. {/* Pagination */}
  224. {pageCount > 1 && (
  225. <div
  226. className="
  227. mt-4
  228. flex
  229. items-center
  230. justify-center
  231. gap-2
  232. "
  233. >
  234. {Array.from({
  235. length: pageCount,
  236. }).map((_, index) => (
  237. <button
  238. key={index}
  239. type="button"
  240. aria-label={`Go to page ${index + 1}`}
  241. onClick={() => handlePaginationClick(index)}
  242. className={`
  243. h-2
  244. w-10
  245. rounded-full
  246. transition-all
  247. duration-300
  248. ${activePage === index ? "bg-black" : "bg-[#DBDBDB]!"}
  249. `}
  250. />
  251. ))}
  252. </div>
  253. )}
  254. </div>
  255. );
  256. }