|
@@ -1,85 +1,127 @@
|
|
|
"use client";
|
|
"use client";
|
|
|
-import Image from "next/image";
|
|
|
|
|
-import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
|
|
|
+// import Image from "next/image";
|
|
|
|
|
+import React, { useState, useEffect, useRef } from "react";
|
|
|
|
|
|
|
|
// 模拟积分明细数据(可替换为接口请求)
|
|
// 模拟积分明细数据(可替换为接口请求)
|
|
|
const mockPointsData = [
|
|
const mockPointsData = [
|
|
|
{
|
|
{
|
|
|
id: 1,
|
|
id: 1,
|
|
|
- type: 'rewarded',
|
|
|
|
|
- title: 'Check In',
|
|
|
|
|
- points: '+10 Points',
|
|
|
|
|
- date: 'May 17, 2026 11:07 PM',
|
|
|
|
|
- currentPoints: 'My points:100009',
|
|
|
|
|
|
|
+ type: "rewarded",
|
|
|
|
|
+ title: "Check In",
|
|
|
|
|
+ points: "+10 Points",
|
|
|
|
|
+ date: "May 17, 2026 11:07 PM",
|
|
|
|
|
+ currentPoints: "My points:100009",
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 2,
|
|
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',
|
|
|
|
|
|
|
+ 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,
|
|
id: 3,
|
|
|
- type: 'used',
|
|
|
|
|
- title: 'Points Redeem Hair',
|
|
|
|
|
- points: '-6000 Points',
|
|
|
|
|
- date: 'Oct 17, 2025 10:27 PM',
|
|
|
|
|
- currentPoints: 'My points:87999',
|
|
|
|
|
|
|
+ type: "used",
|
|
|
|
|
+ title: "Points Redeem Hair",
|
|
|
|
|
+ points: "-6000 Points",
|
|
|
|
|
+ date: "Oct 17, 2025 10:27 PM",
|
|
|
|
|
+ currentPoints: "My points:87999",
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 4,
|
|
id: 4,
|
|
|
- type: 'used',
|
|
|
|
|
- title: 'Points Redeem Hair',
|
|
|
|
|
- points: '-6000 Points',
|
|
|
|
|
- date: 'Oct 17, 2025 10:25 PM',
|
|
|
|
|
- currentPoints: 'My points:93999',
|
|
|
|
|
|
|
+ type: "used",
|
|
|
|
|
+ title: "Points Redeem Hair",
|
|
|
|
|
+ points: "-6000 Points",
|
|
|
|
|
+ date: "Oct 17, 2025 10:25 PM",
|
|
|
|
|
+ currentPoints: "My points:93999",
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 5,
|
|
id: 5,
|
|
|
- type: 'rewarded',
|
|
|
|
|
- title: 'Updated by Admin',
|
|
|
|
|
- points: '+99999 Points',
|
|
|
|
|
- date: 'Oct 17, 2025 10:24 PM',
|
|
|
|
|
- currentPoints: 'My points:99999',
|
|
|
|
|
|
|
+ type: "rewarded",
|
|
|
|
|
+ title: "Updated by Admin",
|
|
|
|
|
+ points: "+99999 Points",
|
|
|
|
|
+ date: "Oct 17, 2025 10:24 PM",
|
|
|
|
|
+ currentPoints: "My points:99999",
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 6,
|
|
id: 6,
|
|
|
- type: 'used',
|
|
|
|
|
- title: 'Points Redeem Giftcard',
|
|
|
|
|
- points: '-300 Points',
|
|
|
|
|
- date: 'Oct 13, 2025 8:41 PM',
|
|
|
|
|
- currentPoints: 'My points:0',
|
|
|
|
|
|
|
+ type: "used",
|
|
|
|
|
+ title: "Points Redeem Giftcard",
|
|
|
|
|
+ points: "-300 Points",
|
|
|
|
|
+ date: "Oct 13, 2025 8:41 PM",
|
|
|
|
|
+ currentPoints: "My points:0",
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 7,
|
|
id: 7,
|
|
|
- type: 'rewarded',
|
|
|
|
|
- title: 'Reward for Registering',
|
|
|
|
|
- points: '+200 Points',
|
|
|
|
|
- date: 'Jun 9, 2025 7:39 PM',
|
|
|
|
|
- currentPoints: 'My points:300',
|
|
|
|
|
|
|
+ type: "rewarded",
|
|
|
|
|
+ title: "Reward for Registering",
|
|
|
|
|
+ points: "+200 Points",
|
|
|
|
|
+ date: "Jun 9, 2025 7:39 PM",
|
|
|
|
|
+ currentPoints: "My points:300",
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
id: 8,
|
|
id: 8,
|
|
|
- type: 'rewarded',
|
|
|
|
|
- title: 'Reward for Signing up Newsletter',
|
|
|
|
|
- points: '+100 Points',
|
|
|
|
|
- date: 'Jun 9, 2025 7:39 PM',
|
|
|
|
|
- currentPoints: 'My points:100',
|
|
|
|
|
|
|
+ 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' },
|
|
|
|
|
|
|
+ {
|
|
|
|
|
+ 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 PointsDetails = () => {
|
|
|
// 状态管理
|
|
// 状态管理
|
|
|
- const [activeTab, setActiveTab] = useState('all'); // 激活标签:all/rewarded/used
|
|
|
|
|
|
|
+ const [activeTab, setActiveTab] = useState("all"); // 激活标签:all/rewarded/used
|
|
|
const [listData, setListData] = useState([]); // 展示的列表数据
|
|
const [listData, setListData] = useState([]); // 展示的列表数据
|
|
|
const [page, setPage] = useState(1); // 当前页码
|
|
const [page, setPage] = useState(1); // 当前页码
|
|
|
const [loading, setLoading] = useState(false); // 加载状态
|
|
const [loading, setLoading] = useState(false); // 加载状态
|
|
@@ -95,10 +137,10 @@ const PointsDetails = () => {
|
|
|
setHasMore(true);
|
|
setHasMore(true);
|
|
|
// 筛选数据
|
|
// 筛选数据
|
|
|
let filteredData = mockPointsData;
|
|
let filteredData = mockPointsData;
|
|
|
- if (activeTab === 'rewarded') {
|
|
|
|
|
- filteredData = mockPointsData.filter(item => item.type === 'rewarded');
|
|
|
|
|
- } else if (activeTab === 'used') {
|
|
|
|
|
- filteredData = mockPointsData.filter(item => item.type === 'used');
|
|
|
|
|
|
|
+ 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);
|
|
const firstPageData = filteredData.slice(0, PAGE_SIZE);
|
|
@@ -123,35 +165,46 @@ const PointsDetails = () => {
|
|
|
|
|
|
|
|
const container = listRef.current;
|
|
const container = listRef.current;
|
|
|
if (container) {
|
|
if (container) {
|
|
|
- container.addEventListener('scroll', handleScroll);
|
|
|
|
|
- return () => container.removeEventListener('scroll', handleScroll);
|
|
|
|
|
|
|
+ container.addEventListener("scroll", handleScroll);
|
|
|
|
|
+ return () => container.removeEventListener("scroll", handleScroll);
|
|
|
}
|
|
}
|
|
|
- }, [loading, hasMore, listData]);
|
|
|
|
|
|
|
+ }, [loading, hasMore]);
|
|
|
|
|
|
|
|
// 加载更多数据
|
|
// 加载更多数据
|
|
|
const loadMore = () => {
|
|
const loadMore = () => {
|
|
|
|
|
+ if (loading) return;
|
|
|
|
|
+
|
|
|
setLoading(true);
|
|
setLoading(true);
|
|
|
- // 模拟接口请求延迟
|
|
|
|
|
|
|
+
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
let filteredData = mockPointsData;
|
|
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);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (activeTab === "rewarded") {
|
|
|
|
|
+ filteredData = mockPointsData.filter(
|
|
|
|
|
+ (item) => item.type === "rewarded",
|
|
|
|
|
+ );
|
|
|
|
|
+ } else if (activeTab === "used") {
|
|
|
|
|
+ filteredData = mockPointsData.filter((item) => item.type === "used");
|
|
|
}
|
|
}
|
|
|
- setLoading(false);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ setPage((prevPage) => {
|
|
|
|
|
+ const nextPage = prevPage + 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]);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setHasMore(false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setLoading(false);
|
|
|
|
|
+
|
|
|
|
|
+ return nextPage;
|
|
|
|
|
+ });
|
|
|
}, 800);
|
|
}, 800);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -172,31 +225,31 @@ const PointsDetails = () => {
|
|
|
{/* 标签切换栏 */}
|
|
{/* 标签切换栏 */}
|
|
|
<div className="flex border-b border-gray-200">
|
|
<div className="flex border-b border-gray-200">
|
|
|
<button
|
|
<button
|
|
|
- onClick={() => setActiveTab('all')}
|
|
|
|
|
|
|
+ onClick={() => setActiveTab("all")}
|
|
|
className={`px-6 py-3 font-medium text-sm ${
|
|
className={`px-6 py-3 font-medium text-sm ${
|
|
|
- activeTab === 'all'
|
|
|
|
|
- ? 'bg-black text-white'
|
|
|
|
|
- : 'bg-white text-gray-600 hover:bg-gray-50'
|
|
|
|
|
|
|
+ activeTab === "all"
|
|
|
|
|
+ ? "bg-black text-white"
|
|
|
|
|
+ : "bg-white text-gray-600 hover:bg-gray-50"
|
|
|
}`}
|
|
}`}
|
|
|
>
|
|
>
|
|
|
ALL
|
|
ALL
|
|
|
</button>
|
|
</button>
|
|
|
<button
|
|
<button
|
|
|
- onClick={() => setActiveTab('rewarded')}
|
|
|
|
|
|
|
+ onClick={() => setActiveTab("rewarded")}
|
|
|
className={`px-6 py-3 font-medium text-sm ${
|
|
className={`px-6 py-3 font-medium text-sm ${
|
|
|
- activeTab === 'rewarded'
|
|
|
|
|
- ? 'bg-black text-white'
|
|
|
|
|
- : 'bg-white text-gray-600 hover:bg-gray-50'
|
|
|
|
|
|
|
+ activeTab === "rewarded"
|
|
|
|
|
+ ? "bg-black text-white"
|
|
|
|
|
+ : "bg-white text-gray-600 hover:bg-gray-50"
|
|
|
}`}
|
|
}`}
|
|
|
>
|
|
>
|
|
|
REWARDED
|
|
REWARDED
|
|
|
</button>
|
|
</button>
|
|
|
<button
|
|
<button
|
|
|
- onClick={() => setActiveTab('used')}
|
|
|
|
|
|
|
+ onClick={() => setActiveTab("used")}
|
|
|
className={`px-6 py-3 font-medium text-sm ${
|
|
className={`px-6 py-3 font-medium text-sm ${
|
|
|
- activeTab === 'used'
|
|
|
|
|
- ? 'bg-black text-white'
|
|
|
|
|
- : 'bg-white text-gray-600 hover:bg-gray-50'
|
|
|
|
|
|
|
+ activeTab === "used"
|
|
|
|
|
+ ? "bg-black text-white"
|
|
|
|
|
+ : "bg-white text-gray-600 hover:bg-gray-50"
|
|
|
}`}
|
|
}`}
|
|
|
>
|
|
>
|
|
|
USED
|
|
USED
|
|
@@ -204,10 +257,7 @@ const PointsDetails = () => {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* 积分明细列表(带滚动监听) */}
|
|
{/* 积分明细列表(带滚动监听) */}
|
|
|
- <div
|
|
|
|
|
- ref={listRef}
|
|
|
|
|
- className="h-[calc(100vh-120px)] overflow-y-auto"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <div ref={listRef} className="h-[calc(100vh-120px)] overflow-y-auto">
|
|
|
{listData.length > 0 ? (
|
|
{listData.length > 0 ? (
|
|
|
<div className="divide-y divide-gray-100">
|
|
<div className="divide-y divide-gray-100">
|
|
|
{listData.map((item) => (
|
|
{listData.map((item) => (
|
|
@@ -215,19 +265,25 @@ const PointsDetails = () => {
|
|
|
{/* 左侧:标题+日期 */}
|
|
{/* 左侧:标题+日期 */}
|
|
|
<div className="flex justify-between items-start">
|
|
<div className="flex justify-between items-start">
|
|
|
<div>
|
|
<div>
|
|
|
- <h3 className="text-base font-medium text-black">{item.title}</h3>
|
|
|
|
|
|
|
+ <h3 className="text-base font-medium text-black">
|
|
|
|
|
+ {item.title}
|
|
|
|
|
+ </h3>
|
|
|
<p className="text-xs text-gray-500 mt-1">{item.date}</p>
|
|
<p className="text-xs text-gray-500 mt-1">{item.date}</p>
|
|
|
</div>
|
|
</div>
|
|
|
{/* 右侧:积分变动 */}
|
|
{/* 右侧:积分变动 */}
|
|
|
<div className="text-right">
|
|
<div className="text-right">
|
|
|
- <p
|
|
|
|
|
|
|
+ <p
|
|
|
className={`text-base font-medium ${
|
|
className={`text-base font-medium ${
|
|
|
- item.points.startsWith('+') ? 'text-black' : 'text-black'
|
|
|
|
|
|
|
+ item.points.startsWith("+")
|
|
|
|
|
+ ? "text-black"
|
|
|
|
|
+ : "text-black"
|
|
|
}`}
|
|
}`}
|
|
|
>
|
|
>
|
|
|
{item.points}
|
|
{item.points}
|
|
|
</p>
|
|
</p>
|
|
|
- <p className="text-xs text-gray-500 mt-1">{item.currentPoints}</p>
|
|
|
|
|
|
|
+ <p className="text-xs text-gray-500 mt-1">
|
|
|
|
|
+ {item.currentPoints}
|
|
|
|
|
+ </p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -243,11 +299,13 @@ const PointsDetails = () => {
|
|
|
{/* 加载中/无更多提示 */}
|
|
{/* 加载中/无更多提示 */}
|
|
|
<div className="px-4 py-3 text-center text-sm">
|
|
<div className="px-4 py-3 text-center text-sm">
|
|
|
{loading && <p className="text-gray-500">Loading...</p>}
|
|
{loading && <p className="text-gray-500">Loading...</p>}
|
|
|
- {!loading && !hasMore && <p className="text-gray-500">NO MORE DATA</p>}
|
|
|
|
|
|
|
+ {!loading && !hasMore && (
|
|
|
|
|
+ <p className="text-gray-500">NO MORE DATA</p>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export default PointsDetails;
|
|
|
|
|
|
|
+export default PointsDetails;
|