Sfoglia il codice sorgente

Merge branch 'zzf-account'

zhangzf 2 giorni fa
parent
commit
7b35aadc2f

BIN
public/image/account/202306141502.png


BIN
public/image/account/202306141504.png


BIN
public/image/account/202306141505.png


BIN
public/image/account/202306141506.png


BIN
public/image/account/202605201502.png


File diff suppressed because it is too large
+ 221 - 0
src/app/(public)/customer/account/mypoints/page.tsx


+ 253 - 0
src/app/(public)/customer/account/pointlist/page.tsx

@@ -0,0 +1,253 @@
+"use client";
+import Image from "next/image";
+import React, { useState, useEffect, useRef } from 'react';
+
+// 模拟积分明细数据(可替换为接口请求)
+const mockPointsData = [
+  {
+    id: 1,
+    type: 'rewarded',
+    title: 'Check In',
+    points: '+10 Points',
+    date: 'May 17, 2026 11:07 PM',
+    currentPoints: 'My points:100009',
+  },
+  {
+    id: 2,
+    type: 'rewarded',
+    title: 'Points Return of Redeem Hair Usage',
+    points: '+12000 Points',
+    date: 'Oct 23, 2025 1:11 AM',
+    currentPoints: 'My points:99999',
+  },
+  {
+    id: 3,
+    type: 'used',
+    title: 'Points Redeem Hair',
+    points: '-6000 Points',
+    date: 'Oct 17, 2025 10:27 PM',
+    currentPoints: 'My points:87999',
+  },
+  {
+    id: 4,
+    type: 'used',
+    title: 'Points Redeem Hair',
+    points: '-6000 Points',
+    date: 'Oct 17, 2025 10:25 PM',
+    currentPoints: 'My points:93999',
+  },
+  {
+    id: 5,
+    type: 'rewarded',
+    title: 'Updated by Admin',
+    points: '+99999 Points',
+    date: 'Oct 17, 2025 10:24 PM',
+    currentPoints: 'My points:99999',
+  },
+  {
+    id: 6,
+    type: 'used',
+    title: 'Points Redeem Giftcard',
+    points: '-300 Points',
+    date: 'Oct 13, 2025 8:41 PM',
+    currentPoints: 'My points:0',
+  },
+  {
+    id: 7,
+    type: 'rewarded',
+    title: 'Reward for Registering',
+    points: '+200 Points',
+    date: 'Jun 9, 2025 7:39 PM',
+    currentPoints: 'My points:300',
+  },
+  {
+    id: 8,
+    type: 'rewarded',
+    title: 'Reward for Signing up Newsletter',
+    points: '+100 Points',
+    date: 'Jun 9, 2025 7:39 PM',
+    currentPoints: 'My points:100',
+  },
+  // 补充测试数据(凑够分页演示)
+  { id: 9, type: 'rewarded', title: 'Check In', points: '+10 Points', date: 'May 16, 2026 10:00 PM', currentPoints: 'My points:100000' },
+  { id: 10, type: 'used', title: 'Points Redeem Accessory', points: '-500 Points', date: 'May 15, 2026 9:00 AM', currentPoints: 'My points:99990' },
+  { id: 11, type: 'rewarded', title: 'Review Product', points: '+50 Points', date: 'May 14, 2026 8:00 PM', currentPoints: 'My points:100490' },
+  { id: 12, type: 'rewarded', title: 'Refer a Friend', points: '+200 Points', date: 'May 13, 2026 7:00 PM', currentPoints: 'My points:100440' },
+  { id: 13, type: 'used', title: 'Points Redeem Cash', points: '-1000 Points', date: 'May 12, 2026 6:00 PM', currentPoints: 'My points:100240' },
+  { id: 14, type: 'rewarded', title: 'Check In', points: '+10 Points', date: 'May 11, 2026 5:00 PM', currentPoints: 'My points:101240' },
+];
+
+const PointsDetails = () => {
+  // 状态管理
+  const [activeTab, setActiveTab] = useState('all'); // 激活标签:all/rewarded/used
+  const [listData, setListData] = useState([]); // 展示的列表数据
+  const [page, setPage] = useState(1); // 当前页码
+  const [loading, setLoading] = useState(false); // 加载状态
+  const [hasMore, setHasMore] = useState(true); // 是否有更多数据
+  const listRef = useRef(null); // 列表容器ref,用于监听滚动
+
+  // 每页展示12条
+  const PAGE_SIZE = 12;
+
+  // 初始化/标签切换时重置数据
+  useEffect(() => {
+    setPage(1);
+    setHasMore(true);
+    // 筛选数据
+    let filteredData = mockPointsData;
+    if (activeTab === 'rewarded') {
+      filteredData = mockPointsData.filter(item => item.type === 'rewarded');
+    } else if (activeTab === 'used') {
+      filteredData = mockPointsData.filter(item => item.type === 'used');
+    }
+    // 第一页数据
+    const firstPageData = filteredData.slice(0, PAGE_SIZE);
+    setListData(firstPageData);
+    // 判断是否有更多数据
+    setHasMore(filteredData.length > PAGE_SIZE);
+  }, [activeTab]);
+
+  // 监听滚动:触底加载
+  useEffect(() => {
+    const handleScroll = () => {
+      if (loading || !hasMore) return;
+      const container = listRef.current;
+      // 滚动到底部的判断:滚动高度 + 可视高度 ≥ 总高度 - 20(阈值)
+      if (
+        container.scrollTop + container.clientHeight >=
+        container.scrollHeight - 20
+      ) {
+        loadMore();
+      }
+    };
+
+    const container = listRef.current;
+    if (container) {
+      container.addEventListener('scroll', handleScroll);
+      return () => container.removeEventListener('scroll', handleScroll);
+    }
+  }, [loading, hasMore, listData]);
+
+  // 加载更多数据
+  const loadMore = () => {
+    setLoading(true);
+    // 模拟接口请求延迟
+    setTimeout(() => {
+      let filteredData = mockPointsData;
+      if (activeTab === 'rewarded') {
+        filteredData = mockPointsData.filter(item => item.type === 'rewarded');
+      } else if (activeTab === 'used') {
+        filteredData = mockPointsData.filter(item => item.type === 'used');
+      }
+      // 计算下一页数据
+      const nextPage = page + 1;
+      const start = (nextPage - 1) * PAGE_SIZE;
+      const end = nextPage * PAGE_SIZE;
+      const newData = filteredData.slice(start, end);
+      
+      if (newData.length > 0) {
+        setListData(prev => [...prev, ...newData]);
+        setPage(nextPage);
+      } else {
+        setHasMore(false);
+      }
+      setLoading(false);
+    }, 800);
+  };
+
+  return (
+    <div className="min-h-screen bg-white">
+      {/* 顶部导航栏 */}
+      <div className="sticky top-0 z-10 bg-white border-b border-gray-100 py-4 px-4 flex items-center">
+        {/* 返回按钮 */}
+        <button className="mr-4">
+          <svg viewBox="0 0 24 24" width="20" height="20" fill="black">
+            <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
+          </svg>
+        </button>
+        {/* 标题 */}
+        <h1 className="text-xl font-bold text-black">Points Details</h1>
+      </div>
+
+      {/* 标签切换栏 */}
+      <div className="flex border-b border-gray-200">
+        <button
+          onClick={() => setActiveTab('all')}
+          className={`px-6 py-3 font-medium text-sm ${
+            activeTab === 'all'
+              ? 'bg-black text-white'
+              : 'bg-white text-gray-600 hover:bg-gray-50'
+          }`}
+        >
+          ALL
+        </button>
+        <button
+          onClick={() => setActiveTab('rewarded')}
+          className={`px-6 py-3 font-medium text-sm ${
+            activeTab === 'rewarded'
+              ? 'bg-black text-white'
+              : 'bg-white text-gray-600 hover:bg-gray-50'
+          }`}
+        >
+          REWARDED
+        </button>
+        <button
+          onClick={() => setActiveTab('used')}
+          className={`px-6 py-3 font-medium text-sm ${
+            activeTab === 'used'
+              ? 'bg-black text-white'
+              : 'bg-white text-gray-600 hover:bg-gray-50'
+          }`}
+        >
+          USED
+        </button>
+      </div>
+
+      {/* 积分明细列表(带滚动监听) */}
+      <div
+        ref={listRef}
+        className="h-[calc(100vh-120px)] overflow-y-auto"
+      >
+        {listData.length > 0 ? (
+          <div className="divide-y divide-gray-100">
+            {listData.map((item) => (
+              <div key={item.id} className="px-4 py-4">
+                {/* 左侧:标题+日期 */}
+                <div className="flex justify-between items-start">
+                  <div>
+                    <h3 className="text-base font-medium text-black">{item.title}</h3>
+                    <p className="text-xs text-gray-500 mt-1">{item.date}</p>
+                  </div>
+                  {/* 右侧:积分变动 */}
+                  <div className="text-right">
+                    <p 
+                      className={`text-base font-medium ${
+                        item.points.startsWith('+') ? 'text-black' : 'text-black'
+                      }`}
+                    >
+                      {item.points}
+                    </p>
+                    <p className="text-xs text-gray-500 mt-1">{item.currentPoints}</p>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        ) : (
+          // 空数据提示
+          <div className="flex items-center justify-center h-32">
+            <p className="text-gray-500 text-sm">NO DATA</p>
+          </div>
+        )}
+
+        {/* 加载中/无更多提示 */}
+        <div className="px-4 py-3 text-center text-sm">
+          {loading && <p className="text-gray-500">Loading...</p>}
+          {!loading && !hasMore && <p className="text-gray-500">NO MORE DATA</p>}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default PointsDetails;

+ 177 - 0
src/components/customer/PointsRule.tsx

@@ -0,0 +1,177 @@
+"use client";
+import Image from "next/image";
+export default function PointsRuleModal({
+  visible,
+  handleCloseModal,
+}: {
+  visible: boolean;
+  handleCloseModal: () => void;
+}) {
+  // 如果不显示,直接 return null
+  if (!visible) return null;
+
+  return (
+    <div
+      id="modal-overlay"
+      onClick={handleCloseModal}
+      className="fixed inset-0 bg-black/70 z-50 flex items-start justify-center overflow-y-auto"
+    >
+      {/* 弹窗内容区 */}
+      <div className="bg-white w-full max-w-2xl max-h-[100vh] overflow-y-auto  p-6 pt-16 ">
+        {/* 返回按钮 */}
+        <button
+          id="back-btn"
+          onClick={handleCloseModal}
+          className="fixed w-full bg-[#000] h-15.5 leading-15.5 justify-center text-xl  left-0 top-0  z-50 mb-4 flex items-center gap-1 text-white  transition-colors"
+        >
+            <Image src="/image/account/202605201502.png" width={22} style={{left:"8.2051vw",position:"absolute"}} height={22} alt="back" />
+         POINTS RULES
+        </button>
+
+        <div className="space-y-6">
+          {/* What Are Reward Points? */}
+          <section className="space-y-2">
+            <h3 className="text-lg font-bold text-black">
+              What Are Reward Points?
+            </h3>
+            <p className="text-sm text-gray-700 leading-relaxed">
+              We love to say thank you to our loyal customers for shopping with
+              us. Reward Points are an added bonus and just one of the ways we
+              like to say thanks. You can earn Points for every purchase you
+              make online, as well as bonus Points for other actions such as
+              referring a friend. Any Points you earn can be used towards your
+              future purchases online.
+            </p>
+          </section>
+
+          {/* How do I get Bonus Point? */}
+          <section className="space-y-3">
+            <h3 className="text-lg font-bold text-black">
+              How do I get Bonus Point?
+            </h3>
+            <p className="text-sm text-gray-700">
+              You can get bonus point from the following methods.
+            </p>
+            <ol className="list-decimal pl-5 space-y-2 text-sm text-gray-700">
+              <li>
+                <span className="font-bold">Buy and Save</span>
+                <p className="pl-2">
+                  You'll earn 1 point for every dollar spent on your purchase.
+                </p>
+              </li>
+              <li>
+                <span className="font-bold">Registration</span>
+                <p className="pl-2">
+                  You can get 200 bonus points for register to be Alipearl Hair
+                  Hair New Member.
+                </p>
+              </li>
+              <li>
+                <span className="font-bold">Sign In</span>
+                <p className="pl-2">
+                  You can get 10 bonus points when you sign in every time and
+                  the total amount you can get in one day is 10 bonus points
+                  only.
+                </p>
+              </li>
+              <li>
+                <span className="font-bold">Reviewing Our Products</span>
+                <p className="pl-2">
+                  You can earn points from writing reviews on our products.
+                  Submitting your review earn 50 points. Under this
+                  circumstance, all you can get in one day is 50 bonus points
+                  only.
+                </p>
+              </li>
+              <li>
+                <span className="font-bold">Referring A friend</span>
+                <p className="pl-2">
+                  Want to share the love with your friends and family? Invite
+                  them to shop with us and as a thank you we'll award you 200
+                  Reward Points when they place their first order. Head to the
+                  My Invitations section of your account.
+                </p>
+              </li>
+            </ol>
+          </section>
+
+          {/* Do I have to be registered to earn Points? */}
+          <section className="space-y-2">
+            <h3 className="text-lg font-bold text-black">
+              Do I have to be registered to earn Points?
+            </h3>
+            <p className="text-sm text-gray-700 leading-relaxed">
+              Yes you need to be registered to earn and spend Reward Points.
+              Registering an account is easy, and can be done prior to placing
+              your first order, or as part of the checkout process. Once you're
+              registered, simply sign in to your account before placing any
+              orders. Each time you make a purchase you'll then automatically
+              earn Points, which will be available for you to use at checkout on
+              future orders.
+            </p>
+          </section>
+
+          {/* Do my Reward Points expire? */}
+          <section className="space-y-2">
+            <h3 className="text-lg font-bold text-black">
+              Do my Reward Points expire?
+            </h3>
+            <p className="text-sm text-gray-700">
+              Yes, any unused Reward Points expire after 6 months (180 days).
+            </p>
+          </section>
+
+          {/* Is there a maximum amount of Reward Points I can redeem per order? */}
+          <section className="space-y-2">
+            <h3 className="text-lg font-bold text-black">
+              Is there a maximum amount of Reward Points I can redeem per order?
+            </h3>
+            <p className="text-sm text-gray-700">
+              Yes, the maximum reward points amount that you can redeem per
+              order is 20000 points.
+            </p>
+          </section>
+
+          {/* What are my Reward Points Worth? */}
+          <section className="space-y-2">
+            <h3 className="text-lg font-bold text-black">
+              What are my Reward Points Worth?
+            </h3>
+            <p className="text-sm text-gray-700">
+              100 Rewards Points = $1.25 which can be used towards any future
+              purchases.
+            </p>
+          </section>
+
+          {/* Terms of Use */}
+          <section className="space-y-2">
+            <h3 className="text-lg font-bold text-black">Terms of Use</h3>
+            <p className="text-sm text-gray-700 leading-relaxed">
+              Your participation in the Rewards Points programme and the
+              administration of your Rewards Points balance is entirely at our
+              discretion. We reserve the right to cancel your account or adjust
+              your Reward Points balance without notice or explanation.
+              <br />
+              If you earn or spend Points for an order which is subsequently
+              cancelled or amended for any reason, we reserve the right to
+              reverse some or all of the Points earned at our discretion.
+              <br />
+              If you earn Points for referring additional customers, we reserve
+              the right to review these new customer accounts, and reverse some
+              or all of the Points earned at our discretion.
+              <br />
+              Reward Points are not legal tender and are in no way redeemable
+              for cash or any other benefit.
+              <br />
+              Reward Points balances are not transferable under any
+              circumstances.
+              <br />
+              Your use of the Reward Points programme indicates your acceptance
+              of these terms of us.
+            </p>
+          </section>
+        </div>
+      </div>
+    </div>
+  );
+}