zhangzf пре 6 дана
родитељ
комит
8205720f07

+ 72 - 0
src/app/(public)/customer/account/mycoupon/page.tsx

@@ -0,0 +1,72 @@
+"use client";
+import React, { useState } from 'react';
+import Link from "next/link";
+import CouponList from '@/components/customer/mycounpon/couponList';
+import GiftCardList from '@/components/customer/mycounpon/giftCardList';
+
+const CouponsPage: React.FC = () => {
+  // Tab 切换状态:'coupon' / 'giftcard'
+  const [activeTab, setActiveTab] = useState<'coupon' | 'giftcard'>('coupon');
+
+  return (
+    <div className="w-full h-full">
+       <div className="bg-[#fff] pr-2.5 pl-2.5 w-full text-center text-base h-11 leading-11 font-semibold relative text-[#0b0b0b]">
+        <Link
+          className="absolute left-2.5 text-2xl inline-block top-1/2  -translate-y-1/2"
+          href="/customer/account/mypoints"
+          aria-label="Go to customer account page"
+        >
+          <svg
+            xmlns="http://www.w3.org/2000/svg"
+            width="24"
+            height="24"
+            viewBox="0 0 24 24"
+            fill="none"
+          >
+            <path
+              stroke="rgba(0, 0, 0, 1)"
+              stroke-width="1.2"
+              stroke-linejoin="round"
+              stroke-linecap="round"
+              d="M15 18L9 12L15 6"
+            ></path>
+          </svg>
+        </Link>
+        <span className="vipReturnTitles">Accessories</span>
+      </div>
+
+      {/* Tab 切换栏 */}
+      <div className="flex w-full">
+        {/* My Coupon Tab */}
+        <button
+          onClick={() => setActiveTab('coupon')}
+          className={`flex-1 py-3 text-center font-medium ${
+            activeTab === 'coupon' 
+              ? 'bg-white text-black border-b-2 border-black' 
+              : 'bg-gray-100 text-gray-500'
+          }`}
+        >
+          My Coupon
+        </button>
+        {/* My Giftcard Tab */}
+        <button
+          onClick={() => setActiveTab('giftcard')}
+          className={`flex-1 py-3 text-center font-medium ${
+            activeTab === 'giftcard' 
+              ? 'bg-black text-white' 
+              : 'bg-gray-100 text-gray-500'
+          }`}
+        >
+          My Giftcard
+        </button>
+      </div>
+
+      {/* 内容展示区 */}
+      <div className="mt-2">
+        {activeTab === 'coupon' ? <CouponList /> : <GiftCardList />}
+      </div>
+    </div>
+  );
+};
+
+export default CouponsPage;

+ 90 - 0
src/components/customer/mycounpon/couponList.tsx

@@ -0,0 +1,90 @@
+// src/components/CouponList.tsx
+import React, { useState, useEffect } from 'react';
+import { useInfiniteScroll } from './useInfiniteScroll';
+
+// 优惠券数据类型
+interface CouponItem {
+  id: number;
+  discount: string; // 优惠金额(如 $15 OFF)
+  desc: string; // 优惠描述
+  valid: string; // 有效期
+}
+
+const CouponList: React.FC = () => {
+  // 初始化优惠券数据
+  const [coupons, setCoupons] = useState<CouponItem[]>([
+    { id: 1, discount: '$15 OFF', desc: 'For All Products', valid: 'Valid:6/2/2026-12/31/2026' },
+    { id: 2, discount: '$10 OFF', desc: 'For All Products', valid: 'Valid:6/2/2026-12/31/2026' },
+  ]);
+  const [hasMore, setHasMore] = useState(true); // 模拟:初始有更多数据,加载2次后无更多
+
+  // 加载更多优惠券(模拟异步请求)
+  const loadMoreCoupons = async () => {
+    return new Promise<void>((resolve) => {
+      setTimeout(() => {
+        if (coupons.length >= 6) { // 模拟最多加载6条
+          setHasMore(false);
+          resolve();
+          return;
+        }
+        // 模拟新增数据
+        const newCoupons = [
+          { 
+            id: coupons.length + 1, 
+            discount: `$${5 * (coupons.length + 1)} OFF`, 
+            desc: 'For All Products', 
+            valid: 'Valid:6/2/2026-12/31/2026' 
+          },
+        ];
+        setCoupons(prev => [...prev, ...newCoupons]);
+        resolve();
+      }, 800);
+    });
+  };
+
+  // 复用懒加载Hook
+  const { containerRef, isLoading } = useInfiniteScroll({
+    loadMore: loadMoreCoupons,
+    hasMore,
+  });
+
+  return (
+    <div 
+      ref={containerRef}
+      className="h-[calc(100vh-80px)] overflow-auto px-2 py-4"
+    >
+      {/* 优惠券列表 */}
+      <div className="space-y-4">
+        {coupons.map((item) => (
+          <div 
+            key={item.id}
+            className="flex items-center bg-pink-100 rounded-lg overflow-hidden"
+          >
+            {/* 优惠信息区 */}
+            <div className="flex-1 p-4 text-pink-600">
+              <h3 className="text-2xl font-bold">{item.discount}</h3>
+              <p className="text-sm mt-1">{item.desc}</p>
+              <p className="text-xs mt-2 text-pink-500">{item.valid}</p>
+            </div>
+            {/* 分割线 */}
+            <div className="w-0.5 bg-pink-300 h-16 relative">
+              <div className="absolute top-0 left-[-4px] w-2 h-2 bg-white border border-pink-300 rounded-full"></div>
+              <div className="absolute bottom-0 left-[-4px] w-2 h-2 bg-white border border-pink-300 rounded-full"></div>
+            </div>
+            {/* 使用按钮区 */}
+            <div className="px-6 py-4 font-bold text-black">
+              Use Now
+            </div>
+          </div>
+        ))}
+      </div>
+
+      {/* 加载状态/无更多提示 */}
+      <div className="text-center mt-4 text-gray-400 text-sm">
+        {isLoading ? 'Loading...' : hasMore ? '' : 'No More Data'}
+      </div>
+    </div>
+  );
+};
+
+export default CouponList;

+ 89 - 0
src/components/customer/mycounpon/giftCardList.tsx

@@ -0,0 +1,89 @@
+// src/components/GiftCardList.tsx
+import React, { useState } from 'react';
+import { useInfiniteScroll } from './useInfiniteScroll';
+
+// 礼品卡数据类型
+interface GiftCardItem {
+  id: number;
+  amount: string; // 礼品卡金额
+  status: string; // 状态
+  valid: string; // 有效期
+}
+
+const GiftCardList: React.FC = () => {
+  // 初始化礼品卡数据
+  const [giftCards, setGiftCards] = useState<GiftCardItem[]>([
+    { id: 1, amount: '$50 Gift Card', status: 'Unused', valid: 'Valid:6/2/2026-12/31/2026' },
+    { id: 2, amount: '$100 Gift Card', status: 'Unused', valid: 'Valid:6/2/2026-12/31/2026' },
+  ]);
+  const [hasMore, setHasMore] = useState(true);
+
+  // 加载更多礼品卡(模拟异步请求)
+  const loadMoreGiftCards = async () => {
+    return new Promise<void>((resolve) => {
+      setTimeout(() => {
+        if (giftCards.length >= 6) {
+          setHasMore(false);
+          resolve();
+          return;
+        }
+        const newGiftCards = [
+          { 
+            id: giftCards.length + 1, 
+            amount: `$${(giftCards.length + 1) * 50} Gift Card`, 
+            status: 'Unused', 
+            valid: 'Valid:6/2/2026-12/31/2026' 
+          },
+        ];
+        setGiftCards(prev => [...prev, ...newGiftCards]);
+        resolve();
+      }, 800);
+    });
+  };
+
+  // 复用懒加载Hook
+  const { containerRef, isLoading } = useInfiniteScroll({
+    loadMore: loadMoreGiftCards,
+    hasMore,
+  });
+
+  return (
+    <div 
+      ref={containerRef}
+      className="h-[calc(100vh-80px)] overflow-auto px-2 py-4"
+    >
+      {/* 礼品卡列表 */}
+      <div className="space-y-4">
+        {giftCards.map((item) => (
+          <div 
+            key={item.id}
+            className="flex items-center bg-blue-100 rounded-lg overflow-hidden"
+          >
+            {/* 礼品卡信息区 */}
+            <div className="flex-1 p-4 text-blue-600">
+              <h3 className="text-2xl font-bold">{item.amount}</h3>
+              <p className="text-sm mt-1">{item.status}</p>
+              <p className="text-xs mt-2 text-blue-500">{item.valid}</p>
+            </div>
+            {/* 分割线 */}
+            <div className="w-0.5 bg-blue-300 h-16 relative">
+              <div className="absolute top-0 left-[-4px] w-2 h-2 bg-white border border-blue-300 rounded-full"></div>
+              <div className="absolute bottom-0 left-[-4px] w-2 h-2 bg-white border border-blue-300 rounded-full"></div>
+            </div>
+            {/* 使用按钮区 */}
+            <div className="px-6 py-4 font-bold text-black">
+              Use Now
+            </div>
+          </div>
+        ))}
+      </div>
+
+      {/* 加载状态/无更多提示 */}
+      <div className="text-center mt-4 text-gray-400 text-sm">
+        {isLoading ? 'Loading...' : hasMore ? '' : 'No More Data'}
+      </div>
+    </div>
+  );
+};
+
+export default GiftCardList;

+ 36 - 0
src/components/customer/mycounpon/useInfiniteScroll.tsx

@@ -0,0 +1,36 @@
+// src/hooks/useInfiniteScroll.ts
+import { useEffect, useRef, useState } from 'react';
+
+type UseInfiniteScrollProps = {
+  loadMore: () => Promise<void>; // 加载更多的异步方法
+  hasMore: boolean; // 是否还有更多数据
+};
+
+export const useInfiniteScroll = ({ loadMore, hasMore }: UseInfiniteScrollProps) => {
+  const [isLoading, setIsLoading] = useState(false);
+  const containerRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    const container = containerRef.current;
+    if (!container) return;
+
+    const handleScroll = async () => {
+      // 触底判断:滚动条距离底部 < 100px 且 有更多数据 且 不在加载中
+      const { scrollTop, scrollHeight, clientHeight } = container;
+      if (
+        scrollTop + clientHeight >= scrollHeight - 100 &&
+        hasMore &&
+        !isLoading
+      ) {
+        setIsLoading(true);
+        await loadMore();
+        setIsLoading(false);
+      }
+    };
+
+    container.addEventListener('scroll', handleScroll);
+    return () => container.removeEventListener('scroll', handleScroll);
+  }, [loadMore, hasMore, isLoading]);
+
+  return { containerRef, isLoading };
+};