| // Personal.jsx | |
| import React, { useState, useEffect } from 'react'; | |
| import { useNavigate, useLocation, Outlet } from 'react-router-dom'; | |
| import { getUserInfo, getDownloadQuota, getDownloadProgress } from '../../api/personal'; | |
| import './Personal.css'; | |
| import ActionCard from './ActionCard'; | |
| const Personal = () => { | |
| const navigate = useNavigate(); | |
| const location = useLocation(); | |
| const [userData, setUserData] = useState(null); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState(null); | |
| const [downloadProgress, setDownloadProgress] = useState({}); | |
| useEffect(() => { | |
| const fetchData = async () => { | |
| try { | |
| // 并行获取用户信息和下载额度 | |
| const [userInfo, downloadQuota] = await Promise.all([ | |
| getUserInfo(), | |
| getDownloadQuota() | |
| ]); | |
| setUserData({ | |
| username: userInfo.username, | |
| avatar: 'https://via.placeholder.com/150', | |
| joinDate: userInfo.registTime, | |
| level: userInfo.level, // 可以根据userInfo.level设置不同等级 | |
| points: userInfo.magicPoints, | |
| upload: userInfo.upload, | |
| download: userInfo.download, | |
| ratio: userInfo.shareRate, | |
| downloadQuota: { | |
| total: downloadQuota.total, | |
| used: downloadQuota.used, | |
| remaining: downloadQuota.remaining | |
| } | |
| }); | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| fetchData(); | |
| }, []); | |
| // 获取下载进度数据 | |
| const fetchDownloadProgress = async () => { | |
| try { | |
| const progressData = await getDownloadProgress(); | |
| setDownloadProgress(progressData); | |
| } catch (err) { | |
| console.error('获取下载进度失败:', err); | |
| } | |
| }; | |
| useEffect(() => { | |
| // 初始获取下载进度 | |
| fetchDownloadProgress(); | |
| // 设置定时器,每10秒获取一次下载进度 | |
| const intervalId = setInterval(fetchDownloadProgress, 10000); | |
| // 组件卸载时清除定时器 | |
| return () => clearInterval(intervalId); | |
| }, []); | |
| if (loading) { | |
| return <div className="loading">加载中...</div>; | |
| } | |
| if (error) { | |
| return <div className="error">错误: {error}</div>; | |
| } | |
| if (!userData) { | |
| return <div className="error">未获取到用户数据</div>; | |
| } | |
| const features = [ | |
| { | |
| title: '兑换区', | |
| description: '下载量/邀请码', | |
| path: '/personal/Exchange' // 相对路径 | |
| }, | |
| { | |
| title: '上传记录', | |
| description: '管理上传的资源', | |
| path: '/personal/Upload' | |
| }, | |
| { | |
| title: '消息通知', | |
| description: '查看系统消息', | |
| path: '/personal/Notice' | |
| }, | |
| { | |
| title: '设置', | |
| description: '修改个人资料', | |
| path: '/personal/Setting' | |
| } | |
| ]; | |
| const handleCardClick = (path) => { | |
| navigate(path); // 相对导航 | |
| }; | |
| const handleBack = () => { | |
| if (location.state?.fromSubpage) { | |
| // 如果是从子页面返回,则继续返回到Dashboard | |
| navigate(`/dashboard/${location.state.dashboardTab || ''}`, { | |
| replace: true | |
| }); | |
| } else { | |
| // 普通返回逻辑 | |
| navigate(-1); | |
| } | |
| }; | |
| const formatSize = (bytes) => { | |
| if (bytes < 1024) return `${bytes} B`; | |
| const kb = bytes / 1024; | |
| if (kb < 1024) return `${kb.toFixed(2)} KB`; | |
| const mb = kb / 1024; | |
| if (mb < 1024) return `${mb.toFixed(2)} MB`; | |
| const gb = mb / 1024; | |
| return `${gb.toFixed(2)} GB`; | |
| }; | |
| // 添加进度条颜色函数 | |
| const getProgressColor = (percentage) => { | |
| if (percentage < 0.3) return '#4CAF50'; // 绿色 | |
| if (percentage < 0.7) return '#FFC107'; // 黄色 | |
| return '#F44336'; // 红色 | |
| }; | |
| return ( | |
| <div className="personal-container"> | |
| {/* 返回按钮 */} | |
| <button className="back-button" onClick={handleBack}> | |
| 返回 | |
| </button> | |
| {/* 用户基本信息卡片 */} | |
| <div className="profile-card"> | |
| <div className="profile-header"> | |
| <img | |
| src={userData.avatar} | |
| alt={userData.username} | |
| className="profile-avatar" | |
| /> | |
| <div className="profile-info"> | |
| <h2 className="username">{userData.username}</h2> | |
| <div className="user-meta"> | |
| <span>加入时间: {userData.joinDate}</span> | |
| <span>会员等级: Lv.{userData.level}</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* 用户数据统计 */} | |
| <div className="stats-grid"> | |
| <div className="stat-item"> | |
| <div className="stat-label">保种积分</div> | |
| <div className="stat-value">{userData.points}</div> | |
| </div> | |
| <div className="stat-item"> | |
| <div className="stat-label">上传量</div> | |
| <div className="stat-value">{formatSize(userData.upload)}</div> | |
| </div> | |
| <div className="stat-item"> | |
| <div className="stat-label">下载量</div> | |
| <div className="stat-value">{formatSize(userData.download)}</div> | |
| </div> | |
| <div className="stat-item"> | |
| <div className="stat-label">分享率</div> | |
| <div className="stat-value">{userData.ratio}</div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* 下载额度卡片 */} | |
| <div className="quota-card"> | |
| <h3>下载额度</h3> | |
| <div className="quota-info"> | |
| <span className="quota-used"> | |
| {formatSize(userData.downloadQuota.used)} 已使用 | |
| </span> | |
| <span className="quota-remaining"> | |
| {formatSize(userData.downloadQuota.remaining)} 剩余 | |
| </span> | |
| </div> | |
| <div className="progress-bar"> | |
| <div | |
| className="progress-fill" | |
| style={{ | |
| width: `${(userData.downloadQuota.used / userData.downloadQuota.total) * 100}%`, | |
| backgroundColor: getProgressColor(userData.downloadQuota.used / userData.downloadQuota.total) | |
| }} | |
| ></div> | |
| </div> | |
| <div className="quota-total"> | |
| 总额度: {formatSize(userData.downloadQuota.total)} | |
| </div> | |
| </div> | |
| {Object.keys(downloadProgress).length > 0 && ( | |
| <div className="progress-card"> | |
| <h3>当前下载进度</h3> | |
| {Object.entries(downloadProgress).map(([taskId, progress]) => ( | |
| <div key={taskId} className="download-task"> | |
| <div className="task-info"> | |
| <span className="task-id">任务: {taskId.substring(0, 8)}...</span> | |
| <span className="task-progress">{Math.round(progress * 100)}%</span> | |
| </div> | |
| <div className="progress-bar"> | |
| <div | |
| className="progress-fill" | |
| style={{ width: `${progress * 100}%` }} | |
| ></div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {/* 功能卡片区 */} | |
| <div className="action-cards"> | |
| {features.map((feature) => ( | |
| <div | |
| key={feature.path} | |
| className="action-card" | |
| onClick={() => handleCardClick(feature.path)} | |
| > | |
| <h3>{feature.title}</h3> | |
| <p>{feature.description}</p> | |
| </div> | |
| ))} | |
| </div> | |
| {/* 子路由出口 */} | |
| <div className="subpage-container"> | |
| <Outlet /> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default Personal; |