/**
|
* @author 韩天尊
|
* @time 2024-01-15
|
* @version 1.0.0
|
* @description 签到页面组件
|
*/
|
import React, { useState, useEffect } from 'react';
|
import { useLocation } from 'react-router-dom';
|
import { useAppContext } from '../context/AppContext';
|
import PageHeader from '../components/PageHeader';
|
import { activityAPI, checkinAPI } from '../services/api';
|
import { Activity } from '../types';
|
|
const ScanCheckinPage: React.FC = () => {
|
const location = useLocation();
|
const { state, dispatch } = useAppContext();
|
const [currentLocation, setCurrentLocation] = useState('正在获取位置...');
|
const [locationCoordinates, setLocationCoordinates] = useState('');
|
const [hasActivityId, setHasActivityId] = useState(false);
|
const [activityDetail, setActivityDetail] = useState<Activity | null>(null);
|
const [loading, setLoading] = useState(false);
|
const [buttonLoading, setButtonLoading] = useState(false);
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
const [successMessage, setSuccessMessage] = useState('');
|
|
// 检查URL参数并获取活动详情
|
useEffect(() => {
|
const urlParams = new URLSearchParams(location.search);
|
const activityId = urlParams.get('activityId');
|
setHasActivityId(!!activityId);
|
|
if (activityId) {
|
fetchActivityDetail(parseInt(activityId));
|
}
|
}, [location.search]);
|
|
// 获取活动详情
|
const fetchActivityDetail = async (activityId: number) => {
|
setLoading(true);
|
try {
|
const response = await activityAPI.getDetail(activityId);
|
if (response.code === 0) {
|
setActivityDetail(response.data);
|
} else {
|
console.error('获取活动详情失败:', response.msg);
|
}
|
} catch (error) {
|
console.error('获取活动详情出错:', error);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 获取按钮状态
|
const getButtonState = () => {
|
if (!activityDetail?.volActivityUser) {
|
return { type: 'checkin', disabled: false, text: '签到' };
|
}
|
|
const { checkinTime, checkoutTime } = activityDetail.volActivityUser;
|
|
if (!checkinTime) {
|
return { type: 'checkin', disabled: false, text: '签到' };
|
} else if (checkinTime && !checkoutTime) {
|
return { type: 'checkout', disabled: false, text: '签退' };
|
} else {
|
return { type: 'completed', disabled: true, text: '已完成' };
|
}
|
};
|
|
// 处理签到
|
const handleCheckin = async () => {
|
if (!activityDetail) return;
|
|
setButtonLoading(true);
|
try {
|
let latitude: number | undefined;
|
let longitude: number | undefined;
|
let location: string | undefined;
|
|
// 如果有位置信息则使用,没有则传空
|
if (locationCoordinates) {
|
[latitude, longitude] = locationCoordinates.split(', ').map(Number);
|
location = currentLocation;
|
}
|
// 如果没有定位地址,location保持undefined(传空)
|
|
const response = await checkinAPI.scanCheck(
|
activityDetail.id,
|
latitude,
|
longitude,
|
location
|
);
|
|
if (response.code === 0) {
|
setSuccessMessage('签到成功!');
|
setShowSuccessModal(true);
|
// 延迟刷新页面,让用户看到成功提示
|
setTimeout(() => {
|
window.location.reload();
|
}, 2000);
|
} else {
|
alert('签到失败:' + response.msg);
|
}
|
} catch (error) {
|
console.error('签到出错:', error);
|
alert('签到失败,请重试');
|
} finally {
|
setButtonLoading(false);
|
}
|
};
|
|
// 处理签退
|
const handleCheckout = async () => {
|
if (!activityDetail) return;
|
|
setButtonLoading(true);
|
try {
|
let latitude: number | undefined;
|
let longitude: number | undefined;
|
let location: string | undefined;
|
|
// 如果有位置信息则使用,没有则传空
|
if (locationCoordinates) {
|
[latitude, longitude] = locationCoordinates.split(', ').map(Number);
|
location = currentLocation;
|
}
|
// 如果没有定位地址,location保持undefined(传空)
|
|
const response = await checkinAPI.scanCheck(
|
activityDetail.id,
|
latitude,
|
longitude,
|
location
|
);
|
|
if (response.code === 0) {
|
setSuccessMessage('签退成功!');
|
setShowSuccessModal(true);
|
// 延迟刷新页面,让用户看到成功提示
|
setTimeout(() => {
|
window.location.reload();
|
}, 2000);
|
} else {
|
alert('签退失败:' + response.msg);
|
}
|
} catch (error) {
|
console.error('签退出错:', error);
|
alert('签退失败,请重试');
|
} finally {
|
setButtonLoading(false);
|
}
|
};
|
|
// 处理按钮点击
|
const handleButtonClick = () => {
|
const buttonState = getButtonState();
|
|
if (buttonState.disabled) return;
|
|
if (buttonState.type === 'checkin') {
|
handleCheckin();
|
} else if (buttonState.type === 'checkout') {
|
handleCheckout();
|
}
|
};
|
|
// 获取当前位置
|
useEffect(() => {
|
if (navigator.geolocation) {
|
navigator.geolocation.getCurrentPosition(
|
(position) => {
|
const { latitude, longitude } = position.coords;
|
setLocationCoordinates(`${latitude.toFixed(6)}, ${longitude.toFixed(6)}`);
|
|
// 这里可以调用地理编码API获取具体地址
|
// 暂时使用坐标作为地址
|
setCurrentLocation(`纬度: ${latitude.toFixed(6)}, 经度: ${longitude.toFixed(6)}`);
|
},
|
(error) => {
|
console.error('获取位置失败:', error);
|
setCurrentLocation('无法获取位置信息');
|
setLocationCoordinates('位置获取失败');
|
}
|
);
|
} else {
|
setCurrentLocation('浏览器不支持地理位置');
|
setLocationCoordinates('不支持地理位置');
|
}
|
}, []);
|
|
// 刷新位置
|
const refreshLocation = () => {
|
setCurrentLocation('正在获取位置...');
|
setLocationCoordinates('');
|
|
if (navigator.geolocation) {
|
navigator.geolocation.getCurrentPosition(
|
(position) => {
|
const { latitude, longitude } = position.coords;
|
setLocationCoordinates(`${latitude.toFixed(6)}, ${longitude.toFixed(6)}`);
|
setCurrentLocation(`纬度: ${latitude.toFixed(6)}, 经度: ${longitude.toFixed(6)}`);
|
},
|
(error) => {
|
console.error('获取位置失败:', error);
|
setCurrentLocation('无法获取位置信息');
|
setLocationCoordinates('位置获取失败');
|
}
|
);
|
}
|
};
|
|
|
return (
|
<div className="page">
|
<PageHeader title="扫码签到" />
|
|
<div className="scan-container">
|
{/* 定位信息 - 暂时隐藏 */}
|
<div className="location-info" style={{ display: 'none' }}>
|
<div className="location-header">
|
<i className="fas fa-map-marker-alt"></i>
|
<span>当前位置</span>
|
</div>
|
<div className="location-content">
|
<div className="location-address">{currentLocation}</div>
|
<div className="location-coordinates">{locationCoordinates}</div>
|
</div>
|
<button className="refresh-location-btn" onClick={refreshLocation}>
|
<i className="fas fa-sync-alt"></i>
|
<span>刷新位置</span>
|
</button>
|
</div>
|
|
{/* 活动信息展示 */}
|
{hasActivityId && (
|
<div className="activity-info-container">
|
{loading ? (
|
<div className="loading-container">
|
<i className="fas fa-spinner fa-spin"></i>
|
<span>正在加载活动信息...</span>
|
</div>
|
) : activityDetail ? (
|
<div className="activity-info">
|
<div className="activity-header">
|
<i className="fas fa-calendar-alt"></i>
|
<span>活动信息</span>
|
</div>
|
<div className="activity-content">
|
<div className="activity-title">{activityDetail.title}</div>
|
<div className="activity-details">
|
<div className="detail-item">
|
<i className="fas fa-clock"></i>
|
<span>开始时间:{activityDetail.startTime ? new Date(activityDetail.startTime).toLocaleString() : '未设置'}</span>
|
</div>
|
<div className="detail-item">
|
<i className="fas fa-clock"></i>
|
<span>结束时间:{activityDetail.endTime ? new Date(activityDetail.endTime).toLocaleString() : '未设置'}</span>
|
</div>
|
<div className="detail-item">
|
<i className="fas fa-map-marker-alt"></i>
|
<span>活动地点:{activityDetail.location}</span>
|
</div>
|
<div className="activity-content-text">
|
<span>活动内容:</span>
|
<p>{activityDetail.content}</p>
|
</div>
|
</div>
|
</div>
|
|
{/* 个人签到信息 */}
|
{activityDetail.volActivityUser && (
|
<div className="checkin-info">
|
<div className="checkin-header">
|
<div className="header-icon">
|
<i className="fas fa-user-check"></i>
|
</div>
|
<div className="header-content">
|
<span className="header-title">个人签到信息</span>
|
<span className="header-subtitle">您的活动参与记录</span>
|
</div>
|
</div>
|
<div className="checkin-content">
|
{/* 签到状态卡片 */}
|
<div className="checkin-status-card">
|
<div className="status-indicator">
|
{activityDetail.volActivityUser.checkinTime ? (
|
<div className="status-success">
|
<i className="fas fa-check-circle"></i>
|
<span>已签到</span>
|
</div>
|
) : (
|
<div className="status-pending">
|
<i className="fas fa-clock"></i>
|
<span>未签到</span>
|
</div>
|
)}
|
</div>
|
{activityDetail.volActivityUser.checkoutTime && (
|
<div className="status-indicator">
|
<div className="status-completed">
|
<i className="fas fa-flag-checkered"></i>
|
<span>已签退</span>
|
</div>
|
</div>
|
)}
|
</div>
|
|
{/* 签到详情 */}
|
{activityDetail.volActivityUser.checkinTime && (
|
<div className="checkin-details">
|
<div className="details-header">
|
<i className="fas fa-sign-in-alt"></i>
|
<span>签到详情</span>
|
</div>
|
<div className="details-grid">
|
<div className="detail-card">
|
<div className="detail-icon">
|
<i className="fas fa-clock"></i>
|
</div>
|
<div className="detail-content">
|
<span className="detail-label">签到时间</span>
|
<span className="detail-value">{new Date(activityDetail.volActivityUser.checkinTime).toLocaleString()}</span>
|
</div>
|
</div>
|
{activityDetail.volActivityUser.checkinLocation && (
|
<div className="detail-card">
|
<div className="detail-icon">
|
<i className="fas fa-map-marker-alt"></i>
|
</div>
|
<div className="detail-content">
|
<span className="detail-label">签到地点</span>
|
<span className="detail-value">{activityDetail.volActivityUser.checkinLocation}</span>
|
</div>
|
</div>
|
)}
|
</div>
|
</div>
|
)}
|
|
{/* 签退详情 */}
|
{activityDetail.volActivityUser.checkoutTime && (
|
<div className="checkin-details">
|
<div className="details-header">
|
<i className="fas fa-sign-out-alt"></i>
|
<span>签退详情</span>
|
</div>
|
<div className="details-grid">
|
<div className="detail-card">
|
<div className="detail-icon">
|
<i className="fas fa-clock"></i>
|
</div>
|
<div className="detail-content">
|
<span className="detail-label">签退时间</span>
|
<span className="detail-value">{new Date(activityDetail.volActivityUser.checkoutTime).toLocaleString()}</span>
|
</div>
|
</div>
|
{activityDetail.volActivityUser.checkoutLocation && (
|
<div className="detail-card">
|
<div className="detail-icon">
|
<i className="fas fa-map-marker-alt"></i>
|
</div>
|
<div className="detail-content">
|
<span className="detail-label">签退地点</span>
|
<span className="detail-value">{activityDetail.volActivityUser.checkoutLocation}</span>
|
</div>
|
</div>
|
)}
|
</div>
|
</div>
|
)}
|
|
{/* 无记录提示 */}
|
{!activityDetail.volActivityUser.checkinTime && !activityDetail.volActivityUser.checkoutTime && (
|
<div className="no-checkin-info">
|
<div className="no-checkin-icon">
|
<i className="fas fa-calendar-plus"></i>
|
</div>
|
<div className="no-checkin-content">
|
<span className="no-checkin-title">暂无签到记录</span>
|
<span className="no-checkin-desc">请及时参与活动签到</span>
|
</div>
|
</div>
|
)}
|
</div>
|
</div>
|
)}
|
</div>
|
) : (
|
<div className="error-container">
|
<i className="fas fa-exclamation-triangle"></i>
|
<span>获取活动信息失败</span>
|
</div>
|
)}
|
|
{/* 签到/签退按钮 */}
|
{activityDetail && (
|
<div className="checkin-button-container">
|
{(() => {
|
const buttonState = getButtonState();
|
return (
|
<button
|
className={`checkin-button ${buttonState.type} ${buttonState.disabled ? 'disabled' : ''}`}
|
onClick={handleButtonClick}
|
disabled={buttonState.disabled || buttonLoading}
|
>
|
{buttonLoading ? (
|
<>
|
<i className="fas fa-spinner fa-spin"></i>
|
<span>处理中...</span>
|
</>
|
) : (
|
<>
|
<i className={`fas ${buttonState.type === 'checkin' ? 'fa-sign-in-alt' :
|
buttonState.type === 'checkout' ? 'fa-sign-out-alt' : 'fa-check-circle'}`}></i>
|
<span>{buttonState.text}</span>
|
</>
|
)}
|
</button>
|
);
|
})()}
|
</div>
|
)}
|
</div>
|
)}
|
|
{/* 微信扫码指引 */}
|
{!hasActivityId && (
|
<div className="wechat-scan-tip">
|
<div className="tip-icon">
|
<i className="fab fa-weixin"></i>
|
</div>
|
<div className="tip-content">
|
<h3>请用微信扫码进行签到/签退</h3>
|
<p>使用微信扫描签到/签退二维码,自动跳转签到/签退页面</p>
|
</div>
|
<div className="tip-steps1">
|
<div className="step-item">
|
<div className="step-number">1</div>
|
<div className="step-text">打开微信扫一扫</div>
|
</div>
|
<div className="step-item">
|
<div className="step-number">2</div>
|
<div className="step-text">扫描签到/签退二维码</div>
|
</div>
|
<div className="step-item">
|
<div className="step-number">3</div>
|
<div className="step-text">自动跳转签到/签退页面</div>
|
</div>
|
</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 ScanCheckinPage;
|