/**
|
* @author 韩天尊
|
* @time 2024-01-15
|
* @version 1.0.0
|
* @description 积分核销页面组件
|
*/
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import { useNavigate } from 'react-router-dom';
|
import { useAppContext } from '../context/AppContext';
|
import { pointsAPI } from '../services/api';
|
import { PointsOverview, CommunityPoints, CommunityPointsOverview, QRCodeScanResult } from '../types';
|
import PageHeader from '../components/PageHeader';
|
import QRCode from 'qrcode';
|
import { APP_CONFIG } from '../config/appConfig';
|
|
const PointsRedemptionPage: React.FC = () => {
|
const navigate = useNavigate();
|
const { state } = useAppContext();
|
const [qrCodeKey, setQrCodeKey] = useState(Date.now()); // 用于刷新二维码
|
const [qrCodeUrl, setQrCodeUrl] = useState<string>(''); // 存储二维码图片URL
|
const qrCanvasRef = useRef<HTMLCanvasElement>(null);
|
|
const [pointsData, setPointsData] = useState<PointsOverview>({
|
available: 0,
|
redeemed: 0,
|
total: 0
|
});
|
const [communityPointsData, setCommunityPointsData] = useState<CommunityPoints[]>([]);
|
const [selectedCommunityIndex, setSelectedCommunityIndex] = useState<number>(0);
|
const [loading, setLoading] = useState(false);
|
|
// 生成二维码 - 使用 useCallback 包装
|
const generateQRCode = useCallback(async () => {
|
try {
|
// 检查必要数据是否存在
|
if (!state.user?.id) {
|
console.log('等待用户数据加载...');
|
return;
|
}
|
|
if (communityPointsData.length === 0) {
|
console.log('等待社区数据加载...');
|
return;
|
}
|
|
const currentCommunity = communityPointsData[selectedCommunityIndex];
|
if (!currentCommunity) {
|
console.log('社区数据不存在');
|
return;
|
}
|
|
// 生成核销二维码URL
|
const params = new URLSearchParams({
|
communityCode: currentCommunity.communityCode || '',
|
volunteerId: String(state.user.id),
|
volunteerName: state.user.name || '',
|
points: pointsData.available.toString()
|
});
|
|
// 使用 HashRouter 格式,添加 # 符号
|
const qrString = `${APP_CONFIG.ADMIN_BASE_URL}/#/admin-points-redemption?${params.toString()}`;
|
|
console.log('开始生成二维码,URL:', qrString);
|
|
// 生成二维码并保存为Data URL
|
const qrUrl = await QRCode.toDataURL(qrString, {
|
width: 200,
|
margin: 2,
|
color: {
|
dark: '#ff6b6b', // 使用主题色
|
light: '#FFFFFF'
|
},
|
errorCorrectionLevel: 'M'
|
});
|
|
setQrCodeUrl(qrUrl);
|
console.log('二维码生成成功, qrCodeUrl长度:', qrUrl?.length);
|
|
// 同时生成Canvas版本用于更好的显示效果
|
if (qrCanvasRef.current) {
|
try {
|
await QRCode.toCanvas(qrCanvasRef.current, qrString, {
|
width: 180,
|
margin: 2,
|
color: {
|
dark: '#ff6b6b',
|
light: '#FFFFFF'
|
},
|
errorCorrectionLevel: 'M'
|
});
|
console.log('Canvas二维码生成成功');
|
} catch (canvasError) {
|
console.error('Canvas二维码生成失败:', canvasError);
|
}
|
}
|
} catch (error) {
|
console.error('生成二维码失败:', error);
|
setQrCodeUrl(''); // 清空二维码,显示加载状态
|
}
|
}, [state.user, communityPointsData, selectedCommunityIndex, pointsData.available]);
|
|
// 加载积分数据
|
useEffect(() => {
|
const loadPointsData = async () => {
|
setLoading(true);
|
try {
|
const response = await pointsAPI.getOverview();
|
console.log('积分概览接口返回数据:', response);
|
|
// 检查返回的数据结构
|
if ('communityList' in response.data && Array.isArray(response.data.communityList) && response.data.communityList.length > 0) {
|
// 新的社区积分数据结构
|
console.log('使用新数据结构 - CommunityPointsOverview');
|
const communityData = response.data as CommunityPointsOverview;
|
console.log('社区列表数据:', communityData.communityList);
|
setCommunityPointsData(communityData.communityList);
|
// 设置默认选中第一个社区
|
const firstCommunity = communityData.communityList[0];
|
setPointsData({
|
available: firstCommunity.points,
|
redeemed: firstCommunity.redeemedPoints,
|
total: firstCommunity.totalPoints
|
});
|
} else {
|
// 兼容旧的数据结构或社区数据为空
|
console.log('使用旧数据结构或社区为空 - PointsOverview');
|
const pointsData = response.data as PointsOverview;
|
console.log('积分数据:', pointsData);
|
setPointsData(pointsData);
|
// 创建默认的虚拟社区用于生成二维码
|
setCommunityPointsData([{
|
points: pointsData.available,
|
redeemedPoints: pointsData.redeemed,
|
totalPoints: pointsData.total,
|
communityName: '暂未绑定社区',
|
communityCode: '0'
|
}]);
|
}
|
} catch (error) {
|
console.error('加载积分数据失败:', error);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
loadPointsData();
|
}, [state.user]);
|
|
// 当积分数据或社区改变时重新生成二维码
|
useEffect(() => {
|
generateQRCode();
|
}, [generateQRCode]);
|
|
// 处理社区切换
|
const handleCommunityChange = (index: number) => {
|
setSelectedCommunityIndex(index);
|
if (communityPointsData[index]) {
|
const community = communityPointsData[index];
|
setPointsData({
|
available: community.points,
|
redeemed: community.redeemedPoints,
|
total: community.totalPoints
|
});
|
}
|
};
|
|
// 扫描二维码获取用户信息
|
const scanQRCodeUser = async (communityCode: string) => {
|
try {
|
const response = await pointsAPI.scanQRCodeUser(communityCode);
|
if (response.code === 0) {
|
const result: QRCodeScanResult = response.data;
|
showToast(`扫描成功!用户:${result.userName},可用积分:${result.points}`);
|
console.log('扫描结果:', result);
|
|
// 跳转到管理端积分核销页面并传递参数
|
const currentCommunity = communityPointsData[selectedCommunityIndex];
|
const params = new URLSearchParams({
|
communityCode: currentCommunity?.communityCode || '',
|
volunteerId: String(result.userId || state.user?.id || ''),
|
volunteerName: result.userName || state.user?.name || '',
|
points: (result.points || pointsData.available).toString()
|
});
|
|
// 构建完整的管理端URL(使用 HashRouter 格式)
|
const adminUrl = `${APP_CONFIG.ADMIN_BASE_URL}/#/admin-points-redemption?${params.toString()}`;
|
|
// 跳转到外部管理端页面
|
window.location.href = adminUrl;
|
|
return result;
|
} else {
|
showToast(`扫描失败:${response.msg}`);
|
return null;
|
}
|
} catch (error) {
|
console.error('扫描二维码失败:', error);
|
showToast('扫描失败,请重试');
|
return null;
|
}
|
};
|
|
// 刷新二维码
|
const refreshQRCode = () => {
|
setQrCodeKey(Date.now());
|
generateQRCode();
|
showToast('二维码已刷新');
|
};
|
|
// 显示核销记录
|
const showRedemptionHistory = () => {
|
navigate('/points-query');
|
};
|
|
// 显示提示信息
|
const showToast = (message: string) => {
|
// 简单的提示实现
|
const toast = document.createElement('div');
|
toast.style.cssText = `
|
position: fixed;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
background: rgba(0, 0, 0, 0.8);
|
color: white;
|
padding: 12px 24px;
|
border-radius: 6px;
|
z-index: 10000;
|
font-size: 14px;
|
`;
|
toast.textContent = message;
|
document.body.appendChild(toast);
|
|
setTimeout(() => {
|
if (document.body.contains(toast)) {
|
document.body.removeChild(toast);
|
}
|
}, 2000);
|
};
|
|
return (
|
<div className="page">
|
<PageHeader title="积分核销" />
|
|
{/* 积分统计卡片 */}
|
<div className="redemption-summary">
|
<div className="summary-card">
|
<div className="summary-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="summary-content">
|
<div className="summary-item">
|
<div className="summary-label">可用积分</div>
|
<div className="summary-value">{pointsData.available}</div>
|
</div>
|
<div className="summary-item">
|
<div className="summary-label">已核销积分</div>
|
<div className="summary-value">{pointsData.redeemed}</div>
|
</div>
|
<div className="summary-item">
|
<div className="summary-label">累计积分</div>
|
<div className="summary-value">{pointsData.total}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
{/* 核销操作区域 */}
|
<div className="redemption-section">
|
<div className="redemption-card">
|
{/* 核销二维码 */}
|
<div className="qr-container">
|
<div className="qr-code-wrapper" key={qrCodeKey}>
|
{qrCodeUrl ? (
|
<div className="qr-display">
|
<div className="qr-code-border">
|
<img
|
src={qrCodeUrl}
|
alt="二维码"
|
style={{
|
width: '180px',
|
height: '180px',
|
display: 'block',
|
margin: '0 auto'
|
}}
|
onLoad={() => console.log('二维码图片显示成功')}
|
onError={(e) => {
|
console.error('二维码图片显示失败:', e);
|
console.log('qrCodeUrl:', qrCodeUrl?.substring(0, 100));
|
}}
|
/>
|
</div>
|
<div className="qr-info">
|
<h4>核销二维码</h4>
|
<p>微信扫码直接跳转核销页面</p>
|
</div>
|
</div>
|
) : (
|
<div className="qr-loading">
|
<div className="qr-icon">
|
<i className="fas fa-qrcode"></i>
|
</div>
|
<div className="qr-text">
|
<h4>生成二维码中...</h4>
|
<p>请稍候</p>
|
</div>
|
</div>
|
)}
|
</div>
|
</div>
|
|
{/* 核销说明 */}
|
<div className="redemption-tips">
|
<div className="tip-item">
|
<i className="fas fa-mobile-alt"></i>
|
<span>微信扫码可直接跳转核销页面</span>
|
</div>
|
<div className="tip-item">
|
<i className="fas fa-info-circle"></i>
|
<span>核销时请确保网络连接正常</span>
|
</div>
|
<div className="tip-item">
|
<i className="fas fa-shield-alt"></i>
|
<span>请勿将二维码分享给他人</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
{/* 快捷操作 */}
|
<div className="redemption-actions">
|
<button className="action-btn" onClick={refreshQRCode}>
|
<i className="fas fa-sync-alt"></i>
|
<span>刷新二维码</span>
|
</button>
|
<button className="action-btn" onClick={showRedemptionHistory}>
|
<i className="fas fa-history"></i>
|
<span>核销记录</span>
|
</button>
|
<button className="action-btn" onClick={() => {
|
const currentCommunity = communityPointsData[selectedCommunityIndex];
|
const params = new URLSearchParams({
|
communityCode: currentCommunity?.communityCode || '',
|
volunteerId: String(state.user?.id || ''),
|
volunteerName: state.user?.name || '',
|
points: pointsData.available.toString()
|
});
|
const adminUrl = `${APP_CONFIG.ADMIN_BASE_URL}/#/admin-points-redemption?${params.toString()}`;
|
window.open(adminUrl, '_blank');
|
}} style={{ display: 'none' }}>
|
<i className="fas fa-external-link-alt"></i>
|
<span>预览核销页面</span>
|
</button>
|
</div>
|
</div>
|
);
|
};
|
|
export default PointsRedemptionPage;
|