/**
|
* @author 韩天尊
|
* @time 2024-01-15
|
* @version 1.0.0
|
* @description 积分查询页面组件
|
*/
|
import React, { useState, useEffect } from 'react';
|
import { useNavigate } from 'react-router-dom';
|
import { useAppContext } from '../context/AppContext';
|
import { pointsAPI } from '../services/api';
|
import { PointsOverview, RedemptionRecord, CommunityPoints, CommunityPointsOverview } from '../types';
|
import PageHeader from '../components/PageHeader';
|
|
const PointsQueryPage: React.FC = () => {
|
const navigate = useNavigate();
|
const { state, loadDeclarationRecords } = useAppContext();
|
const [activeTab, setActiveTab] = useState<'declarations' | 'redemptions'>('declarations');
|
const [pointsData, setPointsData] = useState<PointsOverview>({
|
available: 0,
|
redeemed: 0,
|
total: 0
|
});
|
const [communityPointsData, setCommunityPointsData] = useState<CommunityPoints[]>([]);
|
const [selectedCommunityIndex, setSelectedCommunityIndex] = useState<number>(0);
|
const [redemptionRecords, setRedemptionRecords] = useState<RedemptionRecord[]>([]);
|
const [loading, setLoading] = useState(false);
|
|
// 加载积分数据
|
useEffect(() => {
|
const loadPointsData = async () => {
|
setLoading(true);
|
try {
|
const response = await pointsAPI.getOverview();
|
// 检查返回的数据结构
|
if ('communityList' in response.data && Array.isArray(response.data.communityList)) {
|
// 新的社区积分数据结构
|
const communityData = response.data as CommunityPointsOverview;
|
setCommunityPointsData(communityData.communityList);
|
// 设置默认选中第一个社区
|
if (communityData.communityList.length > 0) {
|
const firstCommunity = communityData.communityList[0];
|
setPointsData({
|
available: firstCommunity.points,
|
redeemed: firstCommunity.redeemedPoints,
|
total: firstCommunity.totalPoints
|
});
|
}
|
} else {
|
// 兼容旧的数据结构
|
const pointsData = response.data as PointsOverview;
|
setPointsData(pointsData);
|
}
|
await loadDeclarationRecords();
|
} catch (error) {
|
console.error('加载积分数据失败:', error);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
loadPointsData();
|
}, []); // 修改:改为空依赖数组,确保每次组件挂载都调用接口
|
|
// 加载核销记录
|
const loadRedemptionRecords = async () => {
|
try {
|
const response = await pointsAPI.getRedemptionRecords({
|
page: 1,
|
size: 50
|
});
|
setRedemptionRecords(response.data.list || []);
|
} catch (error) {
|
console.error('加载核销记录失败:', error);
|
}
|
};
|
|
// 处理Tab切换
|
const handleTabChange = (tab: 'declarations' | 'redemptions') => {
|
setActiveTab(tab);
|
// 当切换到核销历史时,加载核销记录
|
if (tab === 'redemptions' && redemptionRecords.length === 0) {
|
loadRedemptionRecords();
|
}
|
};
|
|
// 处理社区切换
|
const handleCommunityChange = (index: number) => {
|
setSelectedCommunityIndex(index);
|
if (communityPointsData[index]) {
|
const community = communityPointsData[index];
|
setPointsData({
|
available: community.points,
|
redeemed: community.redeemedPoints,
|
total: community.totalPoints
|
});
|
}
|
};
|
|
// 获取状态文本
|
const getStatusText = (status: string | number) => {
|
switch (String(status)) {
|
case '1':
|
case 'approved': return '审核通过';
|
case '0':
|
case 'pending': return '待审核';
|
case '2':
|
case 'rejected': return '审核拒绝';
|
default: return '未知';
|
}
|
};
|
|
// 格式化时间显示
|
const formatTimeDisplay = (time?: string, startTime?: string, endTime?: string): string => {
|
// 如果有单独的时间字段,直接格式化
|
if (time) {
|
return formatSingleTime(time);
|
}
|
|
// 如果有开始时间和结束时间
|
if (startTime && endTime) {
|
try {
|
const startDate = new Date(startTime);
|
const endDate = new Date(endTime);
|
|
// 检查日期是否有效
|
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
return `${startTime} - ${endTime}`;
|
}
|
|
// 检查是否在同一天
|
const isSameDay =
|
startDate.getFullYear() === endDate.getFullYear() &&
|
startDate.getMonth() === endDate.getMonth() &&
|
startDate.getDate() === endDate.getDate();
|
|
if (isSameDay) {
|
// 同一天:yyyy-MM-dd HH:mm - HH:mm
|
const dateStr = formatSingleTime(startTime).split(' ')[0];
|
const startTimeStr = formatTimeOnly(startTime);
|
const endTimeStr = formatTimeOnly(endTime);
|
return `${dateStr} ${startTimeStr} - ${endTimeStr}`;
|
} else {
|
// 不同天:yyyy-MM-dd HH:mm - yyyy-MM-dd HH:mm
|
return `${formatSingleTime(startTime)} - ${formatSingleTime(endTime)}`;
|
}
|
} catch (error) {
|
return `${startTime} - ${endTime}`;
|
}
|
}
|
|
// 如果只有开始时间
|
if (startTime) {
|
return formatSingleTime(startTime);
|
}
|
|
// 如果只有结束时间
|
if (endTime) {
|
return formatSingleTime(endTime);
|
}
|
|
return '';
|
};
|
|
// 格式化单个时间为 yyyy-MM-dd HH:mm
|
const formatSingleTime = (timeString: string): string => {
|
try {
|
const date = new Date(timeString);
|
if (isNaN(date.getTime())) {
|
return timeString;
|
}
|
|
const year = date.getFullYear();
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
const hours = String(date.getHours()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
} catch (error) {
|
return timeString;
|
}
|
};
|
|
// 格式化时间为 HH:mm
|
const formatTimeOnly = (timeString: string): string => {
|
try {
|
const date = new Date(timeString);
|
if (isNaN(date.getTime())) {
|
return timeString;
|
}
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
return `${hours}:${minutes}`;
|
} catch (error) {
|
return timeString;
|
}
|
};
|
|
|
// 点击积分申报卡片,跳转到详情页面
|
const handleDeclarationClick = (recordId: number) => {
|
navigate(`/declaration-detail/${recordId}`);
|
};
|
|
// 点击核销卡片,跳转到详情页面
|
const handleRedemptionClick = (recordId: number) => {
|
navigate(`/redemption-detail/${recordId}`);
|
};
|
|
return (
|
<div className="page">
|
<PageHeader title="我的积分" />
|
|
{/* 积分总览卡片 */}
|
<div className="points-overview">
|
<div className="overview-card">
|
<div className="overview-header">
|
<i className="fas fa-coins"></i>
|
<span>积分总览</span>
|
{communityPointsData.length > 1 && (
|
<div className="community-selector">
|
<select
|
value={selectedCommunityIndex}
|
onChange={(e) => handleCommunityChange(Number(e.target.value))}
|
className="community-select"
|
>
|
{communityPointsData.map((community, index) => (
|
<option key={index} value={index}>
|
{community.communityName}
|
</option>
|
))}
|
</select>
|
</div>
|
)}
|
</div>
|
<div className="overview-content">
|
<div className="overview-item">
|
<div className="overview-label">可用积分</div>
|
<div className="overview-value" id="available-points">{pointsData.available}</div>
|
<div className="overview-icon">
|
<i className="fas fa-wallet"></i>
|
</div>
|
</div>
|
<div className="overview-item">
|
<div className="overview-label">已核销积分</div>
|
<div className="overview-value" id="redeemed-points">{pointsData.redeemed}</div>
|
<div className="overview-icon">
|
<i className="fas fa-exchange-alt"></i>
|
</div>
|
</div>
|
<div className="overview-item">
|
<div className="overview-label">累计积分</div>
|
<div className="overview-value" id="total-points">{pointsData.total}</div>
|
<div className="overview-icon">
|
<i className="fas fa-chart-line"></i>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
|
{/* 积分记录 */}
|
<div className="points-records">
|
<div className="records-header">
|
<h3>积分记录</h3>
|
</div>
|
|
{/* Tab切换 */}
|
<div className="tab-section">
|
<button
|
className={`tab-btn ${activeTab === 'declarations' ? 'active' : ''}`}
|
onClick={() => handleTabChange('declarations')}
|
>
|
积分申报
|
</button>
|
<button
|
className={`tab-btn ${activeTab === 'redemptions' ? 'active' : ''}`}
|
onClick={() => handleTabChange('redemptions')}
|
>
|
核销历史
|
</button>
|
</div>
|
|
{/* 列表内容 */}
|
<div className="list-content">
|
<div className={`list-tab ${activeTab === 'declarations' ? 'active' : ''}`}>
|
<div className="declarations-list">
|
{/* 积分申报记录 */}
|
{state.declarationRecords.map((record) => (
|
<div
|
key={record.id}
|
className="declaration-card"
|
onClick={() => handleDeclarationClick(record.id)}
|
>
|
<div className="declaration-header">
|
<div className="declaration-title">{record.title || record.activityName}</div>
|
<div className={`declaration-status ${String(record.status)}`}>
|
{getStatusText(record.status)}
|
</div>
|
</div>
|
<div className="declaration-info">
|
<div className="declaration-info-row">
|
<i className="fas fa-tag"></i>
|
<span>申报类型:{record.categoryDesc || record.category || '未知'}</span>
|
</div>
|
<div className="declaration-info-row">
|
<i className="fas fa-clock"></i>
|
<span>时间:{formatTimeDisplay(record.time, record.startTime, record.endTime)}</span>
|
</div>
|
</div>
|
<div className="declaration-content">{record.content}</div>
|
<div className="declaration-footer">
|
<div className="points-info">
|
<i className="fas fa-coins"></i>
|
<span>+{record.points || 0} 积分</span>
|
</div>
|
<div className="declaration-arrow">
|
<i className="fas fa-chevron-right"></i>
|
</div>
|
</div>
|
</div>
|
))}
|
</div>
|
</div>
|
<div className={`list-tab ${activeTab === 'redemptions' ? 'active' : ''}`}>
|
<div className="redemptions-list">
|
{/* 核销历史记录 */}
|
{redemptionRecords.length > 0 ? (
|
redemptionRecords.map((record) => (
|
<div
|
key={record.id}
|
className="redemption-card"
|
onClick={() => handleRedemptionClick(record.id)}
|
>
|
<div className="redemption-header">
|
<div className="redemption-title">{record.productName || '积分核销'}</div>
|
<div className={`redemption-status ${record.status}`}>
|
{record.status === 'completed' ? '已完成' : '已核销'}
|
</div>
|
</div>
|
<div className="redemption-info">
|
<div className="redemption-info-row">
|
<i className="fas fa-clock"></i>
|
<span>核销时间:{formatSingleTime(record.createTime || record.redemptionTime)}</span>
|
</div>
|
<div className="redemption-info-row">
|
<i className="fas fa-store"></i>
|
<span>核销地点:社区服务中心</span>
|
</div>
|
</div>
|
<div className="redemption-content">{record.description}</div>
|
<div className="redemption-footer">
|
<div className="points-info">
|
<i className="fas fa-coins"></i>
|
<span>-{record.points} 积分</span>
|
</div>
|
<div className="redemption-arrow">
|
<i className="fas fa-chevron-right"></i>
|
</div>
|
</div>
|
</div>
|
))
|
) : (
|
<div className="empty-state">
|
<p>暂无核销记录</p>
|
</div>
|
)}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
);
|
};
|
|
export default PointsQueryPage;
|