前段
Change-Id: I718d4d07ea03c6d2b6bcbd4d426c5d1af2201bf4
diff --git a/src/components/Personal/ActionCard.jsx b/src/components/Personal/ActionCard.jsx
new file mode 100644
index 0000000..7d3cd4f
--- /dev/null
+++ b/src/components/Personal/ActionCard.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const ActionCard = ({ title, subtitle, icon, onClick }) => {
+ return (
+ <div className="action-card" onClick={onClick}>
+ {icon && <div className="action-icon">{icon}</div>}
+ <div className="action-content">
+ <h3>{title}</h3>
+ <p>{subtitle}</p>
+ </div>
+ </div>
+ );
+};
+
+ActionCard.propTypes = {
+ title: PropTypes.string.isRequired,
+ subtitle: PropTypes.string,
+ icon: PropTypes.node,
+ onClick: PropTypes.func
+};
+
+export default ActionCard;
\ No newline at end of file
diff --git a/src/components/Personal/Favorite.jsx b/src/components/Personal/Favorite.jsx
new file mode 100644
index 0000000..97aa1e6
--- /dev/null
+++ b/src/components/Personal/Favorite.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import ActionCard from './ActionCard';
+import './personalSubpage.css';
+
+const Favorites = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ // 模拟数据
+ const [favorites] = React.useState([
+ { id: 1, name: '盗梦空间', type: 'movie', added: '2023-10-01' },
+ { id: 2, name: '权力的游戏', type: 'tv', added: '2023-09-15' }
+ ]);
+
+ const handleBack = () => {
+ // 返回个人中心,并携带来源标记
+ navigate('/personal', {
+ state: {
+ fromSubpage: true, // 标记来自子页面
+ dashboardTab: location.state?.dashboardTab // 保留Dashboard的标签页状态
+ },
+ replace: true // 替换当前历史记录
+ });
+ };
+
+ return (
+ <div className="personal-page">
+ <button className="back-button" onClick={(handleBack)}>
+ ← 返回个人中心
+ </button>
+
+ <h2>我的收藏</h2>
+ <div className="resource-grid">
+ {favorites.map(item => (
+ <ActionCard
+ key={item.id}
+ title={item.name}
+ subtitle={`收藏于 ${item.added}`}
+ onClick={() => console.log('查看详情', item.id)}
+ />
+ ))}
+ </div>
+ </div>
+ );
+};
+
+export default Favorites;
\ No newline at end of file
diff --git a/src/components/Personal/Notice.jsx b/src/components/Personal/Notice.jsx
new file mode 100644
index 0000000..55bc955
--- /dev/null
+++ b/src/components/Personal/Notice.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { useNavigate,useLocation } from 'react-router-dom';
+import './personalSubpage.css';
+
+const Notice = ({ onLogout }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ // 模拟数据
+ const [notices] = React.useState([
+ {
+ id: 1,
+ title: '积分奖励到账',
+ content: '您上传的资源《盗梦空间》获得100积分奖励',
+ date: '2023-10-20',
+ read: false
+ },
+ {
+ id: 2,
+ title: '系统通知',
+ content: '服务器将于今晚2:00-4:00进行维护',
+ date: '2023-10-18',
+ read: true
+ }
+ ]);
+
+ const handleBack = () => {
+ // 返回个人中心,并携带来源标记
+ navigate('/personal', {
+ state: {
+ fromSubpage: true, // 标记来自子页面
+ dashboardTab: location.state?.dashboardTab // 保留Dashboard的标签页状态
+ },
+ replace: true // 替换当前历史记录
+ });
+ };
+
+ return (
+ <div className="subpage-container">
+ <button className="back-button" onClick={(handleBack)}>
+ ← 返回个人中心
+ </button>
+
+ <h2 className="page-title">消息通知</h2>
+
+ <div className="notice-list">
+ {notices.map(notice => (
+ <div key={notice.id} className={`list-item ${!notice.read ? 'unread' : ''}`}>
+ <div className="notice-header">
+ <h3>{notice.title}</h3>
+ <span className="notice-date">{notice.date}</span>
+ </div>
+ <p className="notice-content">{notice.content}</p>
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+};
+
+export default Notice;
\ No newline at end of file
diff --git a/src/components/Personal/Personal.css b/src/components/Personal/Personal.css
new file mode 100644
index 0000000..c087ac6
--- /dev/null
+++ b/src/components/Personal/Personal.css
@@ -0,0 +1,162 @@
+/* Personal.css */
+.personal-container {
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 20px;
+ }
+
+ .back-button {
+ background: none;
+ border: none;
+ color: #1890ff;
+ cursor: pointer;
+ font-size: 16px;
+ margin-bottom: 20px;
+ padding: 5px 0;
+ }
+
+ .profile-card {
+ background: #fff;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ }
+
+ .profile-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+ }
+
+ .profile-avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ margin-right: 20px;
+ object-fit: cover;
+ }
+
+ .profile-info {
+ flex-grow: 1;
+ }
+
+ .username {
+ font-size: 24px;
+ margin: 0 0 5px;
+ }
+
+ .user-meta {
+ display: flex;
+ gap: 15px;
+ color: #666;
+ font-size: 14px;
+ }
+
+ .stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 15px;
+ }
+
+ .stat-item {
+ background: #f5f5f5;
+ border-radius: 6px;
+ padding: 15px;
+ text-align: center;
+ }
+
+ .stat-label {
+ font-size: 14px;
+ color: #666;
+ margin-bottom: 5px;
+ }
+
+ .stat-value {
+ font-size: 18px;
+ font-weight: bold;
+ }
+
+ .quota-card {
+ background: #fff;
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ }
+
+ .quota-card h3 {
+ margin-top: 0;
+ margin-bottom: 15px;
+ }
+
+ .quota-info {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ }
+
+ .quota-used {
+ color: #1890ff;
+ }
+
+ .quota-remaining {
+ color: #52c41a;
+ }
+
+ .progress-bar {
+ height: 10px;
+ background: #f0f0f0;
+ border-radius: 5px;
+ margin-bottom: 10px;
+ overflow: hidden;
+ }
+
+ .progress-fill {
+ height: 100%;
+ background: #1890ff;
+ border-radius: 5px;
+ transition: width 0.3s ease;
+ }
+
+ .quota-total {
+ text-align: right;
+ color: #666;
+ font-size: 14px;
+ }
+
+ .action-cards {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 15px;
+ }
+
+ .action-card {
+ background: #fff;
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+ cursor: pointer;
+ transition: transform 0.2s ease;
+ }
+
+ .action-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+ }
+
+ .action-card h3 {
+ margin-top: 0;
+ color: #1890ff;
+ }
+
+ .action-card p {
+ color: #666;
+ margin-bottom: 0;
+ }
+
+ .subpage-container {
+ margin-top: 20px;
+ border-top: 1px solid #f0f0f0;
+ padding-top: 20px;
+ }
\ No newline at end of file
diff --git a/src/components/Personal/Personal.jsx b/src/components/Personal/Personal.jsx
new file mode 100644
index 0000000..033952d
--- /dev/null
+++ b/src/components/Personal/Personal.jsx
@@ -0,0 +1,149 @@
+import React from 'react';
+import { useNavigate,useLocation, Outlet } from 'react-router-dom';
+import './Personal.css';
+import ActionCard from './ActionCard';
+
+const Personal = () => {
+ const navigate = useNavigate();
+ const location = useLocation(); // 获取路由信息
+
+ // 模拟用户数据
+ const userData = {
+ username: 'PT爱好者',
+ avatar: 'https://via.placeholder.com/150',
+ joinDate: '2023-01-15',
+ level: '中级会员',
+ points: 1250,
+ upload: '3.2TB',
+ download: '1.5TB',
+ ratio: '2.13',
+ downloadQuota: {
+ total: 10, // 10GB
+ used: 3.7, // 已使用3.7GB
+ remaining: 6.3 // 剩余6.3GB
+ }
+ };
+
+ const features = [
+ {
+ title: '我的收藏',
+ description: '查看收藏的资源',
+ path: '/personal/Favorite' // 相对路径
+ },
+ {
+ 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);
+ }
+ };
+
+ 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>等级: {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">{userData.upload}</div>
+ </div>
+ <div className="stat-item">
+ <div className="stat-label">下载量</div>
+ <div className="stat-value">{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">{userData.downloadQuota.used}GB 已使用</span>
+ <span className="quota-remaining">{userData.downloadQuota.remaining}GB 剩余</span>
+ </div>
+ <div className="progress-bar">
+ <div
+ className="progress-fill"
+ style={{ width: `${(userData.downloadQuota.used / userData.downloadQuota.total) * 100}%` }}
+ ></div>
+ </div>
+ <div className="quota-total">总额度: {userData.downloadQuota.total}GB</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;
\ No newline at end of file
diff --git a/src/components/Personal/Setting.jsx b/src/components/Personal/Setting.jsx
new file mode 100644
index 0000000..967be6c
--- /dev/null
+++ b/src/components/Personal/Setting.jsx
@@ -0,0 +1,99 @@
+import React, { useState } from 'react';
+import { useNavigate,useLocation } from 'react-router-dom';
+import './personalSubpage.css';
+
+const Setting = ({ onLogout }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ // 模拟数据
+ const [formData, setFormData] = useState({
+ username: 'user123',
+ email: 'user@example.com',
+ notification: true
+ });
+
+ const handleChange = (e) => {
+ const { name, value, type, checked } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: type === 'checkbox' ? checked : value
+ }));
+ };
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ alert('设置已保存');
+ };
+
+ const handleBack = () => {
+ // 返回个人中心,并携带来源标记
+ navigate('/personal', {
+ state: {
+ fromSubpage: true, // 标记来自子页面
+ dashboardTab: location.state?.dashboardTab // 保留Dashboard的标签页状态
+ },
+ replace: true // 替换当前历史记录
+ });
+ };
+
+ return (
+ <div className="subpage-container">
+ <button className="back-button" onClick={(handleBack)}>
+ ← 返回个人中心
+ </button>
+
+ <h2 className="page-title">账号设置</h2>
+
+ <form onSubmit={handleSubmit}>
+ <div className="form-group">
+ <label className="form-label">用户名</label>
+ <input
+ type="text"
+ name="username"
+ value={formData.username}
+ onChange={handleChange}
+ className="form-input"
+ />
+ </div>
+
+ <div className="form-group">
+ <label className="form-label">电子邮箱</label>
+ <input
+ type="email"
+ name="email"
+ value={formData.email}
+ onChange={handleChange}
+ className="form-input"
+ />
+ </div>
+
+ <div className="form-group">
+ <label className="form-label">
+ <input
+ type="checkbox"
+ name="notification"
+ checked={formData.notification}
+ onChange={handleChange}
+ />
+ 接收邮件通知
+ </label>
+ </div>
+
+ <div className="form-actions">
+ <button type="submit" className="action-btn">
+ 保存设置
+ </button>
+ <button
+ type="button"
+ className="action-btn danger-btn"
+ onClick={onLogout}
+ >
+ 退出登录
+ </button>
+ </div>
+ </form>
+ </div>
+ );
+};
+
+export default Setting;
\ No newline at end of file
diff --git a/src/components/Personal/Upload.jsx b/src/components/Personal/Upload.jsx
new file mode 100644
index 0000000..4d6e934
--- /dev/null
+++ b/src/components/Personal/Upload.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { useNavigate,useLocation } from 'react-router-dom';
+import './personalSubpage.css';
+
+const Upload = ({ onLogout }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const [uploads] = React.useState([
+ { id: 1, name: '星际穿越', status: '已发布', date: '2023-10-15', size: '15.2GB' },
+ { id: 2, name: '黑暗骑士', status: '审核中', date: '2023-10-18', size: '12.7GB' }
+ ]);
+
+ const handleBack = () => {
+ // 返回个人中心,并携带来源标记
+ navigate('/personal', {
+ state: {
+ fromSubpage: true, // 标记来自子页面
+ dashboardTab: location.state?.dashboardTab // 保留Dashboard的标签页状态
+ },
+ replace: true // 替换当前历史记录
+ });
+ };
+ return (
+ <div className="subpage-container">
+ <button className="back-button" onClick={(handleBack)}>
+ ← 返回个人中心
+ </button>
+
+ <h2 className="page-title">上传记录</h2>
+
+ <table className="uploads-table">
+ <thead>
+ <tr>
+ <th>资源名称</th>
+ <th>大小</th>
+ <th>状态</th>
+ <th>上传时间</th>
+ <th>操作</th>
+ </tr>
+ </thead>
+ <tbody>
+ {uploads.map(item => (
+ <tr key={item.id} className="list-item">
+ <td>{item.name}</td>
+ <td>{item.size}</td>
+ <td>
+ <span className={`status-badge ${
+ item.status === '已发布' ? 'published' : 'pending'
+ }`}>
+ {item.status}
+ </span>
+ </td>
+ <td>{item.date}</td>
+ <td>
+ <button className="action-btn">详情</button>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ );
+};
+
+export default Upload;
\ No newline at end of file
diff --git a/src/components/Personal/personalSubpage.css b/src/components/Personal/personalSubpage.css
new file mode 100644
index 0000000..a8e5638
--- /dev/null
+++ b/src/components/Personal/personalSubpage.css
@@ -0,0 +1,161 @@
+/* 基础布局 */
+.subpage-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ }
+
+ .back-button {
+ background: none;
+ border: none;
+ color: #1890ff;
+ font-size: 16px;
+ cursor: pointer;
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ }
+
+ .back-button:hover {
+ color: #40a9ff;
+ }
+
+ .page-title {
+ color: #333;
+ border-bottom: 1px solid #f0f0f0;
+ padding-bottom: 10px;
+ margin-bottom: 20px;
+ }
+
+ /* 列表项样式 */
+ .list-item {
+ padding: 15px;
+ border-bottom: 1px solid #f5f5f5;
+ transition: background 0.3s;
+ }
+
+ .list-item:hover {
+ background: #f9f9f9;
+ }
+
+ /* 表单样式 */
+ .form-group {
+ margin-bottom: 20px;
+ }
+
+ .form-label {
+ display: block;
+ margin-bottom: 8px;
+ font-weight: 500;
+ }
+
+ .form-input {
+ width: 100%;
+ padding: 10px;
+ border: 1px solid #d9d9d9;
+ border-radius: 4px;
+ }
+
+ /* 按钮样式 */
+ .action-btn {
+ padding: 8px 15px;
+ background: #1890ff;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-right: 10px;
+ }
+
+ .action-btn:hover {
+ background: #40a9ff;
+ }
+
+ .danger-btn {
+ background: #ff4d4f;
+ }
+
+ .danger-btn:hover {
+ background: #ff7875;
+ }
+
+ /* 收藏列表 */
+.favorite-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .item-header {
+ margin-bottom: 8px;
+ }
+
+ .item-meta {
+ color: #666;
+ font-size: 14px;
+ }
+
+ .item-actions {
+ display: flex;
+ gap: 10px;
+ margin-top: 10px;
+ }
+
+ /* 上传表格 */
+ .uploads-table {
+ width: 100%;
+ border-collapse: collapse;
+ }
+
+ .uploads-table th, .uploads-table td {
+ padding: 12px 15px;
+ text-align: left;
+ }
+
+ .status-badge {
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ }
+
+ .status-badge.published {
+ background: #f6ffed;
+ color: #52c41a;
+ }
+
+ .status-badge.pending {
+ background: #fff7e6;
+ color: #fa8c16;
+ }
+
+ /* 消息通知 */
+ .notice-header {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 5px;
+ }
+
+ .notice-date {
+ color: #999;
+ font-size: 14px;
+ }
+
+ .notice-content {
+ color: #666;
+ margin: 0;
+ }
+
+ .unread {
+ background: #f0f7ff;
+ }
+
+ /* 设置表单 */
+ .form-actions {
+ margin-top: 30px;
+ display: flex;
+ gap: 15px;
+ }
\ No newline at end of file