Pārlūkot izejas kodu

vip专题页部分调整

zhangzf 1 nedēļu atpakaļ
vecāks
revīzija
dfceec84a0

BIN
public/image/account/vip/viplevel_1.png


BIN
public/image/account/vip/viplevel_2.png


BIN
public/image/account/vip/viplevel_3.png


BIN
public/image/account/vip/viplevel_4.png


BIN
public/image/account/vip/viplevel_5.png


+ 171 - 0
src/app/(public)/customer/account/vip/_components/VipLevel.tsx

@@ -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;

+ 146 - 0
src/app/(public)/customer/account/vip/_components/VipRedeemGiftcard.tsx

@@ -0,0 +1,146 @@
+"use client";
+import { clientFetch } from "@/lib/restApiClient";
+import { useState, useEffect } from "react";
+import { Swiper, SwiperSlide } from "swiper/react";
+import { useCustomToast } from "@/utils/hooks/useToast";
+import type { Swiper as SwiperType } from "swiper/types";
+import { Navigation } from "swiper/modules";
+import type { GiftItem } from "@/types/api/gift/lists";
+const VipRedeemGiftcard = () => {
+  const [list, setList] = useState<GiftItem[]>([]);
+  const [swiperRef, setSwiperRef] = useState<SwiperType | null>(null);
+  const [myPoints, setmyPoints] = useState<number>();
+  const [activeIndex, setActiveIndex] = useState(0);
+  const handleSlideChange = (swiper: any) => {
+    setActiveIndex(swiper.activeIndex);
+  };
+   const { showToast } = useCustomToast();
+  useEffect(() => {
+    const load = async () => {
+      const resp = await clientFetch("/api/gift/lists");
+      const dataObj = resp.data.lists;
+      const dataArray = Object.values(dataObj) as GiftItem[];
+      setList(dataArray);
+      setmyPoints(resp.data.myPoints);
+      console.log("完整返回:", dataArray,dataObj, myPoints,'aaaa',list);
+    };
+
+    load();
+  }, []);
+  // 积分兑换逻辑
+  const handleAdd = async (id: number, points: number) => {
+    const res = await clientFetch("/api/gift/add", {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+      },
+      // 传给接口的参数,对应 params = await req.json()
+      body: JSON.stringify({
+        id: id,
+        // 其它需要传给PHP后端的字段
+      }),
+    });
+    if(res.message=="Success"){
+        showToast(res.data, 'success');
+    }
+    setList(prevList => {
+      return prevList.map(gift => {
+        if (gift.id === id) {
+          return {
+            ...gift,
+            status: true,
+            num: gift.num + 1 
+          };
+        }
+        return gift;
+      });
+    });
+  };
+  // 4. 真实数据按每2个拆分一组,替换原来的模拟拆分逻辑
+  const chunkSize = 2;
+  const productBlocks: GiftItem[][] = [];
+  for (let i = 0; i < list.length; i += chunkSize) {
+    productBlocks.push(list.slice(i, i + chunkSize));
+  }
+  console.log("productBlocks-----------------------",productBlocks,list);
+  
+  const handleIndicatorClick = (index: any) => {
+    if (swiperRef) {
+      swiperRef.slideTo(index);
+      setActiveIndex(index);
+    }
+  };
+  return (
+    <div>
+      <div className="px-4 pt-4">
+        <Swiper
+          modules={[Navigation]}
+          spaceBetween={10}
+          slidesPerView={1}
+          onSwiper={(swiper) => setSwiperRef(swiper)}
+          onSlideChange={handleSlideChange}
+          className="mb-6"
+        >
+          {productBlocks.map((block, blockIndex) => (
+            <SwiperSlide key={blockIndex}>
+              <div className="flex flex-wrap gap-3">
+                {block.map((item) => (
+                  <div
+                    key={item.id}
+                    className="rounded-md w-full max-w-[358px] p-4 pt-2 h-33.5 flex items-center justify-between text-white bg-[url('/image/account/202609021400.png')] bg-no-repeat bg-size-[100%_100%]"
+                  >
+                    {/* 左侧折扣+积分信息 */}
+                    <div className="flex flex-col h-full py-2 justify-between">
+                      <span className="text-2xl leading-7.5 font-bold ">
+                        Gift Card
+                      </span>
+                      <span className="text-sm font-normal  opacity-90">
+                        {item.points}Pts
+                      </span>
+                    </div>
+
+                    {/* 右侧按钮区域 */}
+                    <div className="w-[132px] h-full  text-center flex flex-col justify-between pt-1">
+                      <div className="font-bold text-5xl">${item.amount}</div>
+                      {/* 按钮:已兑换显示 USED,未兑换显示 REDEEM */}
+                      <button
+                        onClick={() => handleAdd(item.id, item.points)}
+                        className={`w-full h-8  rounded-2xl border mx-auto ${
+                          item.status
+                            ? "bg-gray-300 text-gray-600 border-gray-400 cursor-not-allowed"
+                            : "bg-white text-[#01754AFF] border-white hover:bg-orange-50 transition-colors"
+                        }`}
+                        disabled={item.status}
+                      >
+                        {item.num ? "USED" : "REDEEM"}
+                      </button>
+                      {/* <p className="text-xs mt-1 opacity-90">ALL Products</p> */}
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </SwiperSlide>
+          ))}
+        </Swiper>
+        {/* 底部指示器按钮:2个,激活态黑色,未激活灰色 */}
+        <div className="flex justify-center gap-2">
+          <button
+            onClick={() => handleIndicatorClick(0)}
+            className={`w-6 h-[5px] rounded-full  opacity-100 ${
+              activeIndex === 0 ? "bg-[#333]" : "bg-[#E5E5E5]"
+            }`}
+            aria-label="切换到第一块"
+          />
+          <button
+            onClick={() => handleIndicatorClick(1)}
+            className={`w-6 h-[5px] rounded-full  opacity-100 ${
+              activeIndex === 1 ? "bg-[#333]" : "bg-[#E5E5E5]"
+            }`}
+            aria-label="切换到第二块"
+          />
+        </div>
+      </div>
+    </div>
+  );
+};
+export default VipRedeemGiftcard;

+ 27 - 1
src/app/(public)/customer/account/vip/page.tsx

@@ -1,5 +1,23 @@
 import Link from "next/link";
-const VipCenterPage = () => {
+import { restApiFetch } from "@utils/bagisto/index";
+import VipLevel ,{type RewardItem} from "./_components/VipLevel"
+import VipRedeemGiftcard from "./_components/VipRedeemGiftcard"
+const VipCenterPage = async() => {
+      const res = await restApiFetch({
+        api: `/customer/profile`,
+        method: "GET",
+        isRoute: false,
+      });
+    console.log('accountres-----------------------------:',res);
+    const vipData = res.body.data.growth;
+  const rewardList: RewardItem[] = [
+    {id:"r1", title:"Sign-up Gift"},
+    {id:"r2", title:"Fast Free Shipping"},
+    {id:"r3", title:"Shop & Earn Point"},
+    {id:"r4", title:"Customer Service"},
+    {id:"r5", title:"Level Coupon"},
+  ];
+
   return (
     <div className="w-full h-full">
       <div className=" text-black py-4 px-4 flex items-center border-b border-[#F9F9F9FF]">
@@ -26,6 +44,14 @@ const VipCenterPage = () => {
         </Link>
         <h1 className="text-lg w-full text-center  font-normal">VIP Center</h1>
       </div>
+      <VipLevel
+        vipData={vipData}
+        rewardList={rewardList}
+      />
+      <div className="w-full px-3">
+        <hr className="border-t border-[#ACBDB8] border-solid w-full" />
+      </div>
+      <VipRedeemGiftcard />
     </div>
   );
 };