/**
|
* @author 韩天尊
|
* @time 2024-01-15
|
* @version 1.0.0
|
* @description 活动详情页面组件
|
*/
|
import React, { useState, useEffect } from 'react';
|
import { useParams, useNavigate } from 'react-router-dom';
|
import { useAppContext } from '../context/AppContext';
|
import { activityAPI } from '../services/api';
|
import { Activity } from '../types';
|
import PageHeader from '../components/PageHeader';
|
import { formatActivityTime } from '../utils/timeFormatter';
|
import { APP_CONFIG } from '../config/appConfig';
|
|
const ActivityDetailPage: React.FC = () => {
|
const { id } = useParams<{ id: string }>();
|
const navigate = useNavigate();
|
const { state } = useAppContext();
|
const [activity, setActivity] = useState<Activity | null>(null);
|
const [loading, setLoading] = useState(true);
|
const [error, setError] = useState('');
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
const [successMessage, setSuccessMessage] = useState('');
|
const [signupLoading, setSignupLoading] = useState(false);
|
|
useEffect(() => {
|
const loadActivityDetail = async () => {
|
try {
|
setLoading(true);
|
const response = await activityAPI.getDetail(Number(id));
|
if (response.code === 0) {
|
setActivity(response.data);
|
} else {
|
setError('活动不存在');
|
}
|
} catch (err: any) {
|
setError(err.message || '获取活动详情失败');
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
if (id) {
|
loadActivityDetail();
|
}
|
}, [id]);
|
|
// 构建完整的图片URL
|
const buildImageUrl = (filePath: string) => {
|
// 如果已经是完整URL,直接返回
|
if (filePath.startsWith('http')) {
|
return filePath;
|
}
|
// 否则拼接基础路径
|
return `${APP_CONFIG.API.SYS_BASE_URL}${filePath}`;
|
};
|
|
// 获取报名状态文本
|
const getUserStatusText = (userStatus?: string) => {
|
switch (userStatus) {
|
case '0':
|
return '未报名';
|
case '1':
|
return '已报名';
|
case '2':
|
return '已签到';
|
case '3':
|
return '已签退';
|
default:
|
return '未报名';
|
}
|
};
|
|
// 获取报名状态样式类
|
const getUserStatusClass = (userStatus?: string) => {
|
switch (userStatus) {
|
case '0':
|
return 'status-not-registered';
|
case '1':
|
return 'status-registered';
|
case '2':
|
return 'status-checked-in';
|
case '3':
|
return 'status-checked-out';
|
default:
|
return 'status-not-registered';
|
}
|
};
|
|
// 处理报名按钮点击
|
const handleSignupClick = async () => {
|
if (!activity) return;
|
|
setSignupLoading(true);
|
try {
|
const response = await activityAPI.join(activity.id);
|
if (response.code === 0) {
|
setSuccessMessage('报名成功!');
|
setShowSuccessModal(true);
|
// 重新加载活动详情以更新状态
|
const detailResponse = await activityAPI.getDetail(Number(id));
|
if (detailResponse.code === 0) {
|
setActivity(detailResponse.data);
|
}
|
// 延迟关闭弹窗
|
setTimeout(() => {
|
setShowSuccessModal(false);
|
}, 2000);
|
} else {
|
alert(response.msg || '报名失败');
|
}
|
} catch (err: any) {
|
alert(err.message || '报名失败');
|
} finally {
|
setSignupLoading(false);
|
}
|
};
|
|
// 处理签到按钮点击
|
const handleCheckinClick = () => {
|
if (!activity) return;
|
navigate(`/activity-checkin/${activity.id}`);
|
};
|
|
if (loading) {
|
return (
|
<div className="page">
|
<PageHeader title="活动详情" showBack={true} />
|
<div className="loading-container">
|
<div className="loading-spinner"></div>
|
<p>加载中...</p>
|
</div>
|
</div>
|
);
|
}
|
|
if (error || !activity) {
|
return (
|
<div className="page">
|
<PageHeader title="活动详情" showBack={true} />
|
<div className="error-container">
|
<div className="error-icon">
|
<i className="fas fa-exclamation-triangle"></i>
|
</div>
|
<p>{error || '活动不存在'}</p>
|
<button
|
className="retry-btn"
|
onClick={() => navigate(-1)}
|
>
|
返回
|
</button>
|
</div>
|
</div>
|
);
|
}
|
|
return (
|
<div className="page">
|
<PageHeader title="活动详情" showBack={true} />
|
|
<div className="activity-detail-content">
|
{/* 活动封面 */}
|
<div className="activity-cover">
|
<img
|
src={
|
// 优先显示fileList中的第一张图片
|
activity.fileList && activity.fileList.length > 0
|
? buildImageUrl(activity.fileList[0])
|
: activity.img || require('../image/image1.jpg')
|
}
|
alt={activity.title}
|
/>
|
</div>
|
|
{/* 基础信息 */}
|
<div className="detail-section">
|
<h3 className="section-title">基础信息</h3>
|
<div className="detail-info">
|
<div className="detail-item">
|
<label>活动名称</label>
|
<span>{activity.title}</span>
|
</div>
|
<div className="detail-item">
|
<label>报名状态</label>
|
<span className={`user-status ${getUserStatusClass(activity.userStatus)}`}>
|
{getUserStatusText(activity.userStatus)}
|
</span>
|
</div>
|
<div className="detail-item">
|
<label>活动时间</label>
|
<span>{formatActivityTime(activity.startTime, activity.endTime, activity.time)}</span>
|
</div>
|
<div className="detail-item">
|
<label>活动地点</label>
|
<span>{activity.location}</span>
|
</div>
|
<div className="detail-item">
|
<label>活动积分</label>
|
<span className="activity-points">{activity.points || 0} 分</span>
|
</div>
|
<div className="detail-item">
|
<label>活动内容</label>
|
<p>{activity.content || '暂无详细描述'}</p>
|
</div>
|
</div>
|
</div>
|
|
{/* 参与流程 */}
|
<div className="detail-section">
|
<h3 className="section-title">参与流程</h3>
|
<div className="detail-info">
|
<div className="detail-item">
|
<label>报名截止日</label>
|
<span>{activity.deadline ? new Date(activity.deadline).toLocaleDateString() : '无限制'}</span>
|
</div>
|
<div className="detail-item">
|
<label>活动人数</label>
|
<span>{activity.maxParticipants} 人</span>
|
</div>
|
<div className="detail-item">
|
<label>当前报名人数</label>
|
<span>{activity.currentParticipants} 人</span>
|
</div>
|
</div>
|
</div>
|
|
{/* 签到签退信息 */}
|
<div className="detail-section">
|
<h3 className="section-title">签到签退信息</h3>
|
<div className="detail-info">
|
<div className="detail-item">
|
<label>签到信息</label>
|
<div className={`checkin-info-card ${activity.volActivityUser?.checkinTime ? 'has-checkin' : 'no-checkin'}`}>
|
<div className="checkin-icon">
|
<i className="fas fa-sign-in-alt"></i>
|
</div>
|
<div className="checkin-content">
|
<div className="checkin-time">
|
{activity.volActivityUser?.checkinTime
|
? new Date(activity.volActivityUser.checkinTime).toLocaleString('zh-CN')
|
: '未签到'
|
}
|
</div>
|
<div className="checkin-location">
|
{activity.volActivityUser?.checkinLocation || '暂无地点'}
|
</div>
|
</div>
|
</div>
|
</div>
|
<div className="detail-item">
|
<label>签退信息</label>
|
<div className={`checkout-info-card ${activity.volActivityUser?.checkoutTime ? 'has-checkout' : 'no-checkout'}`}>
|
<div className="checkout-icon">
|
<i className="fas fa-sign-out-alt"></i>
|
</div>
|
<div className="checkout-content">
|
<div className="checkout-time">
|
{activity.volActivityUser?.checkoutTime
|
? new Date(activity.volActivityUser.checkoutTime).toLocaleString('zh-CN')
|
: '未签退'
|
}
|
</div>
|
<div className="checkout-location">
|
{activity.volActivityUser?.checkoutLocation || '暂无地点'}
|
</div>
|
</div>
|
</div>
|
</div>
|
<div className="detail-item">
|
<label>服务时长</label>
|
<span className={`service-hours ${activity.volActivityUser?.checkinTime && activity.volActivityUser?.checkoutTime ? 'has-service' : ''}`}>
|
{activity.volActivityUser?.checkinTime && activity.volActivityUser?.checkoutTime
|
? (() => {
|
const checkinTime = new Date(activity.volActivityUser.checkinTime);
|
const checkoutTime = new Date(activity.volActivityUser.checkoutTime);
|
const diffMs = checkoutTime.getTime() - checkinTime.getTime();
|
const diffHours = Math.round(diffMs / (1000 * 60 * 60) * 10) / 10;
|
return `${diffHours}小时`;
|
})()
|
: '0小时'
|
}
|
</span>
|
</div>
|
<div className="detail-item">
|
<label>获得积分</label>
|
<span className={`earned-points ${activity.volActivityUser?.earnedPoints ? 'has-points' : ''}`}>
|
{activity.volActivityUser?.earnedPoints || 0} 分
|
</span>
|
</div>
|
</div>
|
</div>
|
|
{/* 报名按钮 */}
|
<div className="detail-actions">
|
{/* 根据用户报名状态和活动状态显示不同按钮 */}
|
{/* 当活动状态为"1"且用户状态为"0"时显示报名按钮 */}
|
{activity.status === '1' && activity.userStatus === '0' && (
|
<button
|
className="signup-btn"
|
onClick={handleSignupClick}
|
disabled={signupLoading}
|
>
|
{signupLoading ? (
|
<>
|
<i className="fas fa-spinner fa-spin"></i>
|
报名中...
|
</>
|
) : (
|
<>
|
<i className="fas fa-user-plus"></i>
|
立即报名
|
</>
|
)}
|
</button>
|
)}
|
|
{/* 原有的其他按钮逻辑保持不变 */}
|
{activity.userStatus === '1' && activity.status === 'ongoing' && (
|
<button
|
className="signup-btn"
|
onClick={handleCheckinClick}
|
>
|
<i className="fas fa-check-circle"></i>
|
签到
|
</button>
|
)}
|
|
{activity.userStatus === '2' && activity.status === 'ongoing' && (
|
<button
|
className="signup-btn"
|
onClick={handleCheckinClick}
|
>
|
<i className="fas fa-sign-out-alt"></i>
|
签退
|
</button>
|
)}
|
|
{(activity.userStatus === '3' || activity.userStatus === '2') && activity.status === 'completed' && (
|
<div className="completed-status">
|
<i className="fas fa-check-circle"></i>
|
<span>活动已完成</span>
|
</div>
|
)}
|
</div>
|
</div>
|
|
{/* 成功弹窗 */}
|
{showSuccessModal && (
|
<div className="success-modal-overlay">
|
<div className="success-modal">
|
<div className="success-icon">
|
<i className="fas fa-check-circle"></i>
|
</div>
|
<div className="success-content">
|
<h3 className="success-title">{successMessage}</h3>
|
<p className="success-subtitle">恭喜您成功报名本次活动!</p>
|
</div>
|
<div className="success-animation">
|
<div className="loading-dots">
|
<span></span>
|
<span></span>
|
<span></span>
|
</div>
|
</div>
|
</div>
|
</div>
|
)}
|
</div>
|
);
|
};
|
|
export default ActivityDetailPage;
|