|
@@ -0,0 +1,171 @@
|
|
|
|
|
+'use client';
|
|
|
|
|
+import React from "react";
|
|
|
|
|
+import { useState, useRef } from "react";
|
|
|
|
|
+import { Swiper, SwiperSlide } from "swiper/react";
|
|
|
|
|
+import type { Swiper as SwiperType } from "swiper/types";
|
|
|
|
|
+// import Image from "next/image";
|
|
|
|
|
+
|
|
|
|
|
+// ================== TS类型定义 ==================
|
|
|
|
|
+type NextLevelInfo = {
|
|
|
|
|
+ group_id: number;
|
|
|
|
|
+ group_name: string;
|
|
|
|
|
+ growth_level_name: string;
|
|
|
|
|
+ min_growth_value: number;
|
|
|
|
|
+ icon: string | null;
|
|
|
|
|
+ require_first_order: boolean;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export type VipGrowthData = {
|
|
|
|
|
+ growth_value: number;
|
|
|
|
|
+ level_name: string;
|
|
|
|
|
+ discount_rate: number;
|
|
|
|
|
+ level_icon: string | null;
|
|
|
|
|
+ benefits: string[];
|
|
|
|
|
+ next_level: NextLevelInfo;
|
|
|
|
|
+ growth_to_next_level: string;
|
|
|
|
|
+ expires_at: string | null;
|
|
|
|
|
+ first_order_completed: boolean;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 福利项类型
|
|
|
|
|
+export type RewardItem = {
|
|
|
|
|
+ id: string;
|
|
|
|
|
+ title: string;
|
|
|
|
|
+ icon?: string;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export interface VipMemberCardProps {
|
|
|
|
|
+ vipData: VipGrowthData;
|
|
|
|
|
+ rewardList: RewardItem[];
|
|
|
|
|
+ onLevelUpClick?: () => void; // Level Up Now点击事件
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const VipLevel: React.FC<VipMemberCardProps> = ({
|
|
|
|
|
+ vipData,
|
|
|
|
|
+ rewardList,
|
|
|
|
|
+ onLevelUpClick
|
|
|
|
|
+}) => {
|
|
|
|
|
+ // 进度百分比计算,增加安全防护
|
|
|
|
|
+ let progressPercent = 0;
|
|
|
|
|
+ let progressText = "";
|
|
|
|
|
+ let descText = "";
|
|
|
|
|
+ const hasNextLevel = !!vipData.next_level;
|
|
|
|
|
+
|
|
|
|
|
+ if (hasNextLevel && vipData.next_level.min_growth_value > 0) {
|
|
|
|
|
+ const rawPercent = (vipData.growth_value / vipData.next_level.min_growth_value) * 100;
|
|
|
|
|
+ progressPercent = Math.min(Math.max(rawPercent, 0), 100);
|
|
|
|
|
+ progressText = `${vipData.growth_value}/${vipData.next_level.min_growth_value} Pts`;
|
|
|
|
|
+ descText = `X Growth Points to level up & unlock more rewards!`;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ progressText = vipData.growth_to_next_level || "Already max level";
|
|
|
|
|
+ descText = "X Growth Points to level up & unlock more rewards!";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+// swiper计算
|
|
|
|
|
+const SLIDES_PER_VIEW = 4.5;
|
|
|
|
|
+const SLIDES_PER_GROUP = 4;
|
|
|
|
|
+const SPACE_BETWEEN = 24;
|
|
|
|
|
+const swiperRef = useRef<SwiperType | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ const [, setActivePage] = useState(0);
|
|
|
|
|
+ const [, setIsBeginning] = useState(true);
|
|
|
|
|
+ const [, setIsEnd] = useState(rewardList.length <= SLIDES_PER_VIEW);
|
|
|
|
|
+ const pageCount = Math.ceil(rewardList.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);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="w-full pl-3 pt-4 bg-linear-to-b from-[#FFF3ED] to-[#FFFFFF]">
|
|
|
|
|
+ {/* ===== 顶部VIP卡片(后续直接替换背景图,卡片整体背景交给外层图片)===== */}
|
|
|
|
|
+ <div className="relative overflow-hidden rounded-3xl p-6 shadow-lg bg-[url('/image/account/vip/viplevel_7.png')] bg-no-repeat bg-size-[100%_100%] mb-6 mr-3">
|
|
|
|
|
+ {/* 后续这里放背景图:<Image src={bgUrl} alt="" fill className="object-cover" /> */}
|
|
|
|
|
+ <div className="relative z-10">
|
|
|
|
|
+ {/* Level标题 */}
|
|
|
|
|
+ <h2 className="text-2xl font-bold text-black">
|
|
|
|
|
+ {vipData.level_name}
|
|
|
|
|
+ </h2>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 进度条 + 积分文字 一行布局 */}
|
|
|
|
|
+ <div className="flex items-center gap-4 mt-2">
|
|
|
|
|
+ <div className="flex-1 h-2 bg-black/15 rounded-full overflow-hidden">
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="h-full bg-black rounded-full transition-all duration-500 ease-out"
|
|
|
|
|
+ style={{ width: `${progressPercent}%` }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span className="text-black font-normal text-xs whitespace-nowrap">
|
|
|
|
|
+ {progressText}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 描述文案 和 升级按钮 左右布局 */}
|
|
|
|
|
+ <div className="flex items-end justify-between mt-6 gap-4">
|
|
|
|
|
+ <p className="text-black text-xs w-[168px] font-normal">
|
|
|
|
|
+ {descText}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ {/* 升级按钮,满级时隐藏 */}
|
|
|
|
|
+ {hasNextLevel && (
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={onLevelUpClick}
|
|
|
|
|
+ className="bg-white text-black text-sm font-medium px-3 py-2 rounded-full shadow-sm hover:opacity-90 transition-opacity"
|
|
|
|
|
+ >
|
|
|
|
|
+ Level Up Now
|
|
|
|
|
+ </button>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* ===== Exclusive Rewards 福利区域 ===== */}
|
|
|
|
|
+ <h3 className="text-ly-18 font-medium text-black mb-4">Exclusive Rewards</h3>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <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"
|
|
|
|
|
+ >
|
|
|
|
|
+ {rewardList.map((item, index) => (
|
|
|
|
|
+ <SwiperSlide className="!h-auto" key={`${item.title}-${index}`}>
|
|
|
|
|
+
|
|
|
|
|
+ {/* <div className="relative aspect-3/4 w-full overflow-hidden rounded-md">
|
|
|
|
|
+ <Image
|
|
|
|
|
+ src={item.image}
|
|
|
|
|
+ alt={item.title}
|
|
|
|
|
+ fill
|
|
|
|
|
+ className="object-cover rounded-md"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div> */}
|
|
|
|
|
+ <div className="w-[72px] h-[72px] bg-white rounded-xl shadow-sm mb-3"></div>
|
|
|
|
|
+ <p className="text-center text-sm text-black">{item.title}</p>
|
|
|
|
|
+ </SwiperSlide>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </Swiper>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {/* 底部分页圆点指示器 */}
|
|
|
|
|
+ <div className="flex justify-center gap-2 mt-4">
|
|
|
|
|
+ <span className="w-2 h-2 rounded-full bg-black"></span>
|
|
|
|
|
+ <span className="w-2 h-2 rounded-full bg-black/20"></span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default VipLevel;
|