final
Change-Id: Icf4ec3950a6bd1e066fa4a1976af36721af62a06
diff --git a/front/src/AdminPage.css b/front/src/AdminPage.css
new file mode 100644
index 0000000..3da371c
--- /dev/null
+++ b/front/src/AdminPage.css
@@ -0,0 +1,447 @@
+/* AdminPage 特定样式 - 翡翠园林风格 */
+@import './SharedStyles.css';
+
+/* 管理员页面容器 */
+.admin-page-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 20%, #8fbc8f 40%, #98fb98 60%, #f0fff0 100%);
+ position: relative;
+ font-family: 'Lora', serif;
+ overflow-x: hidden;
+}
+
+/* 背景装饰元素 */
+.admin-page-container::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.06) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(144, 238, 144, 0.08) 0%, transparent 50%);
+ animation: backgroundShift 25s ease-in-out infinite;
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* 主内容区域 */
+.admin-main-content {
+ position: relative;
+ z-index: 10;
+ padding: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+/* 页面标题 */
+.admin-title {
+ text-align: center;
+ margin: 30px 0 40px 0;
+ color: #fff;
+ font-family: 'Playfair Display', serif;
+ font-size: 3rem;
+ font-weight: 700;
+ text-shadow:
+ 0 4px 12px rgba(45, 80, 22, 0.4),
+ 0 2px 8px rgba(0, 0, 0, 0.3);
+ letter-spacing: 2px;
+ position: relative;
+}
+
+.admin-title::after {
+ content: '';
+ position: absolute;
+ bottom: -15px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 120px;
+ height: 4px;
+ background: linear-gradient(90deg, transparent, #90ee90, transparent);
+ border-radius: 2px;
+ box-shadow: 0 0 15px rgba(144, 238, 144, 0.6);
+}
+
+/* 系统参数卡片 */
+.admin-config-card {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(20px);
+ border-radius: 25px;
+ padding: 25px 35px;
+ margin-bottom: 40px;
+ box-shadow:
+ 0 20px 60px rgba(45, 80, 22, 0.12),
+ 0 8px 25px rgba(144, 238, 144, 0.08),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ position: relative;
+ overflow: hidden;
+ display: flex;
+ gap: 30px;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+}
+
+.admin-config-card::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ background: linear-gradient(45deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #f0fff0 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 27px;
+ z-index: -1;
+ animation: borderGlow 4s ease-in-out infinite;
+}
+
+.admin-config-label {
+ font-weight: 700;
+ color: #2d5016;
+ font-size: 18px;
+ letter-spacing: 1px;
+}
+
+.admin-config-item {
+ color: #4a7c59;
+ font-weight: 600;
+ font-size: 16px;
+ padding: 8px 16px;
+ background: rgba(144, 238, 144, 0.1);
+ border-radius: 12px;
+ border: 1px solid rgba(144, 238, 144, 0.3);
+ transition: all 0.3s ease;
+}
+
+.admin-config-item:hover {
+ background: rgba(144, 238, 144, 0.2);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(144, 238, 144, 0.3);
+}
+
+/* 用户列表区域 */
+.admin-section {
+ margin-bottom: 40px;
+}
+
+.admin-section-title {
+ color: #fff;
+ font-family: 'Playfair Display', serif;
+ font-size: 2rem;
+ font-weight: 600;
+ margin-bottom: 20px;
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ letter-spacing: 1px;
+}
+
+.admin-section-title.cheat {
+ color: #ffcdd2;
+}
+
+.admin-section-title.suspicious {
+ color: #fff3e0;
+}
+
+/* 表格容器 - 使用与HomePage一致的样式 */
+.admin-table-container {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(20px);
+ border-radius: 25px;
+ padding: 0;
+ margin: 30px 0;
+ box-shadow:
+ 0 20px 60px rgba(45, 80, 22, 0.12),
+ 0 8px 25px rgba(144, 238, 144, 0.08),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ position: relative;
+ overflow: hidden;
+}
+
+.admin-table-container::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ background: linear-gradient(45deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #f0fff0 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 27px;
+ z-index: -1;
+ animation: borderGlow 4s ease-in-out infinite;
+}
+
+/* 表格样式 */
+.admin-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-family: 'Lora', serif;
+ background: transparent;
+}
+
+.admin-table thead {
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 100%);
+}
+
+.admin-table th {
+ padding: 18px 24px;
+ text-align: left;
+ color: white;
+ font-weight: 600;
+ font-size: 16px;
+ letter-spacing: 1px;
+ border-bottom: 3px solid #90ee90;
+ position: relative;
+}
+
+.admin-table th:first-child {
+ border-radius: 23px 0 0 0;
+}
+
+.admin-table th:last-child {
+ border-radius: 0 23px 0 0;
+}
+
+.admin-table tbody tr {
+ transition: all 0.3s ease;
+ border-bottom: 1px solid rgba(144, 238, 144, 0.2);
+}
+
+.admin-table tbody tr:hover {
+ background: rgba(144, 238, 144, 0.1);
+ transform: translateX(5px);
+ box-shadow: 0 4px 15px rgba(144, 238, 144, 0.15);
+}
+
+.admin-table tbody tr:last-child:hover td:first-child {
+ border-radius: 0 0 0 23px;
+}
+
+.admin-table tbody tr:last-child:hover td:last-child {
+ border-radius: 0 0 23px 0;
+}
+
+.admin-table td {
+ padding: 16px 24px;
+ color: #2d5016;
+ font-size: 15px;
+ vertical-align: middle;
+ transition: all 0.3s ease;
+}
+
+.admin-table tbody tr:hover td {
+ color: #1a5c1a;
+}
+
+/* 状态文本样式 */
+.status-banned {
+ color: #e53935 !important;
+ font-weight: 600;
+}
+
+.status-normal {
+ color: #43a047 !important;
+ font-weight: 600;
+}
+
+.status-warning {
+ color: #ff9800 !important;
+ font-weight: 600;
+}
+
+/* 按钮样式 */
+.admin-btn {
+ border: none;
+ border-radius: 12px;
+ padding: 8px 18px;
+ font-weight: 600;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-family: 'Lora', serif;
+ letter-spacing: 0.5px;
+ position: relative;
+ overflow: hidden;
+}
+
+.admin-btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ transition: all 0.3s ease;
+ transform: translate(-50%, -50%);
+}
+
+.admin-btn:hover::before {
+ width: 100px;
+ height: 100px;
+}
+
+.admin-btn:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
+}
+
+.admin-btn-unban {
+ background: linear-gradient(135deg, #13F31E, #43a047);
+ color: white;
+}
+
+.admin-btn-ban {
+ background: linear-gradient(135deg, #e53935, #c62828);
+ color: white;
+}
+
+/* 导航按钮区域 */
+.admin-nav-buttons {
+ display: flex;
+ gap: 30px;
+ justify-content: center;
+ margin-top: 50px;
+ flex-wrap: wrap;
+}
+
+.admin-nav-btn {
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 100%);
+ color: white;
+ border: none;
+ border-radius: 15px;
+ padding: 15px 35px;
+ font-weight: 600;
+ font-size: 16px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-family: 'Lora', serif;
+ letter-spacing: 1px;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 8px 25px rgba(45, 80, 22, 0.3);
+}
+
+.admin-nav-btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(144, 238, 144, 0.3);
+ border-radius: 50%;
+ transition: all 0.3s ease;
+ transform: translate(-50%, -50%);
+}
+
+.admin-nav-btn:hover::before {
+ width: 200px;
+ height: 200px;
+}
+
+.admin-nav-btn:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 15px 40px rgba(45, 80, 22, 0.4);
+}
+
+.admin-nav-btn.appeal {
+ background: linear-gradient(135deg, #1976d2, #1565c0);
+}
+
+.admin-nav-btn.migration {
+ background: linear-gradient(135deg, #43a047, #388e3c);
+}
+
+.admin-nav-btn.promotion {
+ background: linear-gradient(135deg, #ff9800, #f57c00);
+}
+
+/* 动画效果 */
+@keyframes backgroundShift {
+ 0%, 100% { transform: scale(1) rotate(0deg); }
+ 50% { transform: scale(1.1) rotate(1deg); }
+}
+
+@keyframes borderGlow {
+ 0%, 100% { opacity: 0.6; }
+ 50% { opacity: 1; }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .admin-main-content {
+ padding: 0 15px;
+ }
+
+ .admin-title {
+ font-size: 2.2rem;
+ }
+
+ .admin-config-card {
+ padding: 20px 25px;
+ gap: 20px;
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .admin-table-container {
+ margin: 20px 0;
+ border-radius: 20px;
+ }
+
+ .admin-table th,
+ .admin-table td {
+ padding: 12px 16px;
+ font-size: 14px;
+ }
+
+ .admin-nav-buttons {
+ gap: 20px;
+ }
+
+ .admin-nav-btn {
+ padding: 12px 28px;
+ font-size: 15px;
+ }
+}
+
+@media (max-width: 480px) {
+ .admin-title {
+ font-size: 1.8rem;
+ }
+
+ .admin-config-card {
+ padding: 15px 20px;
+ }
+
+ .admin-table th,
+ .admin-table td {
+ padding: 10px 12px;
+ font-size: 13px;
+ }
+
+ .admin-nav-buttons {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .admin-nav-btn {
+ width: 100%;
+ max-width: 300px;
+ }
+}
diff --git a/front/src/AdminPage.js b/front/src/AdminPage.js
index f063c09..218bc9a 100644
--- a/front/src/AdminPage.js
+++ b/front/src/AdminPage.js
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { API_BASE_URL } from "./config";
+import "./AdminPage.css";
// 示例数据
const initialConfig = {
@@ -117,106 +118,108 @@
}, []);
return (
- <div style={{ padding: 40, maxWidth: 900, margin: "0 auto" }}>
- <h1 style={{ textAlign: "center", marginBottom: 32 }}>管理员页面</h1>
- {/* 参数设置 */}
- <div style={{ marginBottom: 32, padding: 18, background: "#f7faff", borderRadius: 12, display: "flex", gap: 24, alignItems: "center", justifyContent: "space-between" }}>
- <b>系统参数:</b>
- <span>FarmNumber: {config.FarmNumber}</span>
- <span>FakeTime: {config.FakeTime}</span>
- <span>BegVote: {config.BegVote}</span>
- <span>CheatTime: {config.CheatTime}</span>
- </div>
- {/* 作弊用户 */}
- <div style={{ marginBottom: 32 }}>
- <h2 style={{ color: "#e53935" }}>作弊用户</h2>
- <table style={{ width: "100%", background: "#fff", borderRadius: 10, boxShadow: "0 2px 8px #e0e7ff", marginBottom: 18 }}>
- <thead>
- <tr style={{ background: "#f5f5f5" }}>
- {/* <th>user_id</th> */}
- <th>email</th>
- <th>username</th>
- <th>account_status</th>
- <th>操作</th>
- </tr>
- </thead>
- <tbody>
- {cheatUsers.map((u) => (
- <tr key={u.userid}>
- {/* <td>{u.userid}</td> */}
- <td>{u.email}</td>
- <td>{u.username}</td>
- <td style={{ color: "#e53935" }}>
- {"封禁"}
- </td>
- <td>
- <button
- style={{ background: "#13F31E", color: "#fff", border: "none", borderRadius: 6, padding: "4px 14px", cursor: "pointer" }}
- onClick={() => handleUnban(u)}
- >
- 解封
- </button>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- {/* 可疑用户 */}
- <div style={{ marginBottom: 32 }}>
- <h2 style={{ color: "#ff9800" }}>可疑用户</h2>
- <table style={{ width: "100%", background: "#fff", borderRadius: 10, boxShadow: "0 2px 8px #e0e7ff" }}>
- <thead>
- <tr style={{ background: "#f5f5f5" }}>
- {/* <th>user_id</th> */}
- <th>email</th>
- <th>username</th>
- <th>account_status</th>
- <th>操作</th>
- </tr>
- </thead>
- <tbody>
- {suspiciousUsers.map((u) => (
- <tr key={u.user_id}>
- {/* <td>{u.user_id}</td> */}
- <td>{u.email}</td>
- <td>{u.username}</td>
- <td style={{ color: u.account_status === 1 ? "#e53935" : "#43a047" }}>
- {u.account_status === 1 ? "封禁" : "正常"}
- </td>
- <td>
- <button
- style={{ background: "#e53935", color: "#fff", border: "none", borderRadius: 6, padding: "4px 14px", cursor: "pointer" }}
- onClick={() => handleBan(u)}
- >
- 封禁
- </button>
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- {/* 跳转按钮 */}
- <div style={{ display: "flex", gap: 24, justifyContent: "center" }}>
- <button
- style={{ background: "#1976d2", color: "#fff", border: "none", borderRadius: 8, padding: "10px 28px", fontWeight: 600, fontSize: 16, cursor: "pointer" }}
- onClick={() => navigate("/appeal-review")}
- >
- 用户申诉
- </button>
- <button
- style={{ background: "#43a047", color: "#fff", border: "none", borderRadius: 8, padding: "10px 28px", fontWeight: 600, fontSize: 16, cursor: "pointer" }}
- onClick={() => navigate("/migration-review")}
- >
- 用户迁移
- </button>
- <button
- style={{ background: "#ff9800", color: "#fff", border: "none", borderRadius: 8, padding: "10px 28px", fontWeight: 600, fontSize: 16, cursor: "pointer" }}
- onClick={() => navigate("/seed-promotion")}
- >
- 促销管理
- </button>
+ <div className="admin-page-container">
+ <div className="admin-main-content">
+ <h1 className="admin-title">管理员页面</h1>
+ {/* 参数设置 */}
+ <div className="admin-config-card">
+ <span className="admin-config-label">系统参数:</span>
+ <span className="admin-config-item">FarmNumber: {config.FarmNumber}</span>
+ <span className="admin-config-item">FakeTime: {config.FakeTime}</span>
+ <span className="admin-config-item">BegVote: {config.BegVote}</span>
+ <span className="admin-config-item">CheatTime: {config.CheatTime}</span>
+ </div>
+ {/* 作弊用户 */}
+ <div className="admin-section">
+ <h2 className="admin-section-title cheat">作弊用户</h2>
+ <div className="admin-table-container">
+ <table className="admin-table">
+ <thead>
+ <tr>
+ <th>邮箱</th>
+ <th>用户名</th>
+ <th>账户状态</th>
+ <th>操作</th>
+ </tr>
+ </thead>
+ <tbody>
+ {cheatUsers.map((u) => (
+ <tr key={u.userid}>
+ <td>{u.email}</td>
+ <td>{u.username}</td>
+ <td className="status-banned">
+ 封禁
+ </td>
+ <td>
+ <button
+ className="admin-btn admin-btn-unban"
+ onClick={() => handleUnban(u)}
+ >
+ 解封
+ </button>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ {/* 可疑用户 */}
+ <div className="admin-section">
+ <h2 className="admin-section-title suspicious">可疑用户</h2>
+ <div className="admin-table-container">
+ <table className="admin-table">
+ <thead>
+ <tr>
+ <th>邮箱</th>
+ <th>用户名</th>
+ <th>账户状态</th>
+ <th>操作</th>
+ </tr>
+ </thead>
+ <tbody>
+ {suspiciousUsers.map((u) => (
+ <tr key={u.user_id}>
+ <td>{u.email}</td>
+ <td>{u.username}</td>
+ <td className={u.account_status === 1 ? "status-banned" : "status-normal"}>
+ {u.account_status === 1 ? "封禁" : "正常"}
+ </td>
+ <td>
+ <button
+ className="admin-btn admin-btn-ban"
+ onClick={() => handleBan(u)}
+ >
+ 封禁
+ </button>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ {/* 跳转按钮 */}
+ <div className="admin-nav-buttons">
+ <button
+ className="admin-nav-btn appeal"
+ onClick={() => navigate("/appeal-review")}
+ >
+ 用户申诉
+ </button>
+ <button
+ className="admin-nav-btn migration"
+ onClick={() => navigate("/migration-review")}
+ >
+ 用户迁移
+ </button>
+ <button
+ className="admin-nav-btn promotion"
+ onClick={() => navigate("/seed-promotion")}
+ >
+ 促销管理
+ </button>
+ </div>
</div>
</div>
);
diff --git a/front/src/AppealPage.css b/front/src/AppealPage.css
new file mode 100644
index 0000000..44cbc5d
--- /dev/null
+++ b/front/src/AppealPage.css
@@ -0,0 +1,456 @@
+/* AppealPage 特定样式 - 翡翠园林风格 */
+@import './SharedStyles.css';
+
+/* 申诉页面容器 */
+.appeal-page-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 20%, #8fbc8f 40%, #98fb98 60%, #f0fff0 100%);
+ position: relative;
+ font-family: 'Lora', serif;
+ overflow-x: hidden;
+ display: flex;
+}
+
+/* 背景装饰元素 */
+.appeal-page-container::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.06) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(144, 238, 144, 0.08) 0%, transparent 50%);
+ animation: backgroundShift 25s ease-in-out infinite;
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* 侧栏样式 */
+.appeal-sidebar {
+ width: 300px;
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(20px);
+ border-right: 3px solid rgba(144, 238, 144, 0.3);
+ padding: 0;
+ position: relative;
+ z-index: 10;
+ box-shadow: 4px 0 20px rgba(45, 80, 22, 0.1);
+}
+
+.appeal-sidebar::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(180deg,
+ rgba(144, 238, 144, 0.05) 0%,
+ rgba(255, 255, 255, 0.02) 50%,
+ rgba(144, 238, 144, 0.05) 100%);
+ z-index: -1;
+}
+
+/* 侧栏标题 */
+.appeal-sidebar-title {
+ text-align: center;
+ padding: 25px 20px 20px 20px;
+ color: #2d5016;
+ font-family: 'Playfair Display', serif;
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin: 0;
+ text-shadow: 0 2px 4px rgba(45, 80, 22, 0.1);
+ border-bottom: 2px solid rgba(144, 238, 144, 0.2);
+ background: linear-gradient(135deg, rgba(144, 238, 144, 0.1), rgba(255, 255, 255, 0.1));
+}
+
+/* 申诉列表容器 */
+.appeal-list-container {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ margin-top: 20px;
+ padding: 0 15px;
+}
+
+/* 申诉项目 */
+.appeal-list-item {
+ margin: 0;
+ padding: 18px 15px;
+ border-radius: 15px;
+ background: rgba(255, 255, 255, 0.8);
+ border: 2px solid transparent;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 4px 15px rgba(45, 80, 22, 0.08);
+}
+
+.appeal-list-item::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent,
+ rgba(144, 238, 144, 0.1),
+ transparent);
+ transition: left 0.5s ease;
+}
+
+.appeal-list-item:hover::before {
+ left: 100%;
+}
+
+.appeal-list-item:hover {
+ transform: translateX(8px);
+ box-shadow: 0 8px 25px rgba(45, 80, 22, 0.15);
+}
+
+/* 选中状态 */
+.appeal-list-item.selected {
+ background: rgba(144, 238, 144, 0.15);
+ border-color: rgba(144, 238, 144, 0.5);
+ transform: translateX(8px);
+ box-shadow: 0 8px 25px rgba(144, 238, 144, 0.2);
+}
+
+/* 审核状态样式 */
+.appeal-list-item.approved {
+ border-color: rgba(67, 160, 71, 0.6);
+ color: #2e7d32;
+}
+
+.appeal-list-item.pending {
+ border-color: rgba(229, 57, 53, 0.6);
+ color: #c62828;
+}
+
+/* 状态标签 */
+.appeal-status-label {
+ float: right;
+ font-size: 12px;
+ padding: 2px 8px;
+ border-radius: 8px;
+ font-weight: 500;
+}
+
+.appeal-status-label.approved {
+ background: rgba(67, 160, 71, 0.1);
+ color: #2e7d32;
+}
+
+.appeal-status-label.pending {
+ background: rgba(229, 57, 53, 0.1);
+ color: #c62828;
+}
+
+/* 主内容区域 */
+.appeal-main-content {
+ flex: 1;
+ padding: 40px 50px;
+ position: relative;
+ z-index: 10;
+}
+
+/* 详情标题 */
+.appeal-detail-title {
+ margin-bottom: 30px;
+ color: #fff;
+ font-family: 'Playfair Display', serif;
+ font-size: 2.2rem;
+ font-weight: 700;
+ text-shadow: 0 4px 12px rgba(45, 80, 22, 0.4);
+ letter-spacing: 1px;
+}
+
+/* 详情卡片 */
+.appeal-detail-card {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(20px);
+ border-radius: 25px;
+ padding: 35px 40px;
+ margin-bottom: 35px;
+ box-shadow:
+ 0 20px 60px rgba(45, 80, 22, 0.12),
+ 0 8px 25px rgba(144, 238, 144, 0.08),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ position: relative;
+ overflow: hidden;
+}
+
+.appeal-detail-card::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ background: linear-gradient(45deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #f0fff0 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 27px;
+ z-index: -1;
+ animation: borderGlow 4s ease-in-out infinite;
+}
+
+/* 详情项目 */
+.appeal-detail-item {
+ margin-bottom: 25px;
+ font-size: 16px;
+ line-height: 1.6;
+ color: #2d5016;
+}
+
+.appeal-detail-item b {
+ color: #1a5c1a;
+ font-weight: 700;
+ margin-right: 8px;
+}
+
+/* 文件查看器样式 */
+.file-viewer-container {
+ margin-top: 15px;
+ padding: 20px;
+ background: rgba(144, 238, 144, 0.05);
+ border-radius: 15px;
+ border: 1px solid rgba(144, 238, 144, 0.2);
+}
+
+.file-viewer-iframe {
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ border-radius: 12px;
+ width: 100%;
+ height: 400px;
+ transition: all 0.3s ease;
+}
+
+.file-viewer-iframe:hover {
+ border-color: rgba(144, 238, 144, 0.5);
+ box-shadow: 0 4px 15px rgba(144, 238, 144, 0.2);
+}
+
+.file-download-link {
+ color: #2d5016;
+ text-decoration: none;
+ font-weight: 600;
+ padding: 10px 20px;
+ background: rgba(144, 238, 144, 0.1);
+ border-radius: 10px;
+ display: inline-block;
+ transition: all 0.3s ease;
+ border: 1px solid rgba(144, 238, 144, 0.3);
+}
+
+.file-download-link:hover {
+ background: rgba(144, 238, 144, 0.2);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(144, 238, 144, 0.3);
+}
+
+/* 按钮容器 */
+.appeal-buttons-container {
+ display: flex;
+ gap: 35px;
+ justify-content: center;
+ margin-top: 40px;
+}
+
+/* 审核按钮样式 */
+.appeal-btn {
+ border: none;
+ border-radius: 15px;
+ padding: 15px 40px;
+ font-weight: 700;
+ font-size: 18px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-family: 'Lora', serif;
+ letter-spacing: 1px;
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+}
+
+.appeal-btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ transition: all 0.3s ease;
+ transform: translate(-50%, -50%);
+}
+
+.appeal-btn:hover::before {
+ width: 120px;
+ height: 120px;
+}
+
+.appeal-btn:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 15px 40px rgba(0, 0, 0, 0.2);
+}
+
+/* 通过按钮 */
+.appeal-btn-approve {
+ background: linear-gradient(135deg, #43a047, #2e7d32);
+ color: white;
+}
+
+.appeal-btn-approve:disabled {
+ background: linear-gradient(135deg, #bdbdbd, #9e9e9e);
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.appeal-btn-approve:disabled:hover {
+ transform: none;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+/* 拒绝按钮 */
+.appeal-btn-reject {
+ background: linear-gradient(135deg, #e53935, #c62828);
+ color: white;
+}
+
+.appeal-btn-reject:disabled {
+ background: linear-gradient(135deg, #bdbdbd, #9e9e9e);
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.appeal-btn-reject:disabled:hover {
+ transform: none;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+/* 加载和错误状态 */
+.appeal-loading,
+.appeal-error {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 100vh;
+ font-size: 24px;
+ font-weight: 600;
+ color: #fff;
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+}
+
+.appeal-error {
+ color: #ffcdd2;
+}
+
+/* 动画效果 */
+@keyframes backgroundShift {
+ 0%, 100% { transform: scale(1) rotate(0deg); }
+ 50% { transform: scale(1.1) rotate(1deg); }
+}
+
+@keyframes borderGlow {
+ 0%, 100% { opacity: 0.6; }
+ 50% { opacity: 1; }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .appeal-page-container {
+ flex-direction: column;
+ }
+
+ .appeal-sidebar {
+ width: 100%;
+ border-right: none;
+ border-bottom: 3px solid rgba(144, 238, 144, 0.3);
+ }
+
+ .appeal-list-container {
+ flex-direction: row;
+ overflow-x: auto;
+ padding: 0 15px 20px 15px;
+ }
+
+ .appeal-list-item {
+ min-width: 200px;
+ flex-shrink: 0;
+ }
+
+ .appeal-main-content {
+ padding: 20px 25px;
+ }
+
+ .appeal-detail-title {
+ font-size: 1.8rem;
+ }
+
+ .appeal-detail-card {
+ padding: 25px 30px;
+ }
+
+ .appeal-buttons-container {
+ gap: 20px;
+ flex-wrap: wrap;
+ }
+
+ .appeal-btn {
+ padding: 12px 30px;
+ font-size: 16px;
+ }
+}
+
+@media (max-width: 480px) {
+ .appeal-sidebar-title {
+ font-size: 1.2rem;
+ padding: 20px 15px 15px 15px;
+ }
+
+ .appeal-main-content {
+ padding: 15px 20px;
+ }
+
+ .appeal-detail-title {
+ font-size: 1.5rem;
+ margin-bottom: 20px;
+ }
+
+ .appeal-detail-card {
+ padding: 20px 25px;
+ margin-bottom: 25px;
+ }
+
+ .appeal-detail-item {
+ font-size: 15px;
+ margin-bottom: 20px;
+ }
+
+ .appeal-buttons-container {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .appeal-btn {
+ width: 100%;
+ max-width: 300px;
+ padding: 12px 20px;
+ font-size: 16px;
+ }
+}
diff --git a/front/src/AppealPage.js b/front/src/AppealPage.js
index dfbe307..0f168c3 100644
--- a/front/src/AppealPage.js
+++ b/front/src/AppealPage.js
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from "react";
import { API_BASE_URL } from "./config";
+import "./AppealPage.css";
// State for appeals fetched from backend
export default function AppealPage() {
@@ -30,8 +31,8 @@
fetchAppeals();
}, []);
- if (loading) return <div>加载中...</div>;
- if (error) return <div>加载失败:{error}</div>;
+ if (loading) return <div className="appeal-loading">加载中...</div>;
+ if (error) return <div className="appeal-error">加载失败:{error}</div>;
const selectedAppeal = appeals.find(a => a.appealid === selectedId) || {};
// Approve selected appeal and refresh
@@ -66,34 +67,25 @@
};
return (
- <div style={{ display: "flex", minHeight: "100vh", background: "#f7faff" }}>
+ <div className="appeal-page-container">
{/* 侧栏 */}
- <div style={{ width: 180, background: "#fff", borderRight: "1px solid #e0e7ff", padding: 0 }}>
- <h3 style={{ textAlign: "center", padding: "18px 0 0 0", color: "#1976d2" }}>申诉列表</h3>
- <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
+ <div className="appeal-sidebar">
+ <h3 className="appeal-sidebar-title">申诉列表</h3>
+ <div className="appeal-list-container">
{appeals.map(a => (
<div
key={a.appealid}
onClick={() => setSelectedId(a.appealid)}
- style={{
- margin: "0 12px",
- padding: "16px 10px",
- borderRadius: 8,
- background: selectedId === a.appealid ? "#e3f2fd" : "#fff",
- border: `2px solid ${a.status === 1 || a.status === 2 ? "#43a047" : "#e53935"}`,
- color: a.status === 1 || a.status === 2 ? "#43a047" : "#e53935",
- fontWeight: 600,
- cursor: "pointer",
- boxShadow: selectedId === a.appealid ? "0 2px 8px #b2d8ea" : "none",
- transition: "all 0.2s"
- }}
+ className={`appeal-list-item ${
+ selectedId === a.appealid ? 'selected' : ''
+ } ${
+ a.status === 1 || a.status === 2 ? 'approved' : 'pending'
+ }`}
>
{a.user.username}
- <span style={{
- float: "right",
- fontSize: 12,
- color: a.status === 1 || a.status === 2 ? "#43a047" : "#e53935"
- }}>
+ <span className={`appeal-status-label ${
+ a.status === 1 || a.status === 2 ? 'approved' : 'pending'
+ }`}>
{a.status === 1 || a.status === 2 ? "已审核" : "未审核"}
</span>
</div>
@@ -101,52 +93,34 @@
</div>
</div>
{/* 申诉详情 */}
- <div style={{ flex: 1, padding: "40px 48px" }}>
- <h2 style={{ marginBottom: 24, color: "#1976d2" }}>申诉详情</h2>
- <div style={{ background: "#fff", borderRadius: 12, padding: 32, boxShadow: "0 2px 8px #e0e7ff", marginBottom: 32 }}>
- <div style={{ marginBottom: 18 }}>
+ <div className="appeal-main-content">
+ <h2 className="appeal-detail-title">申诉详情</h2>
+ <div className="appeal-detail-card">
+ <div className="appeal-detail-item">
<b>申诉ID:</b>{selectedAppeal.appealid}
</div>
- <div style={{ marginBottom: 18 }}>
+ <div className="appeal-detail-item">
<b>用户ID:</b>{selectedAppeal.user.userid}
</div>
- <div style={{ marginBottom: 18 }}>
+ <div className="appeal-detail-item">
<b>申诉内容:</b>{selectedAppeal.content}
</div>
- <div style={{ marginBottom: 18 }}>
+ <div className="appeal-detail-item">
<b>申诉文件:</b>
<FileViewer url={selectedAppeal.fileURL} />
</div>
</div>
{/* 审核按钮 */}
- <div style={{ display: "flex", gap: 32, justifyContent: "center" }}>
+ <div className="appeal-buttons-container">
<button
- style={{
- background: selectedAppeal.status === 1 ? "#bdbdbd" : "#43a047",
- color: "#fff",
- border: "none",
- borderRadius: 8,
- padding: "10px 38px",
- fontWeight: 600,
- fontSize: 18,
- cursor: selectedAppeal.status === 1 ? "not-allowed" : "pointer"
- }}
+ className={`appeal-btn appeal-btn-approve`}
disabled={selectedAppeal.status === 1}
onClick={handleApprove}
>
通过
</button>
<button
- style={{
- background: selectedAppeal.status === 1 ? "#bdbdbd" : "#e53935",
- color: "#fff",
- border: "none",
- borderRadius: 8,
- padding: "10px 38px",
- fontWeight: 600,
- fontSize: 18,
- cursor: selectedAppeal.status === 1 ? "not-allowed" : "pointer"
- }}
+ className={`appeal-btn appeal-btn-reject`}
disabled={selectedAppeal.status === 1}
onClick={handleReject}
>
@@ -163,15 +137,15 @@
if (!url) return <div>无附件</div>;
if (url.endsWith(".pdf")) {
return (
- <iframe
- src={url}
- title="PDF预览"
- width="100%"
- height="400px"
- style={{ border: "1px solid #ccc", borderRadius: 8 }}
- />
+ <div className="file-viewer-container">
+ <iframe
+ src={url}
+ title="PDF预览"
+ className="file-viewer-iframe"
+ />
+ </div>
);
}
// 这里只做PDF示例,实际可扩展为DOC等
- return <a href={url} target="_blank" rel="noopener noreferrer">下载附件</a>;
+ return <a href={url} target="_blank" rel="noopener noreferrer" className="file-download-link">下载附件</a>;
}
\ No newline at end of file
diff --git a/front/src/BegSeedPage.css b/front/src/BegSeedPage.css
new file mode 100644
index 0000000..710ba47
--- /dev/null
+++ b/front/src/BegSeedPage.css
@@ -0,0 +1,460 @@
+/* 翡翠庄园 - 求种页面欧式园林风格样式 */
+
+/* 引入Google字体 */
+@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Lora:wght@400;500;600&display=swap');
+
+/* 页面基础容器样式 */
+.begseed-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 20%, #8fbc8f 40%, #98fb98 60%, #f0fff0 100%);
+ position: relative;
+ font-family: 'Lora', serif;
+ overflow-x: hidden;
+}
+
+/* 背景装饰元素 */
+.begseed-container::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.06) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(144, 238, 144, 0.08) 0%, transparent 50%);
+ animation: backgroundShift 25s ease-in-out infinite;
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* 内容区域 */
+.begseed-content {
+ position: relative;
+ z-index: 10;
+ padding: 20px;
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+/* 页面标题 */
+.begseed-title {
+ text-align: center;
+ margin: 80px 0 40px 0;
+ color: #2d5016;
+ font-family: 'Playfair Display', serif;
+ font-size: 42px;
+ font-weight: 700;
+ letter-spacing: 2px;
+ text-shadow: 0 2px 4px rgba(45, 80, 22, 0.2);
+ animation: titleFloat 6s ease-in-out infinite;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 15px;
+}
+
+.begseed-title-icon {
+ color: #90ee90;
+ filter: drop-shadow(0 2px 4px rgba(144, 238, 144, 0.4));
+ animation: iconPulse 3s ease-in-out infinite;
+}
+
+/* 发布按钮 */
+.begseed-publish-section {
+ text-align: center;
+ margin-bottom: 40px;
+}
+
+.begseed-publish-btn {
+ font-size: 18px;
+ padding: 16px 40px;
+ background: linear-gradient(135deg, #90ee90 0%, #2d5016 100%);
+ color: white;
+ border: none;
+ border-radius: 25px;
+ font-weight: 600;
+ font-family: 'Lora', serif;
+ box-shadow:
+ 0 8px 25px rgba(45, 80, 22, 0.3),
+ 0 4px 12px rgba(144, 238, 144, 0.2);
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ letter-spacing: 1px;
+}
+
+.begseed-publish-btn:hover {
+ transform: translateY(-3px) scale(1.02);
+ box-shadow:
+ 0 12px 35px rgba(45, 80, 22, 0.4),
+ 0 6px 18px rgba(144, 238, 144, 0.3);
+}
+
+.begseed-publish-btn:disabled {
+ background: linear-gradient(135deg, #a0a0a0 0%, #808080 100%);
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: 0 4px 12px rgba(128, 128, 128, 0.2);
+}
+
+.begseed-publish-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+ transition: left 0.6s ease;
+}
+
+.begseed-publish-btn:hover::before {
+ left: 100%;
+}
+
+/* 发布表单 */
+.begseed-form-container {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(20px);
+ border-radius: 25px;
+ padding: 40px;
+ max-width: 800px;
+ margin: 0 auto 40px auto;
+ box-shadow:
+ 0 20px 60px rgba(45, 80, 22, 0.15),
+ 0 8px 25px rgba(144, 238, 144, 0.1),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ position: relative;
+ animation: containerFloat 8s ease-in-out infinite;
+}
+
+.begseed-form-container::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ background: linear-gradient(45deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #f0fff0 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 27px;
+ z-index: -1;
+ animation: borderGlow 4s ease-in-out infinite;
+}
+
+.begseed-form-title {
+ color: #2d5016;
+ font-family: 'Playfair Display', serif;
+ font-size: 28px;
+ font-weight: 600;
+ margin-bottom: 30px;
+ text-align: center;
+}
+
+.begseed-form-group {
+ margin-bottom: 25px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.begseed-form-label {
+ color: #2d5016;
+ font-weight: 600;
+ font-size: 16px;
+ font-family: 'Lora', serif;
+ letter-spacing: 0.5px;
+}
+
+.begseed-form-input {
+ padding: 15px 20px;
+ border-radius: 15px;
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ background: rgba(255, 255, 255, 0.9);
+ font-size: 16px;
+ font-family: 'Lora', serif;
+ transition: all 0.3s ease;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.begseed-form-input:focus {
+ outline: none;
+ border-color: #90ee90;
+ background: rgba(255, 255, 255, 1);
+ box-shadow: 0 0 0 3px rgba(144, 238, 144, 0.2);
+ transform: translateY(-2px);
+}
+
+.begseed-form-actions {
+ display: flex;
+ gap: 20px;
+ justify-content: center;
+ margin-top: 35px;
+}
+
+.begseed-form-btn {
+ padding: 12px 30px;
+ border: none;
+ border-radius: 20px;
+ font-size: 16px;
+ font-weight: 600;
+ font-family: 'Lora', serif;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ min-width: 120px;
+}
+
+.begseed-form-btn-primary {
+ background: linear-gradient(135deg, #4a7c59 0%, #2d5016 100%);
+ color: white;
+ box-shadow: 0 4px 15px rgba(45, 80, 22, 0.3);
+}
+
+.begseed-form-btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(45, 80, 22, 0.4);
+}
+
+.begseed-form-btn-secondary {
+ background: linear-gradient(135deg, #999 0%, #777 100%);
+ color: white;
+ box-shadow: 0 4px 15px rgba(119, 119, 119, 0.3);
+}
+
+.begseed-form-btn-secondary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(119, 119, 119, 0.4);
+}
+
+/* 加载和错误状态 */
+.begseed-loading {
+ text-align: center;
+ margin: 60px 0;
+ color: #4a7c59;
+ font-size: 18px;
+ font-weight: 500;
+}
+
+.begseed-error {
+ text-align: center;
+ margin: 30px auto;
+ padding: 20px;
+ max-width: 600px;
+ background: rgba(255, 235, 238, 0.9);
+ color: #c62828;
+ border-radius: 15px;
+ border: 2px solid rgba(198, 40, 40, 0.2);
+ font-weight: 500;
+}
+
+/* 求种列表 */
+.begseed-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
+ gap: 30px;
+ margin-top: 20px;
+}
+
+/* 求种卡片 */
+.begseed-card {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(15px);
+ border-radius: 20px;
+ padding: 30px;
+ box-shadow:
+ 0 15px 40px rgba(45, 80, 22, 0.12),
+ 0 6px 20px rgba(144, 238, 144, 0.08);
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+}
+
+.begseed-card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(144, 238, 144, 0.1), transparent);
+ transition: left 0.5s;
+ z-index: 0;
+}
+
+.begseed-card:hover::before {
+ left: 100%;
+}
+
+.begseed-card:hover {
+ transform: translateY(-8px);
+ box-shadow:
+ 0 25px 60px rgba(45, 80, 22, 0.18),
+ 0 10px 30px rgba(144, 238, 144, 0.12);
+ border-color: rgba(74, 124, 89, 0.4);
+}
+
+.begseed-card-content {
+ position: relative;
+ z-index: 1;
+}
+
+/* 已过期/已完成的卡片 */
+.begseed-card-expired {
+ background: rgba(245, 245, 245, 0.9);
+ color: #888;
+ opacity: 0.7;
+}
+
+.begseed-card-expired:hover {
+ transform: translateY(-4px);
+ opacity: 0.8;
+}
+
+.begseed-card-title {
+ font-weight: 600;
+ font-size: 20px;
+ margin-bottom: 20px;
+ color: #2d5016;
+ font-family: 'Playfair Display', serif;
+ line-height: 1.3;
+}
+
+.begseed-card-expired .begseed-card-title {
+ color: #888;
+}
+
+.begseed-card-info {
+ margin-bottom: 12px;
+ color: #4a7c59;
+ font-size: 15px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.begseed-card-expired .begseed-card-info {
+ color: #999;
+}
+
+.begseed-card-info-icon {
+ font-size: 16px;
+ opacity: 0.8;
+}
+
+.begseed-card-status {
+ display: inline-block;
+ padding: 6px 12px;
+ border-radius: 12px;
+ font-size: 13px;
+ font-weight: 600;
+ margin-top: 15px;
+}
+
+.begseed-status-active {
+ background: linear-gradient(135deg, #e8f5e8 0%, #d4edda 100%);
+ color: #2d5016;
+ border: 1px solid rgba(45, 80, 22, 0.2);
+}
+
+.begseed-status-completed {
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+ color: #1565c0;
+ border: 1px solid rgba(21, 101, 192, 0.2);
+}
+
+.begseed-status-expired {
+ background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
+ color: #666;
+ border: 1px solid rgba(102, 102, 102, 0.2);
+}
+
+/* 动画效果 */
+@keyframes backgroundShift {
+ 0%, 100% { transform: translate(0, 0) rotate(0deg); }
+ 33% { transform: translate(30px, -30px) rotate(1deg); }
+ 66% { transform: translate(-20px, 20px) rotate(-0.5deg); }
+}
+
+@keyframes titleFloat {
+ 0%, 100% { transform: translateY(0px); }
+ 50% { transform: translateY(-10px); }
+}
+
+@keyframes containerFloat {
+ 0%, 100% { transform: translateY(0px); }
+ 50% { transform: translateY(-5px); }
+}
+
+@keyframes borderGlow {
+ 0%, 100% { opacity: 0.3; }
+ 50% { opacity: 0.6; }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .begseed-container {
+ padding: 20px 10px;
+ }
+
+ .begseed-title {
+ font-size: 32px;
+ margin-bottom: 30px;
+ }
+
+ .begseed-list {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ }
+
+ .begseed-form-container {
+ padding: 30px 20px;
+ margin: 0 10px 30px 10px;
+ }
+
+ .begseed-form-group {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+
+ .begseed-form-label {
+ width: auto;
+ }
+
+ .begseed-form-actions {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .begseed-card {
+ padding: 25px 20px;
+ }
+}
+
+@media (max-width: 480px) {
+ .begseed-title {
+ font-size: 28px;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .begseed-publish-btn {
+ padding: 14px 30px;
+ font-size: 16px;
+ }
+
+ .begseed-form-title {
+ font-size: 24px;
+ }
+}
diff --git a/front/src/BegSeedPage.js b/front/src/BegSeedPage.js
index 0854535..0c1a0bb 100644
--- a/front/src/BegSeedPage.js
+++ b/front/src/BegSeedPage.js
@@ -1,7 +1,12 @@
import React, { useState, useEffect } from "react";
import HelpIcon from "@mui/icons-material/Help";
+import PeopleIcon from "@mui/icons-material/People";
+import MonetizationOnIcon from "@mui/icons-material/MonetizationOn";
+import AccessTimeIcon from "@mui/icons-material/AccessTime";
+import CheckCircleIcon from "@mui/icons-material/CheckCircle";
import { useNavigate } from "react-router-dom";
import { API_BASE_URL } from "./config";
+import "./BegSeedPage.css";
export default function BegSeedPage() {
const navigate = useNavigate();
@@ -153,195 +158,142 @@
};
return (
- <div className="container">
- <h1 style={{ margin: "24px 0 32px 0", color: "#1976d2" }}>
- <HelpIcon style={{ verticalAlign: "middle", marginRight: 8 }} />
- 求种列表
- </h1>
-
- {/* 加载状态 */}
- {loading && (
- <div style={{ textAlign: "center", margin: "40px 0", color: "#666" }}>
- 正在加载求种列表...
+ <div className="begseed-container">
+ <div className="begseed-content">
+ <h1 className="begseed-title">
+ <HelpIcon className="begseed-title-icon" style={{ fontSize: 42 }} />
+ 求种列表
+ </h1>
+
+ {/* 加载状态 */}
+ {loading && (
+ <div className="begseed-loading">
+ 正在加载求种列表...
+ </div>
+ )}
+
+ {/* 错误状态 */}
+ {error && (
+ <div className="begseed-error">
+ 加载失败: {error} (已显示默认数据)
+ </div>
+ )}
+
+ <div className="begseed-publish-section">
+ <button
+ onClick={() => setShowForm(true)}
+ disabled={loading}
+ className="begseed-publish-btn"
+ >
+ 发布求种任务
+ </button>
</div>
- )}
-
- {/* 错误状态 */}
- {error && (
- <div style={{
- textAlign: "center",
- margin: "20px 0",
- padding: "10px",
- background: "#ffebee",
- color: "#c62828",
- borderRadius: "4px"
- }}>
- 加载失败: {error} (已显示默认数据)
- </div>
- )}
-
- <div style={{ margin: "0 0 32px 0", textAlign: "center" }}>
- <button
- onClick={() => setShowForm(true)}
- disabled={loading}
- style={{
- fontSize: 18,
- padding: "12px 36px",
- background: loading ? "#ccc" : "linear-gradient(90deg, #42a5f5 0%, #1976d2 100%)",
- color: "#fff",
- border: "none",
- borderRadius: 8,
- fontWeight: 600,
- boxShadow: "0 2px 8px #b2d8ea",
- cursor: loading ? "not-allowed" : "pointer",
- transition: "background 0.2s",
- }}
- >
- 发布求种任务
- </button>
- </div>
- {showForm && (
- <div
- style={{
- background: "#fff",
- border: "1.5px solid #b2d8ea",
- borderRadius: 12,
- padding: 24,
- maxWidth: 480,
- margin: "0 auto 32px auto",
- boxShadow: "0 2px 8px #e0e7ff",
- }}
- >
- <h3 style={{ color: "#1976d2", marginBottom: 18 }}>发布求种任务</h3>
- <form onSubmit={handleSubmit}>
- <div style={{ marginBottom: 16 }}>
- <label style={{ display: "inline-block", width: 80 }}>求种信息:</label>
- <input
- type="text"
- name="info"
- value={formData.info}
- onChange={handleFormChange}
- required
- style={{
- padding: "6px 12px",
- borderRadius: 6,
- border: "1px solid #b2d8ea",
- width: 260,
- }}
- />
- </div>
- <div style={{ marginBottom: 16 }}>
- <label style={{ display: "inline-block", width: 80 }}>悬赏魔力值:</label>
- <input
- type="number"
- name="reward_magic"
- value={formData.reward_magic}
- onChange={handleFormChange}
- required
- min={1}
- style={{
- padding: "6px 12px",
- borderRadius: 6,
- border: "1px solid #b2d8ea",
- width: 120,
- }}
- />
- </div>
- <div style={{ marginBottom: 16 }}>
- <label style={{ display: "inline-block", width: 80 }}>截止日期:</label>
- <input
- type="datetime-local"
- name="deadline"
- value={formData.deadline}
- onChange={handleFormChange}
- required
- style={{
- padding: "6px 12px",
- borderRadius: 6,
- border: "1px solid #b2d8ea",
- width: 200,
- }}
- />
- </div>
- <div style={{ marginTop: 18 }}>
- <button
- type="submit"
- style={{
- background: "#1976d2",
- color: "#fff",
- border: "none",
- borderRadius: 6,
- padding: "8px 28px",
- fontWeight: 500,
- fontSize: 16,
- marginRight: 18,
- cursor: "pointer",
- }}
+
+ {showForm && (
+ <div className="begseed-form-container">
+ <h3 className="begseed-form-title">发布求种任务</h3>
+ <form onSubmit={handleSubmit}>
+ <div className="begseed-form-group">
+ <label className="begseed-form-label">求种信息:</label>
+ <input
+ type="text"
+ name="info"
+ value={formData.info}
+ onChange={handleFormChange}
+ required
+ className="begseed-form-input"
+ placeholder="请输入您要求种的资源信息"
+ />
+ </div>
+ <div className="begseed-form-group">
+ <label className="begseed-form-label">悬赏魔力值:</label>
+ <input
+ type="number"
+ name="reward_magic"
+ value={formData.reward_magic}
+ onChange={handleFormChange}
+ required
+ min={1}
+ className="begseed-form-input"
+ placeholder="请输入悬赏的魔力值"
+ />
+ </div>
+ <div className="begseed-form-group">
+ <label className="begseed-form-label">截止日期:</label>
+ <input
+ type="datetime-local"
+ name="deadline"
+ value={formData.deadline}
+ onChange={handleFormChange}
+ required
+ className="begseed-form-input"
+ />
+ </div>
+ <div className="begseed-form-actions">
+ <button type="submit" className="begseed-form-btn begseed-form-btn-primary">
+ 提交
+ </button>
+ <button
+ type="button"
+ onClick={() => setShowForm(false)}
+ className="begseed-form-btn begseed-form-btn-secondary"
+ >
+ 取消
+ </button>
+ </div>
+ </form>
+ </div>
+ )}
+
+ <div key={refreshKey} className="begseed-list">
+ {begSeedList.map((beg) => {
+ const isExpired = new Date(beg.deadline) < now || beg.has_match === 1;
+ const isCompleted = beg.has_match === 1;
+ const isActive = !isExpired && !isCompleted;
+
+ return (
+ <div
+ key={beg.beg_id}
+ className={`begseed-card ${isExpired ? 'begseed-card-expired' : ''}`}
+ onClick={() => navigate(`/begseed/${beg.beg_id}`)}
>
- 提交
- </button>
- <button
- type="button"
- onClick={() => setShowForm(false)}
- style={{
- background: "#b0b0b0",
- color: "#fff",
- border: "none",
- borderRadius: 6,
- padding: "8px 28px",
- fontWeight: 500,
- fontSize: 16,
- cursor: "pointer",
- }}
- >
- 取消
- </button>
- </div>
- </form>
+ <div className="begseed-card-content">
+ <div className="begseed-card-title">
+ {beg.info}
+ </div>
+
+ <div className="begseed-card-info">
+ <PeopleIcon className="begseed-card-info-icon" />
+ 求种人数:{beg.beg_count}
+ </div>
+
+ <div className="begseed-card-info">
+ <MonetizationOnIcon className="begseed-card-info-icon" />
+ 悬赏魔力值:{beg.reward_magic}
+ </div>
+
+ <div className="begseed-card-info">
+ <AccessTimeIcon className="begseed-card-info-icon" />
+ 截止时间:{new Date(beg.deadline).toLocaleString()}
+ </div>
+
+ <div className="begseed-card-info">
+ <CheckCircleIcon className="begseed-card-info-icon" />
+ <span
+ className={`begseed-card-status ${
+ isCompleted ? 'begseed-status-completed' :
+ isExpired ? 'begseed-status-expired' :
+ 'begseed-status-active'
+ }`}
+ >
+ {isCompleted ? "已完成" : isExpired ? "已过期" : "进行中"}
+ </span>
+ </div>
+ </div>
+ </div>
+ );
+ })}
</div>
- )}
- <div key={refreshKey} style={{ display: "flex", flexWrap: "wrap", gap: 24 }}>
- {begSeedList.map((beg) => {
- const isExpired =
- new Date(beg.deadline) < now || beg.has_match === 1;
- return (
- <div
- key={beg.beg_id}
- style={{
- background: isExpired ? "#f0f0f0" : "#e3f7e7",
- color: isExpired ? "#b0b0b0" : "#222",
- border: "1.5px solid #b2d8ea",
- borderRadius: 12,
- padding: 18,
- minWidth: 320,
- maxWidth: 420,
- boxShadow: "0 2px 8px #e0e7ff",
- opacity: isExpired ? 0.6 : 1,
- cursor: "pointer",
- marginBottom: 12,
- transition: "box-shadow 0.2s",
- }}
- onClick={() => navigate(`/begseed/${beg.beg_id}`)}
- >
- <div style={{ fontWeight: 600, fontSize: 18, marginBottom: 8 }}>
- {beg.info}
- </div>
- <div>求种人数:{beg.beg_count}</div>
- <div>悬赏魔力值:{beg.reward_magic}</div>
- <div>
- 截止时间:{new Date(beg.deadline).toLocaleString()}
- </div>
- <div>
- 状态:
- {beg.has_match === 1
- ? "已完成"
- : new Date(beg.deadline) < now
- ? "已过期"
- : "进行中"}
- </div>
- </div>
- );
- })}
</div>
</div>
);
diff --git a/front/src/MigrationPage.css b/front/src/MigrationPage.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/front/src/MigrationPage.css
diff --git a/front/src/PublishPage.js b/front/src/PublishPage.js
index df03cae..d9f7db9 100644
--- a/front/src/PublishPage.js
+++ b/front/src/PublishPage.js
@@ -1,9 +1,47 @@
-import React, { useState } from 'react';
-import './App.css';
-import './PublishPage.css';
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import './SharedStyles.css';
import { API_BASE_URL } from "./config";
+import HomeIcon from "@mui/icons-material/Home";
+import MovieIcon from "@mui/icons-material/Movie";
+import TvIcon from "@mui/icons-material/Tv";
+import MusicNoteIcon from "@mui/icons-material/MusicNote";
+import AnimationIcon from "@mui/icons-material/Animation";
+import SportsEsportsIcon from "@mui/icons-material/SportsEsports";
+import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts";
+import PersonIcon from "@mui/icons-material/Person";
+import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import ForumIcon from "@mui/icons-material/Forum";
+import HelpIcon from "@mui/icons-material/Help";
+import CloudUploadIcon from "@mui/icons-material/CloudUpload";
+import PublishIcon from "@mui/icons-material/Publish";
+import CategoryIcon from "@mui/icons-material/Category";
+import TitleIcon from "@mui/icons-material/Title";
+import DescriptionIcon from "@mui/icons-material/Description";
+import CheckCircleIcon from "@mui/icons-material/CheckCircle";
+
+const navItems = [
+ { label: "首页", icon: <HomeIcon className="emerald-nav-icon" />, path: "/home", type: "home" },
+ { label: "电影", icon: <MovieIcon className="emerald-nav-icon" />, path: "/movie", type: "movie" },
+ { label: "剧集", icon: <TvIcon className="emerald-nav-icon" />, path: "/tv", type: "tv" },
+ { label: "音乐", icon: <MusicNoteIcon className="emerald-nav-icon" />, path: "/music", type: "music" },
+ { label: "动漫", icon: <AnimationIcon className="emerald-nav-icon" />, path: "/anime", type: "anime" },
+ { label: "游戏", icon: <SportsEsportsIcon className="emerald-nav-icon" />, path: "/game", type: "game" },
+ { label: "体育", icon: <SportsMartialArtsIcon className="emerald-nav-icon" />, path: "/sport", type: "sport" },
+ { label: "资料", icon: <PersonIcon className="emerald-nav-icon" />, path: "/info", type: "info" },
+ { label: "论坛", icon: <ForumIcon className="emerald-nav-icon" />, path: "/forum", type: "forum" },
+ { label: "发布", icon: <AccountCircleIcon className="emerald-nav-icon" />, path: "/publish", type: "publish" },
+ { label: "求种", icon: <HelpIcon className="emerald-nav-icon" />, path: "/begseed", type: "help" },
+];
+
+// 发布页面专用文字雨内容
+const publishTexts = [
+ "分享", "上传", "种子", "资源", "贡献", "精品", "原创", "高清",
+ "无损", "蓝光", "1080P", "4K", "HDR", "珍藏", "首发", "独家"
+];
const PublishPage = () => {
+ const navigate = useNavigate();
const [formData, setFormData] = useState({
type: '',
torrentFile: '',
@@ -11,6 +49,30 @@
subtitle: ''
});
const [subType, setSubType] = useState('');
+ const [userInfo, setUserInfo] = useState({ avatar_url: '', username: '' }); const [userPT, setUserPT] = useState({ magic: 0, ratio: 0, upload: 0, download: 0 });
+ const [uploadProgress, setUploadProgress] = useState(0);
+ const [isUploading, setIsUploading] = useState(false);
+ const [isDragOver, setIsDragOver] = useState(false);
+
+ useEffect(() => {
+ // 获取用户信息
+ const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+ const userId = match ? match[2] : null;
+ if (userId) {
+ fetch(`${API_BASE_URL}/api/get-userpt?userid=${encodeURIComponent(userId)}`)
+ .then(res => res.json())
+ .then(data => {
+ setUserInfo({ avatar_url: data.user.avatar_url, username: data.user.username });
+ setUserPT({
+ magic: data.magic_value || data.magic || 0,
+ ratio: data.share_ratio || data.share || 0,
+ upload: data.upload_amount || data.upload || 0,
+ download: data.download_amount || data.download || 0,
+ });
+ })
+ .catch(err => console.error('Fetching user profile failed', err));
+ }
+ }, []);
const typeOptions = {
'电影': ['大陆', '港台', '欧美', '日韩'],
@@ -26,17 +88,63 @@
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
-
const handleFileChange = (e) => {
const file = e.target.files[0];
- if (file && file.name.split('.').pop() !== 'torrent') {
+ validateAndSetFile(file);
+ };
+
+ const validateAndSetFile = (file) => {
+ if (file && file.name.split('.').pop().toLowerCase() !== 'torrent') {
alert('仅能上传.torrent类型文件');
- e.target.value = null;
- } else {
+ return false;
+ } else if (file) {
setFormData({ ...formData, torrentFile: file });
+ return true;
+ }
+ return false;
+ };
+
+ // 拖拽上传功能
+ const handleDragEnter = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDragOver(true);
+ };
+
+ const handleDragLeave = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ // 只有当鼠标真正离开拖拽区域时才设置为false
+ if (!e.currentTarget.contains(e.relatedTarget)) {
+ setIsDragOver(false);
}
};
+ const handleDragOver = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ };
+
+ const handleDrop = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setIsDragOver(false);
+
+ const files = e.dataTransfer.files;
+ if (files.length > 0) {
+ const file = files[0];
+ if (validateAndSetFile(file)) {
+ // 如果文件验证成功,清空文件输入框并重新设置
+ const fileInput = document.getElementById('torrentFile');
+ if (fileInput) {
+ // 创建一个新的FileList对象来模拟文件选择
+ const dt = new DataTransfer();
+ dt.items.add(file);
+ fileInput.files = dt.files;
+ }
+ }
+ }
+ };
const handleSubmit = async (e) => {
e.preventDefault();
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
@@ -46,6 +154,21 @@
alert('请上传.torrent文件');
return;
}
+
+ setIsUploading(true);
+ setUploadProgress(0);
+
+ // 模拟上传进度
+ const progressInterval = setInterval(() => {
+ setUploadProgress(prev => {
+ if (prev >= 90) {
+ clearInterval(progressInterval);
+ return prev;
+ }
+ return prev + Math.random() * 15;
+ });
+ }, 200);
+
const data = new FormData();
data.append('userid', userId);
data.append('title', formData.title);
@@ -58,84 +181,299 @@
method: 'POST',
body: data,
});
+
+ clearInterval(progressInterval);
+ setUploadProgress(100);
+
if (response.ok) {
- alert('上传成功!');
+ setTimeout(() => {
+ alert('上传成功!');
+ setIsUploading(false);
+ setUploadProgress(0);
+ // 重置表单
+ setFormData({
+ type: '',
+ torrentFile: '',
+ title: '',
+ subtitle: ''
+ });
+ setSubType('');
+ }, 500);
} else {
+ setIsUploading(false);
+ setUploadProgress(0);
alert('上传失败');
}
} catch (err) {
+ clearInterval(progressInterval);
+ setIsUploading(false);
+ setUploadProgress(0);
alert('网络错误');
}
};
-
return (
- <div className="publish-page">
- <h1 className="page-title">发布种子</h1>
- <form onSubmit={handleSubmit} className="publish-form">
- <div className="form-row">
- <label htmlFor="type">类型</label>
- <select name="type" id="type" value={formData.type} onChange={e => { handleChange(e); setSubType(''); }} required>
- <option value="">请选择类型</option>
- <option value="电影">电影</option>
- <option value="剧集">剧集</option>
- <option value="音乐">音乐</option>
- <option value="动漫">动漫</option>
- <option value="游戏">游戏</option>
- <option value="体育">体育</option>
- <option value="资料">资料</option>
- </select>
- </div>
- {formData.type && typeOptions[formData.type] && (
- <div className="form-row">
- <label htmlFor="subtype">具体类型</label>
- <select name="subtype" id="subtype" value={subType} onChange={e => setSubType(e.target.value)} required>
- <option value="">请选择具体类型</option>
- {typeOptions[formData.type].map(opt => (
- <option key={opt} value={opt}>{opt}</option>
- ))}
- </select>
+ <div className="emerald-home-container">
+ {/* 发布页面专用文字雨 */}
+ <div className="publish-text-rain">
+ {publishTexts.map((text, index) => (
+ <div key={index} className="text-drop" style={{
+ left: `${(index * 4) % 100}%`,
+ animationDelay: `${(index * 0.9) % 12}s`,
+ animationDuration: `${7 + (index % 6)}s`,
+ color: index % 3 === 0 ? '#90ee90' : index % 3 === 1 ? '#2d5016' : '#4a7c59'
+ }}>
+ {text}
</div>
- )}
+ ))}
+ </div>
- <div className="form-row">
- <label htmlFor="torrentFile">种子文件</label>
- <input
- type="file"
- id="torrentFile"
- name="torrentFile"
- onChange={handleFileChange}
- required
- />
- <span style={{ fontSize: '12px', color: '#666' }}>需上传.torrent类型文件</span>
+ {/* 浮动园林装饰元素 */}
+ <div className="floating-garden-elements">
+ <div className="garden-element">📤</div>
+ <div className="garden-element">🌟</div>
+ <div className="garden-element">💎</div>
+ <div className="garden-element">🎯</div>
+ </div>
+
+ <div className="emerald-content">
+ {/* NeuraFlux用户栏 */}
+ <div className="emerald-user-bar">
+ <div className="emerald-user-avatar" onClick={() => navigate('/user')}>
+ {userInfo.avatar_url ? (
+ <img src={userInfo.avatar_url} alt="用户头像" style={{ width: 38, height: 38, borderRadius: '50%', objectFit: 'cover' }} />
+ ) : (
+ <AccountCircleIcon style={{ fontSize: 38, color: 'white' }} />
+ )}
+ </div>
+ <div className="emerald-brand-section">
+ <div className="emerald-brand-icon">⚡</div>
+ <div className="emerald-user-label">NeuraFlux</div>
+ </div>
+ <div className="emerald-user-stats">
+ <span className="emerald-stat-item">
+ 魔力值: <span className="emerald-stat-value">{userPT.magic}</span>
+ </span>
+ <span className="emerald-stat-item">
+ 分享率: <span className="emerald-stat-value">{userPT.ratio}</span>
+ </span>
+ <span className="emerald-stat-item">
+ 上传: <span className="emerald-stat-value">{userPT.upload}GB</span>
+ </span>
+ <span className="emerald-stat-item">
+ 下载: <span className="emerald-stat-value">{userPT.download}GB</span>
+ </span>
+ </div>
</div>
- <div className="form-row">
- <label htmlFor="title">标题</label>
- <input
- type="text"
- id="title"
- name="title"
- value={formData.title}
- onChange={handleChange}
- required
- />
- </div>
+ {/* NeuraFlux导航栏 */}
+ <nav className="emerald-nav-bar">
+ {navItems.map((item) => (
+ <div
+ key={item.label}
+ className={`emerald-nav-item ${item.label === "发布" ? "active" : ""}`}
+ data-type={item.type}
+ onClick={() => navigate(item.path)}
+ >
+ {item.icon}
+ <span className="emerald-nav-label">{item.label}</span>
+ </div>
+ ))}
+ </nav>
- <div className="form-row">
- <label htmlFor="subtitle">种子简介</label>
- <input
- type="text"
- id="subtitle"
- name="subtitle"
- value={formData.subtitle}
- onChange={handleChange}
- />
- </div>
+ {/* 发布页面内容 */}
+ <div className="emerald-content-section">
+ <h1 className="emerald-page-title">
+ <PublishIcon style={{ marginRight: '15px', fontSize: '36px', color: '#2d5016' }} />
+ 🌟 NeuraFlux种子发布
+ </h1>
+ <p style={{ textAlign: 'center', color: '#2d5016', fontSize: '18px', marginBottom: '40px', fontStyle: 'italic' }}>
+ 分享优质资源,成就精彩社区 • 每一次上传都是对知识传承的贡献
+ </p>
- <div className="form-row submit-row">
- <button type="submit" className="submit-button">提交</button>
+ {/* 发布表单 */}
+ <div className="publish-form-container">
+ <form onSubmit={handleSubmit} className="publish-form-advanced">
+ {/* 资源类型选择 */}
+ <div className="form-section">
+ <div className="section-header">
+ <CategoryIcon style={{ color: '#2d5016', marginRight: '10px' }} />
+ <h3>资源分类</h3>
+ </div>
+ <div className="form-grid">
+ <div className="form-field">
+ <label htmlFor="type">主要类型</label>
+ <div className="select-wrapper">
+ <select
+ name="type"
+ id="type"
+ value={formData.type}
+ onChange={e => { handleChange(e); setSubType(''); }}
+ required
+ className="modern-select"
+ >
+ <option value="">请选择资源类型</option>
+ <option value="电影">🎬 电影</option>
+ <option value="剧集">📺 剧集</option>
+ <option value="音乐">🎵 音乐</option>
+ <option value="动漫">🎨 动漫</option>
+ <option value="游戏">🎮 游戏</option>
+ <option value="体育">⚽ 体育</option>
+ <option value="资料">📚 资料</option>
+ </select>
+ </div>
+ </div>
+
+ {formData.type && typeOptions[formData.type] && (
+ <div className="form-field">
+ <label htmlFor="subtype">具体分类</label>
+ <div className="select-wrapper">
+ <select
+ name="subtype"
+ id="subtype"
+ value={subType}
+ onChange={e => setSubType(e.target.value)}
+ required
+ className="modern-select"
+ >
+ <option value="">请选择具体分类</option>
+ {typeOptions[formData.type].map(opt => (
+ <option key={opt} value={opt}>{opt}</option>
+ ))}
+ </select>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+
+ {/* 种子文件上传 */}
+ <div className="form-section">
+ <div className="section-header">
+ <CloudUploadIcon style={{ color: '#2d5016', marginRight: '10px' }} />
+ <h3>种子文件</h3>
+ </div> <div
+ className={`file-upload-area ${isDragOver ? 'drag-over' : ''}`}
+ onDragEnter={handleDragEnter}
+ onDragLeave={handleDragLeave}
+ onDragOver={handleDragOver}
+ onDrop={handleDrop}
+ >
+ <input
+ type="file"
+ id="torrentFile"
+ name="torrentFile"
+ onChange={handleFileChange}
+ required
+ accept=".torrent"
+ className="file-input-hidden"
+ />
+ <label htmlFor="torrentFile" className="file-upload-label">
+ <CloudUploadIcon
+ style={{
+ fontSize: '48px',
+ color: isDragOver ? '#2d5016' : '#90ee90',
+ marginBottom: '10px',
+ transition: 'all 0.3s ease'
+ }}
+ />
+ <div className="upload-text">
+ <span className="upload-main-text">
+ {isDragOver ?
+ '松开鼠标完成上传' :
+ (formData.torrentFile ? formData.torrentFile.name : '点击选择或拖拽.torrent文件')
+ }
+ </span>
+ <span className="upload-sub-text">
+ {isDragOver ?
+ '🎯 即将上传文件到NeuraFlux' :
+ '✨ 支持拖拽上传 • 仅接受.torrent格式文件'
+ }
+ </span>
+ </div>
+ </label>
+ </div>
+ </div>
+
+ {/* 标题和描述 */}
+ <div className="form-section">
+ <div className="section-header">
+ <TitleIcon style={{ color: '#2d5016', marginRight: '10px' }} />
+ <h3>资源信息</h3>
+ </div>
+ <div className="form-field">
+ <label htmlFor="title">资源标题</label>
+ <input
+ type="text"
+ id="title"
+ name="title"
+ value={formData.title}
+ onChange={handleChange}
+ required
+ className="modern-input"
+ placeholder="请输入简洁明确的资源标题..."
+ />
+ </div>
+
+ <div className="form-field">
+ <label htmlFor="subtitle">
+ <DescriptionIcon style={{ marginRight: '5px', fontSize: '18px' }} />
+ 资源简介
+ </label>
+ <textarea
+ id="subtitle"
+ name="subtitle"
+ value={formData.subtitle}
+ onChange={handleChange}
+ className="modern-textarea"
+ rows="4"
+ placeholder="详细描述资源内容、质量、特色等信息..."
+ />
+ </div>
+ </div>
+
+ {/* 上传进度条 */}
+ {isUploading && (
+ <div className="upload-progress-section">
+ <div className="progress-header">
+ <span>正在上传资源...</span>
+ <span>{Math.round(uploadProgress)}%</span>
+ </div>
+ <div className="progress-bar">
+ <div
+ className="progress-fill"
+ style={{ width: `${uploadProgress}%` }}
+ />
+ </div>
+ </div>
+ )}
+
+ {/* 提交按钮 */}
+ <div className="form-submit-section">
+ <button
+ type="submit"
+ className="publish-submit-btn"
+ disabled={isUploading}
+ >
+ {isUploading ? (
+ <>
+ <div className="loading-spinner" />
+ 上传中...
+ </>
+ ) : (
+ <>
+ <CheckCircleIcon style={{ marginRight: '8px', fontSize: '20px' }} />
+ 发布资源
+ </>
+ )}
+ </button>
+ <div className="submit-tips">
+ 💡 请确保上传的资源合法合规,感谢您的贡献!
+ </div>
+ </div>
+ </form>
+ </div>
</div>
- </form>
+ </div>
</div>
);
};
diff --git a/front/src/SharedStyles.css b/front/src/SharedStyles.css
index 730a76e..49c13e6 100644
--- a/front/src/SharedStyles.css
+++ b/front/src/SharedStyles.css
@@ -1156,15 +1156,31 @@
transform: translateY(-1px);
}
-/* 动画定义 */
-@keyframes backgroundShift {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.7; }
+/* 发布页面专用样式 */
+.publish-text-rain {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ overflow: hidden;
+ z-index: 1;
}
-@keyframes meteorFall {
+.publish-text-rain .text-drop {
+ position: absolute;
+ font-size: 16px;
+ font-weight: 600;
+ animation: publishTextFall linear infinite;
+ opacity: 0.7;
+ filter: drop-shadow(0 2px 4px rgba(144, 238, 144, 0.3));
+ font-family: 'Lora', serif;
+}
+
+@keyframes publishTextFall {
0% {
- transform: translateY(-100vh) translateX(-50px) rotate(0deg);
+ transform: translateY(-100vh) rotate(0deg);
opacity: 0;
}
10% {
@@ -1174,246 +1190,638 @@
opacity: 0.8;
}
100% {
- transform: translateY(100vh) translateX(50px) rotate(360deg);
+ transform: translateY(100vh) rotate(360deg);
opacity: 0;
}
}
-@keyframes gardenFloat {
+.publish-form-container {
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+.publish-form-advanced {
+ background: rgba(255, 255, 255, 0.98);
+ border-radius: 25px;
+ padding: 40px;
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ position: relative;
+ overflow: hidden;
+ box-shadow: 0 20px 60px rgba(45, 80, 22, 0.12);
+}
+
+.publish-form-advanced::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ background: linear-gradient(45deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #f0fff0 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 27px;
+ z-index: -1;
+ animation: borderGlow 5s ease-in-out infinite;
+}
+
+.form-section {
+ margin-bottom: 35px;
+ padding: 25px;
+ background: rgba(240, 255, 240, 0.2);
+ border-radius: 20px;
+ border: 1px solid rgba(144, 238, 144, 0.2);
+ transition: all 0.3s ease;
+}
+
+.form-section:hover {
+ background: rgba(240, 255, 240, 0.3);
+ transform: translateY(-2px);
+ box-shadow: 0 8px 25px rgba(45, 80, 22, 0.1);
+}
+
+.section-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid rgba(144, 238, 144, 0.2);
+}
+
+.section-header h3 {
+ color: #2d5016;
+ font-size: 20px;
+ font-weight: 600;
+ margin: 0;
+ font-family: 'Playfair Display', serif;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 25px;
+}
+
+.form-field {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 20px;
+}
+
+.form-field label {
+ color: #2d5016;
+ font-weight: 600;
+ margin-bottom: 8px;
+ font-size: 16px;
+ display: flex;
+ align-items: center;
+ font-family: 'Lora', serif;
+}
+
+.select-wrapper {
+ position: relative;
+}
+
+.modern-select {
+ width: 100%;
+ padding: 15px 20px;
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ border-radius: 15px;
+ font-size: 16px;
+ background: rgba(255, 255, 255, 0.9);
+ color: #2d5016;
+ transition: all 0.3s ease;
+ appearance: none;
+ cursor: pointer;
+ font-family: 'Lora', serif;
+}
+
+.modern-select:focus {
+ outline: none;
+ border-color: #90ee90;
+ background: rgba(255, 255, 255, 1);
+ box-shadow: 0 0 0 3px rgba(144, 238, 144, 0.2);
+ transform: translateY(-2px);
+}
+
+.modern-select::after {
+ content: '▼';
+ position: absolute;
+ right: 15px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #90ee90;
+ pointer-events: none;
+}
+
+.modern-input {
+ width: 100%;
+ padding: 15px 20px;
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ border-radius: 15px;
+ font-size: 16px;
+ background: rgba(255, 255, 255, 0.9);
+ color: #2d5016;
+ transition: all 0.3s ease;
+ font-family: 'Lora', serif;
+ box-sizing: border-box;
+}
+
+.modern-input:focus {
+ outline: none;
+ border-color: #90ee90;
+ background: rgba(255, 255, 255, 1);
+ box-shadow: 0 0 0 3px rgba(144, 238, 144, 0.2);
+ transform: translateY(-2px);
+}
+
+.modern-textarea {
+ width: 100%;
+ padding: 15px 20px;
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ border-radius: 15px;
+ font-size: 16px;
+ background: rgba(255, 255, 255, 0.9);
+ color: #2d5016;
+ transition: all 0.3s ease;
+ resize: vertical;
+ min-height: 120px;
+ font-family: 'Lora', serif;
+ line-height: 1.6;
+ box-sizing: border-box;
+}
+
+.modern-textarea:focus {
+ outline: none;
+ border-color: #90ee90;
+ background: rgba(255, 255, 255, 1);
+ box-shadow: 0 0 0 3px rgba(144, 238, 144, 0.2);
+ transform: translateY(-2px);
+}
+
+/* 文件上传区域 */
+.file-upload-area {
+ border: 3px dashed rgba(144, 238, 144, 0.4);
+ border-radius: 20px;
+ padding: 40px;
+ text-align: center;
+ background: rgba(240, 255, 240, 0.3);
+ transition: all 0.3s ease;
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+}
+
+.file-upload-area:hover {
+ border-color: #90ee90;
+ background: rgba(240, 255, 240, 0.5);
+ transform: translateY(-3px);
+ box-shadow: 0 10px 30px rgba(45, 80, 22, 0.15);
+}
+
+/* 拖拽上传状态样式 */
+.file-upload-area.drag-over {
+ border-color: #2d5016 !important;
+ background: rgba(144, 238, 144, 0.2) !important;
+ transform: translateY(-5px) scale(1.02);
+ box-shadow:
+ 0 15px 40px rgba(45, 80, 22, 0.25),
+ 0 0 0 3px rgba(144, 238, 144, 0.3);
+ animation: dragPulse 1.5s ease-in-out infinite;
+}
+
+.file-upload-area.drag-over .upload-main-text {
+ color: #2d5016 !important;
+ font-weight: 700;
+ transform: scale(1.05);
+}
+
+.file-upload-area.drag-over .upload-sub-text {
+ color: #1a5c1a !important;
+ font-weight: 600;
+}
+
+@keyframes dragPulse {
0%, 100% {
- transform: translateY(0px) rotate(0deg);
- opacity: 0.4;
+ box-shadow:
+ 0 15px 40px rgba(45, 80, 22, 0.25),
+ 0 0 0 3px rgba(144, 238, 144, 0.3);
}
50% {
- transform: translateY(-15px) rotate(180deg);
- opacity: 0.6;
+ box-shadow:
+ 0 20px 50px rgba(45, 80, 22, 0.35),
+ 0 0 0 5px rgba(144, 238, 144, 0.4);
}
}
-@keyframes userBarFloat {
- 0%, 100% {
- transform: translateY(0px);
- }
- 50% {
- transform: translateY(-3px);
- }
+/* 拖拽时的全局遮罩效果 */
+.file-upload-area.drag-over::after {
+ content: '';
+ position: absolute;
+ top: -5px;
+ left: -5px;
+ right: -5px;
+ bottom: -5px;
+ border: 3px dashed #2d5016;
+ border-radius: 25px;
+ background: rgba(144, 238, 144, 0.1);
+ animation: dashRotate 2s linear infinite;
+ pointer-events: none;
}
-@keyframes borderGlow {
- 0%, 100% {
- opacity: 0.6;
- }
- 50% {
- opacity: 1;
- }
-}
-
-@keyframes iconDance {
- 0%, 100% {
- transform: scale(1) rotate(0deg);
+@keyframes dashRotate {
+ 0% {
+ border-color: #2d5016;
+ transform: rotate(0deg);
}
25% {
- transform: scale(1.1) rotate(5deg);
+ border-color: #90ee90;
}
50% {
- transform: scale(1.2) rotate(0deg);
+ border-color: #2d5016;
+ transform: rotate(180deg);
}
75% {
- transform: scale(1.1) rotate(-5deg);
+ border-color: #90ee90;
+ }
+ 100% {
+ border-color: #2d5016;
+ transform: rotate(360deg);
}
}
-@keyframes iconPulse {
+/* 文件上传区域增强过渡效果 */
+.file-upload-area {
+ position: relative;
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.file-upload-label {
+ transition: all 0.3s ease;
+ z-index: 10;
+ position: relative;
+}
+
+.upload-main-text,
+.upload-sub-text {
+ transition: all 0.3s ease;
+}
+
+/* 防止拖拽时的默认行为 */
+.file-upload-area * {
+ pointer-events: none;
+}
+
+.file-upload-area label {
+ pointer-events: auto;
+}
+
+.file-upload-area input {
+ pointer-events: auto;
+}
+
+/* 文件上传隐藏输入框 */
+.file-input-hidden {
+ display: none;
+}
+
+.file-upload-label {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ cursor: pointer;
+ padding: 20px;
+ border-radius: 15px;
+ transition: all 0.3s ease;
+}
+
+.upload-text {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.upload-main-text {
+ font-size: 18px;
+ font-weight: 600;
+ color: #2d5016;
+ transition: all 0.3s ease;
+ font-family: 'Playfair Display', serif;
+}
+
+.upload-sub-text {
+ font-size: 14px;
+ color: #4a7c59;
+ font-style: italic;
+ transition: all 0.3s ease;
+ text-align: center;
+ font-family: 'Lora', serif;
+}
+
+/* 上传进度区域样式 */
+.upload-progress-section {
+ margin: 30px 0;
+ padding: 25px;
+ background: rgba(144, 238, 144, 0.1);
+ border-radius: 20px;
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ animation: progressGlow 2s ease-in-out infinite;
+}
+
+@keyframes progressGlow {
0%, 100% {
- transform: scale(1);
+ border-color: rgba(144, 238, 144, 0.3);
+ box-shadow: 0 0 10px rgba(144, 238, 144, 0.1);
}
50% {
- transform: scale(1.1);
+ border-color: rgba(144, 238, 144, 0.5);
+ box-shadow: 0 0 20px rgba(144, 238, 144, 0.2);
+ }
+}
+
+.progress-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ font-family: 'Lora', serif;
+ font-weight: 600;
+ color: #2d5016;
+ font-size: 16px;
+}
+
+.progress-bar {
+ width: 100%;
+ height: 12px;
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 10px;
+ overflow: hidden;
+ border: 2px solid rgba(144, 238, 144, 0.3);
+ position: relative;
+}
+
+.progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #2d5016 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 8px;
+ transition: width 0.3s ease;
+ position: relative;
+ animation: progressShine 2s ease-in-out infinite;
+}
+
+@keyframes progressShine {
+ 0% {
+ background-position: -100% 0;
+ }
+ 100% {
+ background-position: 100% 0;
+ }
+}
+
+.progress-fill::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent 0%,
+ rgba(255, 255, 255, 0.4) 50%,
+ transparent 100%);
+ animation: progressShineEffect 2s ease-in-out infinite;
+}
+
+@keyframes progressShineEffect {
+ 0% {
+ left: -100%;
+ }
+ 100% {
+ left: 100%;
+ }
+}
+
+/* 表单提交区域样式 */
+.form-submit-section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ margin: 40px auto;
+ padding: 30px;
+ background: rgba(240, 255, 240, 0.3);
+ border-radius: 20px;
+ border: 2px dashed rgba(144, 238, 144, 0.4);
+ position: relative;
+ overflow: hidden;
+ max-width: 400px;
+}
+
+.form-submit-section::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(circle, rgba(144, 238, 144, 0.1) 0%, transparent 70%);
+ animation: submitAreaFloat 8s ease-in-out infinite;
+ pointer-events: none;
+}
+
+@keyframes submitAreaFloat {
+ 0%, 100% {
+ transform: translate(-50%, -50%) rotate(0deg);
+ }
+ 50% {
+ transform: translate(-50%, -50%) rotate(180deg);
+ }
+}
+
+/* 发布提交按钮样式 */
+.publish-submit-btn {
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 25%, #90ee90 50%, #4a7c59 75%, #2d5016 100%);
+ background-size: 300% 300%;
+ color: white;
+ border: none;
+ padding: 18px 45px;
+ font-size: 18px;
+ font-weight: 600;
+ border-radius: 25px;
+ cursor: pointer;
+ transition: all 0.4s ease;
+ position: relative;
+ overflow: hidden;
+ font-family: 'Playfair Display', serif;
+ letter-spacing: 1px;
+ box-shadow:
+ 0 8px 25px rgba(45, 80, 22, 0.3),
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
+ animation: buttonGradient 3s ease-in-out infinite;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 180px;
+ margin: 0 auto;
+ z-index: 10;
+}
+
+.publish-submit-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg,
+ transparent 0%,
+ rgba(255, 255, 255, 0.3) 50%,
+ transparent 100%);
+ transition: left 0.6s ease;
+}
+
+.publish-submit-btn:hover::before {
+ left: 100%;
+}
+
+.publish-submit-btn:hover {
+ transform: translateY(-3px) scale(1.05);
+ box-shadow:
+ 0 12px 35px rgba(45, 80, 22, 0.4),
+ 0 0 0 3px rgba(144, 238, 144, 0.3),
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
+ background-position: 100% 100%;
+}
+
+.publish-submit-btn:active {
+ transform: translateY(-1px) scale(1.02);
+ transition: all 0.1s ease;
+}
+
+.publish-submit-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+ transform: none;
+ animation: none;
+}
+
+.publish-submit-btn:disabled:hover {
+ transform: none;
+ box-shadow: 0 8px 25px rgba(45, 80, 22, 0.3);
+}
+
+@keyframes buttonGradient {
+ 0%, 100% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+}
+
+/* 加载动画样式 */
+.loading-spinner {
+ width: 20px;
+ height: 20px;
+ border: 3px solid rgba(255, 255, 255, 0.3);
+ border-top: 3px solid white;
+ border-radius: 50%;
+ animation: loadingSpin 1s linear infinite;
+ margin-right: 12px;
+}
+
+@keyframes loadingSpin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/* 提交提示样式 */
+.submit-tips {
+ margin: 20px auto 0;
+ color: #4a7c59;
+ font-size: 14px;
+ font-style: italic;
+ padding: 15px;
+ background: rgba(144, 238, 144, 0.1);
+ border-radius: 15px;
+ border: 1px solid rgba(144, 238, 144, 0.2);
+ font-family: 'Lora', serif;
+ line-height: 1.5;
+ position: relative;
+ overflow: hidden;
+ text-align: center;
+ max-width: 350px;
+}
+
+.submit-tips::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -100%;
+ width: 100%;
+ height: calc(100% + 4px);
+ background: linear-gradient(90deg,
+ transparent 0%,
+ rgba(144, 238, 144, 0.2) 50%,
+ transparent 100%);
+ animation: tipShine 4s ease-in-out infinite;
+}
+
+@keyframes tipShine {
+ 0%, 80%, 100% {
+ left: -100%;
+ }
+ 10%, 70% {
+ left: 100%;
}
}
/* 响应式设计 */
-@media (max-width: 1200px) {
- .emerald-user-bar {
- width: 440px;
- right: 20px;
- }
-
- .emerald-nav-bar {
- margin: 100px 20px 20px;
- padding: 16px 20px;
- grid-template-columns: repeat(6, 1fr);
- }
-
- .emerald-nav-item {
- min-width: 80px;
- padding: 12px 16px;
- }
-}
-
@media (max-width: 768px) {
- .emerald-user-bar {
- position: relative;
- top: 0;
- right: 0;
- width: 100%;
- margin: 20px 0;
- flex-direction: column;
- gap: 16px;
+ .form-grid {
+ grid-template-columns: 1fr;
+ gap: 20px;
}
- .emerald-brand-section {
- margin-right: 0;
- justify-content: center;
+ .publish-form-advanced {
+ padding: 25px;
+ margin: 20px;
}
- .emerald-user-stats {
- flex-direction: column;
- gap: 8px;
- width: 100%;
+ .file-upload-area {
+ padding: 30px 20px;
}
- .emerald-nav-bar {
- margin: 20px 10px;
- padding: 16px;
- grid-template-columns: repeat(4, 1fr);
- gap: 12px;
+ .upload-main-text {
+ font-size: 16px;
}
- .emerald-nav-item {
- min-width: 70px;
- padding: 10px 12px;
- }
-
- .emerald-nav-icon {
- font-size: 24px !important;
- }
-
- .emerald-nav-label {
+ .upload-sub-text {
font-size: 12px;
}
- .emerald-content-section {
- margin: 20px 10px;
- padding: 20px 15px;
+ .publish-submit-btn {
+ padding: 15px 35px;
+ font-size: 16px;
+ min-width: 160px;
}
- .meteor {
- font-size: 14px;
- }
-
- .garden-element {
- font-size: 20px;
- }
-
- .forum-toolbar {
- flex-direction: column;
- gap: 15px;
- padding: 15px 20px;
- }
-
- .search-section {
- width: 100%;
- max-width: none;
- }
-
- .search-input-container {
- max-width: none;
- }
-
- .forum-search-input {
- border-radius: 25px 0 0 25px;
- }
-
- .forum-search-btn {
- border-radius: 0 25px 25px 0;
- padding: 14px 16px;
- }
-
- .forum-post-card {
+ .form-submit-section {
padding: 20px;
- }
-
- .post-header {
- flex-direction: column;
- align-items: flex-start;
- gap: 10px;
- }
-
- .post-footer {
- flex-direction: column;
- align-items: flex-start;
- gap: 10px;
- }
-}
-
-@media (max-width: 480px) {
- .emerald-nav-bar {
- grid-template-columns: repeat(3, 1fr);
- gap: 8px;
- padding: 12px;
- }
-
- .emerald-nav-item {
- min-width: 60px;
- padding: 8px 10px;
- }
-
- .emerald-nav-icon {
- font-size: 20px !important;
- }
-
- .emerald-nav-label {
- font-size: 11px;
- }
-
- .emerald-table-section {
- margin: 20px 10px;
- padding: 20px 15px;
- }
-
- .emerald-table th,
- .emerald-table td {
- padding: 10px 12px;
- font-size: 13px;
- }
-
- .forum-toolbar {
- padding: 12px 15px;
- }
-
- .search-section {
- flex-direction: column;
- gap: 10px;
- }
-
- .forum-search-input {
- border-radius: 20px;
- border: 2px solid rgba(144, 238, 144, 0.3);
- border-bottom: none;
- }
-
- .forum-search-btn {
- border-radius: 20px;
- border: 2px solid rgba(144, 238, 144, 0.3);
- border-top: none;
- width: 100%;
- }
-
- .post-detail-title {
- font-size: 24px;
- }
-
- .post-detail-content {
- font-size: 14px;
- }
-
- .reply-textarea {
- padding: 15px;
- }
-
- .submit-reply-btn {
- padding: 10px 20px;
- font-size: 14px;
+ margin-top: 30px;
}
}
diff --git a/front/src/UserProfile.css b/front/src/UserProfile.css
new file mode 100644
index 0000000..913b571
--- /dev/null
+++ b/front/src/UserProfile.css
@@ -0,0 +1,681 @@
+/* UserProfile.css - 翡翠园林风格用户个人资料页面样式 */
+
+/* 引入Google字体 */
+@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Lora:wght@400;500;600&display=swap');
+
+/* 主容器 */
+.user-profile-container {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #2d5016 0%, #4a7c59 20%, #8fbc8f 40%, #98fb98 60%, #f0fff0 100%);
+ padding: 24px;
+ font-family: 'Lora', serif;
+ position: relative;
+ overflow-x: hidden;
+}
+
+/* 背景装饰元素 */
+.user-profile-container::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.06) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(144, 238, 144, 0.08) 0%, transparent 50%);
+ animation: backgroundShift 25s ease-in-out infinite;
+ pointer-events: none;
+ z-index: 0;
+}
+
+/* 响应式网格布局 */
+.profile-grid {
+ display: grid;
+ grid-template-columns: 400px 1fr;
+ grid-template-rows: auto auto;
+ gap: 24px;
+ max-width: 1400px;
+ margin: 0 auto;
+ position: relative;
+ z-index: 10;
+}
+
+@media (max-width: 1200px) {
+ .profile-grid {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto auto auto auto;
+ }
+}
+
+/* 卡片基础样式 */
+.profile-card {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(20px);
+ border-radius: 25px;
+ box-shadow:
+ 0 20px 60px rgba(45, 80, 22, 0.12),
+ 0 8px 25px rgba(144, 238, 144, 0.08),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(144, 238, 144, 0.2);
+ backdrop-filter: blur(10px);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ overflow: hidden;
+ position: relative;
+}
+
+.profile-card::before {
+ content: '';
+ position: absolute;
+ top: -2px;
+ left: -2px;
+ right: -2px;
+ bottom: -2px;
+ background: linear-gradient(45deg,
+ #90ee90 0%,
+ #98fb98 25%,
+ #f0fff0 50%,
+ #98fb98 75%,
+ #90ee90 100%);
+ border-radius: 27px;
+ z-index: -1;
+ animation: borderGlow 4s ease-in-out infinite;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.profile-card:hover {
+ transform: translateY(-4px);
+ box-shadow:
+ 0 25px 80px rgba(45, 80, 22, 0.15),
+ 0 12px 35px rgba(144, 238, 144, 0.12),
+ inset 0 1px 0 rgba(255, 255, 255, 0.9);
+}
+
+.profile-card:hover::before {
+ opacity: 1;
+}
+
+/* 用户基本信息卡片 */
+.user-info-card {
+ grid-column: 1;
+ grid-row: 1;
+ padding: 32px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 600px;
+}
+
+.user-avatar-section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-bottom: 32px;
+ position: relative;
+}
+
+.avatar-container {
+ position: relative;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ margin-bottom: 16px;
+}
+
+.avatar-container:hover {
+ transform: scale(1.05);
+}
+
+.user-avatar {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ border: 4px solid rgba(144, 238, 144, 0.4);
+ box-shadow:
+ 0 8px 24px rgba(45, 80, 22, 0.2),
+ 0 0 0 4px rgba(144, 238, 144, 0.2);
+ transition: all 0.3s ease;
+}
+
+.avatar-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background: rgba(45, 80, 22, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity 0.3s ease;
+ color: white;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.avatar-container:hover .avatar-overlay {
+ opacity: 1;
+}
+
+.user-title {
+ color: #1a237e;
+ font-size: 28px;
+ font-weight: 600;
+ margin: 0;
+ letter-spacing: 0.5px;
+ text-align: center;
+}
+
+/* 表单区域 */
+.user-form {
+ width: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.form-label {
+ font-size: 14px;
+ font-weight: 600;
+ color: #3a4a6b;
+ letter-spacing: 0.3px;
+}
+
+.form-input {
+ padding: 12px 16px;
+ border: 2px solid #e0e7ff;
+ border-radius: 12px;
+ font-size: 16px;
+ background: rgba(248, 250, 255, 0.6);
+ transition: all 0.3s ease;
+ outline: none;
+}
+
+.form-input:focus {
+ border-color: #1a237e;
+ background: #ffffff;
+ box-shadow: 0 0 0 4px rgba(26, 35, 126, 0.1);
+}
+
+.form-input:read-only {
+ background: #f5f7ff;
+ cursor: not-allowed;
+ color: #6b7280;
+}
+
+.status-indicator {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.status-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: 2px solid rgba(255, 255, 255, 0.8);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.status-dot.active {
+ background: #43a047;
+ box-shadow: 0 0 0 3px rgba(67, 160, 71, 0.2);
+}
+
+.status-dot.banned {
+ background: #e53935;
+ box-shadow: 0 0 0 3px rgba(229, 57, 53, 0.2);
+}
+
+/* 邀请功能 */
+.invite-section {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 20px;
+ background: linear-gradient(135deg, #f8faff 0%, #e0e7ff 100%);
+ border-radius: 16px;
+ border: 1px solid #e0e7ff;
+}
+
+.invite-form {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.invite-input {
+ flex: 1;
+ min-width: 0;
+}
+
+.invite-counter {
+ font-size: 14px;
+ color: #6b7280;
+ font-weight: 500;
+}
+
+.invite-status {
+ font-size: 14px;
+ padding: 8px 12px;
+ border-radius: 8px;
+ background: rgba(229, 57, 53, 0.1);
+ color: #e53935;
+ border: 1px solid rgba(229, 57, 53, 0.2);
+}
+
+/* 按钮样式 */
+.btn {
+ padding: 12px 20px;
+ border: none;
+ border-radius: 12px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-width: 100px;
+ max-width: 140px;
+ position: relative;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
+ transition: left 0.5s ease;
+}
+
+.btn:hover::before {
+ left: 100%;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, #1a237e 0%, #3f51b5 100%);
+ color: white;
+ box-shadow: 0 4px 16px rgba(26, 35, 126, 0.3);
+}
+
+.btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 24px rgba(26, 35, 126, 0.4);
+}
+
+.btn-danger {
+ background: linear-gradient(135deg, #e53935 0%, #f44336 100%);
+ color: white;
+ box-shadow: 0 4px 16px rgba(229, 57, 53, 0.3);
+}
+
+.btn-warning {
+ background: linear-gradient(135deg, #ff9800 0%, #ffa726 100%);
+ color: white;
+ box-shadow: 0 4px 16px rgba(255, 152, 0, 0.3);
+}
+
+.btn-small {
+ padding: 6px 12px;
+ font-size: 12px;
+ min-width: 70px;
+ max-width: 100px;
+}
+
+.btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none !important;
+}
+
+.btn-group {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+ margin-top: 20px;
+ flex-wrap: wrap;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+/* 活跃度卡片 */
+.activity-card {
+ grid-column: 1;
+ grid-row: 2;
+ padding: 32px;
+ min-height: 400px;
+}
+
+.activity-title {
+ color: #1a237e;
+ font-size: 24px;
+ font-weight: 600;
+ margin: 0 0 24px 0;
+ letter-spacing: 0.5px;
+}
+
+.activity-content {
+ background: linear-gradient(135deg, #f0fff0 0%, #e6ffe6 100%);
+ border: 2px dashed rgba(144, 238, 144, 0.6);
+ border-radius: 16px;
+ padding: 20px;
+ height: calc(100% - 40px);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ box-sizing: border-box;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+.magic-exchange {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 16px;
+ background: rgba(255, 255, 255, 0.9);
+ border-radius: 12px;
+ border: 1px solid rgba(144, 238, 144, 0.3);
+ box-sizing: border-box;
+ max-width: 100%;
+ overflow: hidden;
+}
+
+.exchange-form {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ align-items: center;
+ box-sizing: border-box;
+ max-width: 100%;
+}
+
+.exchange-input {
+ min-width: 100px;
+ flex: 1;
+ max-width: calc(50% - 4px);
+}
+
+.exchange-result {
+ font-size: 14px;
+ font-weight: 600;
+ color: #43a047;
+ padding: 8px 12px;
+ background: rgba(67, 160, 71, 0.1);
+ border-radius: 8px;
+ border: 1px solid rgba(67, 160, 71, 0.2);
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+}
+
+.stat-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ background: rgba(255, 255, 255, 0.6);
+ border-radius: 10px;
+ border: 1px solid rgba(224, 231, 255, 0.5);
+}
+
+.stat-label {
+ font-size: 14px;
+ color: #6b7280;
+ font-weight: 500;
+}
+
+.stat-value {
+ font-size: 16px;
+ font-weight: 700;
+}
+
+.stat-value.upload { color: #43a047; }
+.stat-value.download { color: #e53935; }
+.stat-value.ratio { color: #ff9800; }
+.stat-value.vip { color: #1976d2; }
+.stat-value.magic { color: #9c27b0; }
+
+/* 种子列表卡片 */
+.seeds-card, .favorites-card {
+ grid-column: 2;
+ padding: 32px;
+ min-height: 400px;
+ display: flex;
+ flex-direction: column;
+}
+
+.seeds-card {
+ grid-row: 1;
+}
+
+.favorites-card {
+ grid-row: 2;
+}
+
+.list-title {
+ color: #1a237e;
+ font-size: 24px;
+ font-weight: 600;
+ margin: 0 0 24px 0;
+ letter-spacing: 0.5px;
+}
+
+.list-container {
+ background: linear-gradient(135deg, #f0fff0 0%, #e6ffe6 100%);
+ border: 2px dashed rgba(144, 238, 144, 0.6);
+ border-radius: 16px;
+ padding: 16px;
+ flex: 1;
+ overflow: hidden;
+ box-sizing: border-box;
+ max-width: 100%;
+ max-height: calc(100% - 40px);
+}
+
+.empty-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 200px;
+ color: #9e9e9e;
+ font-size: 18px;
+}
+
+.empty-icon {
+ font-size: 48px;
+ margin-bottom: 16px;
+ opacity: 0.5;
+}
+
+.seeds-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-height: 320px;
+ overflow-y: auto;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.seed-item {
+ display: flex;
+ align-items: center;
+ padding: 16px 20px;
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 12px;
+ border: 1px solid rgba(224, 231, 255, 0.6);
+ transition: all 0.3s ease;
+ cursor: pointer;
+}
+
+.seed-item:hover {
+ background: #ffffff;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(26, 35, 126, 0.1);
+}
+
+.seed-title {
+ flex: 2;
+ font-weight: 600;
+ color: #1a237e;
+ text-decoration: none;
+ font-size: 16px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.seed-title:hover {
+ text-decoration: underline;
+}
+
+.seed-tags {
+ flex: 1;
+ color: #5c6bc0;
+ font-size: 14px;
+ padding: 0 12px;
+}
+
+.seed-stats {
+ flex: 1;
+ color: #ff9800;
+ font-size: 14px;
+ text-align: right;
+ font-weight: 500;
+ padding-right: 12px;
+}
+
+.seed-actions {
+ display: flex;
+ gap: 8px;
+}
+
+/* 滚动条样式 */
+.seeds-list::-webkit-scrollbar {
+ width: 6px;
+}
+
+.seeds-list::-webkit-scrollbar-track {
+ background: rgba(224, 231, 255, 0.3);
+ border-radius: 3px;
+}
+
+.seeds-list::-webkit-scrollbar-thumb {
+ background: rgba(26, 35, 126, 0.3);
+ border-radius: 3px;
+}
+
+.seeds-list::-webkit-scrollbar-thumb:hover {
+ background: rgba(26, 35, 126, 0.5);
+}
+
+/* 对话框覆盖样式 */
+.dialog-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(8px);
+ z-index: 1000;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .user-profile-container {
+ padding: 16px;
+ }
+
+ .profile-grid {
+ gap: 16px;
+ }
+
+ .profile-card {
+ padding: 24px;
+ }
+
+ .user-title {
+ font-size: 24px;
+ }
+
+ .exchange-form {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .seed-item {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 12px;
+ }
+
+ .seed-title,
+ .seed-tags,
+ .seed-stats {
+ flex: none;
+ text-align: left;
+ padding: 0;
+ }
+
+ .seed-actions {
+ justify-content: flex-end;
+ }
+}
+
+/* 动画 */
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.profile-card {
+ animation: fadeInUp 0.6s ease-out;
+}
+
+.profile-card:nth-child(1) { animation-delay: 0.1s; }
+.profile-card:nth-child(2) { animation-delay: 0.2s; }
+.profile-card:nth-child(3) { animation-delay: 0.3s; }
+.profile-card:nth-child(4) { animation-delay: 0.4s; }
+
+/* 加载动画 */
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.loading {
+ animation: pulse 2s infinite;
+}
diff --git a/front/src/UserProfile.js b/front/src/UserProfile.js
index fb44b43..074d68c 100644
--- a/front/src/UserProfile.js
+++ b/front/src/UserProfile.js
@@ -1,5 +1,13 @@
import React, { useState, useEffect } from "react";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
+import PersonIcon from "@mui/icons-material/Person";
+import EmailIcon from "@mui/icons-material/Email";
+import SchoolIcon from "@mui/icons-material/School";
+import CloudUploadIcon from "@mui/icons-material/CloudUpload";
+import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
+import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
+import FavoriteIcon from "@mui/icons-material/Favorite";
+import EmptyIcon from "@mui/icons-material/Inbox";
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import MenuItem from '@mui/material/MenuItem';
@@ -9,7 +17,7 @@
import DialogActions from '@mui/material/DialogActions';
import { useNavigate } from "react-router-dom";
import { API_BASE_URL } from "./config";
-import "./App.css";
+import "./UserProfile.css";
export default function UserProfile() {
const navigate = useNavigate();
@@ -386,442 +394,352 @@
console.error('账号迁移失败', err);
setMigrationStatus('迁移失败,请检查网络');
}
- };
+ }; return (
+ <div className="user-profile-container">
+ <div className="profile-grid">
+ {/* 用户基本信息卡片 */}
+ <div className="profile-card user-info-card">
+ <div className="user-avatar-section">
+ <div className="avatar-container" onClick={handleAvatarClick}>
+ {tempUserInfo.avatar_url ? (
+ <img
+ src={tempUserInfo.avatar_url}
+ alt="用户头像"
+ className="user-avatar"
+ />
+ ) : (
+ <AccountCircleIcon style={{ fontSize: 120, color: '#1a237e' }} />
+ )}
+ <div className="avatar-overlay">
+ <span>点击更换头像</span>
+ </div>
+ </div>
+ <h2 className="user-title">用户个人资料</h2>
+ </div>
- return (
- <div
- className="container"
- style={{
- minHeight: '100vh',
- background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)',
- display: 'grid',
- gridTemplateColumns: '1.1fr 1.9fr',
- gridTemplateRows: 'auto auto',
- gap: '12px',
- padding: '24px 3vw',
- boxSizing: 'border-box'
- }}
- >
- {/* 左上:用户资料 */}
- <div style={{
- gridColumn: '1 / 2',
- gridRow: '1 / 2',
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- background: '#fff',
- borderRadius: 20,
- boxShadow: '0 6px 32px #e0e7ff',
- padding: '32px 28px',
- minWidth: 320,
- minHeight: 420,
- transition: 'box-shadow 0.2s',
- }}>
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 18 }}>
- <div onClick={handleAvatarClick} style={{ cursor: 'pointer', position: 'relative' }}>
- <AccountCircleIcon style={{ fontSize: 96, color: '#1a237e', marginBottom: 12 }} />
- {tempUserInfo.avatar_url && (
- <img
- src={tempUserInfo.avatar_url}
- alt="用户头像"
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: 96,
- height: 96,
- borderRadius: '50%',
- objectFit: 'cover',
- border: '2px solid #e0e7ff',
- boxShadow: '0 2px 8px #bfcfff'
- }}
+ <div className="user-form">
+ <div className="form-group">
+ <label className="form-label">
+ <PersonIcon style={{ fontSize: 16, marginRight: 4 }} />
+ 用户名
+ </label>
+ <input
+ className="form-input"
+ value={tempUserInfo.username}
+ onChange={(e) => handleInputChange("username", e.target.value)}
+ placeholder="请输入用户名"
/>
+ </div>
+
+ <div className="form-group">
+ <label className="form-label">
+ <EmailIcon style={{ fontSize: 16, marginRight: 4 }} />
+ 邮箱
+ </label>
+ <input
+ className="form-input"
+ value={tempUserInfo.email}
+ readOnly
+ style={{ cursor: 'not-allowed' }}
+ />
+ </div>
+
+ <div className="form-group">
+ <label className="form-label">
+ <SchoolIcon style={{ fontSize: 16, marginRight: 4 }} />
+ 学校
+ </label>
+ <input
+ className="form-input"
+ value={tempUserInfo.school}
+ onChange={(e) => handleInputChange("school", e.target.value)}
+ placeholder="请输入学校名称"
+ />
+ </div>
+
+ <div className="form-group">
+ <label className="form-label">账号状态</label>
+ <div className="status-indicator">
+ <input
+ className="form-input"
+ value={tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}
+ readOnly
+ />
+ <div className={`status-dot ${tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? 'banned' : 'active'}`}></div>
+ </div>
+ </div>
+
+ <div className="form-group">
+ <label className="form-label">性别</label>
+ <select
+ className="form-input"
+ value={tempUserInfo.gender}
+ onChange={(e) => handleInputChange("gender", e.target.value)}
+ >
+ <option value="">请选择性别</option>
+ <option value="m">男性</option>
+ <option value="f">女性</option>
+ </select>
+ </div>
+
+ {/* 邀请功能 */}
+ <div className="invite-section">
+ <label className="form-label">邀请功能</label>
+ <div className="invite-form">
+ <input
+ className="form-input invite-input"
+ type="email"
+ placeholder="输入邀请邮箱"
+ value={inviteEmail}
+ onChange={e => setInviteEmail(e.target.value)}
+ disabled={Number(tempUserInfo.invite_left) === 0}
+ />
+ <button
+ className="btn btn-primary btn-small"
+ onClick={handleInvite}
+ disabled={Number(tempUserInfo.invite_left) === 0 || !inviteEmail}
+ >
+ 邀请
+ </button>
+ </div>
+ <div className="invite-counter">
+ 剩余邀请次数:{tempUserInfo.invite_left || "0"}
+ </div>
+ {inviteStatus && (
+ <div className="invite-status">{inviteStatus}</div>
+ )}
+ </div>
+
+ <div className="btn-group">
+ <button className="btn btn-primary" onClick={handleSave}>
+ 保存信息
+ </button>
+ <button className="btn btn-danger" onClick={() => setAppealOpen(true)}>
+ 用户申诉
+ </button>
+ <button className="btn btn-warning" onClick={() => setMigrationOpen(true)}>
+ 账号迁移
+ </button> </div>
+ </div>
+ </div>
+
+ {/* 活跃度卡片 */}
+ <div className="profile-card activity-card">
+ <h3 className="activity-title">
+ <AutoAwesomeIcon style={{ fontSize: 24, marginRight: 8 }} />
+ 活跃度统计
+ </h3>
+
+ <div className="activity-content">
+ {/* 魔力值兑换 */}
+ <div className="magic-exchange">
+ <div className="stat-item">
+ <span className="stat-label">当前魔力值</span>
+ <span className="stat-value magic">{userStats.magic}</span>
+ </div>
+
+ <div className="exchange-form">
+ <input
+ className="form-input exchange-input"
+ type="number"
+ placeholder="输入兑换魔力值"
+ value={exchangeMagic}
+ onChange={e => setExchangeMagic(e.target.value)}
+ />
+ <select
+ className="form-input exchange-input"
+ value={exchangeType}
+ onChange={e => setExchangeType(e.target.value)}
+ >
+ <option value="uploaded">上传量(增加)</option>
+ <option value="downloaded">下载量(减少)</option>
+ <option value="vip_downloads">VIP下载次数(增加)</option>
+ </select>
+ <button
+ className="btn btn-primary btn-small"
+ onClick={handleExchange}
+ disabled={
+ !exchangeMagic ||
+ isNaN(exchangeMagic) ||
+ Number(exchangeMagic) <= 0 ||
+ Number(exchangeMagic) > userStats.magic ||
+ !Number.isInteger(exchangeResult)
+ }
+ >
+ 兑换
+ </button>
+ </div>
+
+ {exchangeMagic && (
+ <div className="exchange-result">
+ 可兑换:{exchangeResult} {exchangeType === 'vip_downloads' ? '次' : 'MB'}
+ {!Number.isInteger(exchangeResult) && exchangeResult > 0 && (
+ <span style={{ color: '#e53935', marginLeft: 8 }}>
+ (结果必须为整数)
+ </span>
+ )}
+ </div>
+ )}
+ </div>
+
+ {/* 统计数据 */}
+ <div className="stats-grid">
+ <div className="stat-item">
+ <span className="stat-label">
+ <CloudUploadIcon style={{ fontSize: 16, marginRight: 4 }} />
+ 上传量
+ </span>
+ <span className="stat-value upload">
+ {(userStats.upload / 1000000)?.toFixed(2)} MB
+ </span>
+ </div>
+
+ <div className="stat-item">
+ <span className="stat-label">
+ <CloudDownloadIcon style={{ fontSize: 16, marginRight: 4 }} />
+ 下载量
+ </span>
+ <span className="stat-value download">
+ {(userStats.download / 1000000)?.toFixed(2)} MB
+ </span>
+ </div>
+
+ <div className="stat-item">
+ <span className="stat-label">上传/下载比</span>
+ <span className="stat-value ratio">
+ {userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}
+ </span>
+ </div>
+
+ <div className="stat-item">
+ <span className="stat-label">VIP下载次数</span>
+ <span className="stat-value vip">{userStats.viptime}</span>
+ </div>
+ </div>
+ </div>
+ </div> {/* 个人上传种子列表 */}
+ <div className="profile-card seeds-card">
+ <h3 className="list-title">
+ <CloudUploadIcon style={{ fontSize: 24, marginRight: 8 }} />
+ 个人上传种子列表
+ </h3>
+
+ <div className="list-container">
+ {userSeeds.length === 0 ? (
+ <div className="empty-state">
+ <EmptyIcon className="empty-icon" />
+ <span>暂无上传种子</span>
+ </div>
+ ) : (
+ <ul className="seeds-list">
+ {userSeeds.map((seed, idx) => (
+ <li
+ key={seed.seedid || idx}
+ className="seed-item"
+ onClick={e => {
+ if (e.target.classList.contains('delete-btn')) return;
+ navigate(`/torrent/${seed.seed_id}`);
+ }}
+ >
+ <span className="seed-title">{seed.title}</span>
+ <span className="seed-tags">{seed.tags}</span>
+ <span className="seed-stats">人气: {seed.downloadtimes}</span>
+ <div className="seed-actions">
+ <button
+ className="btn btn-danger btn-small delete-btn"
+ onClick={async e => {
+ e.stopPropagation();
+ const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+ const userid = match ? match[2] : null;
+ if (!userid) {
+ alert('未获取到用户ID');
+ return;
+ }
+ try {
+ const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ seed_id: seed.seed_id, userid }),
+ });
+ if (res.ok) {
+ setUserSeeds(userSeeds.filter((s, i) => (s.seed_id || i) !== (seed.seed_id || idx)));
+ } else {
+ alert('删除失败,请重试');
+ }
+ } catch (err) {
+ alert('删除失败,请检查网络');
+ }
+ }}
+ >
+ 删除
+ </button>
+ </div>
+ </li>
+ ))}
+ </ul>
)}
</div>
- <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 26, letterSpacing: 1 }}>用户个人资料</h2>
</div>
- <div className="card" style={{
- padding: 32,
- width: '100%',
- background: '#fff',
- borderRadius: 18,
- boxShadow: '0 2px 12px #e0e7ff',
- flex: 1,
- minWidth: 0
- }}>
- <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
- <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>用户名:</b>
- <TextField
- variant="outlined"
- size="small"
- value={tempUserInfo.username}
- onChange={(e) => handleInputChange("username", e.target.value)}
- sx={{ flex: 1, minWidth: 0 }}
- />
- </div>
- <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
- <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邮箱:</b>
- <TextField
- variant="outlined"
- size="small"
- value={tempUserInfo.email}
- InputProps={{ readOnly: true }}
- sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
- />
- </div>
- {/* 邀请功能 */}
- <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
- <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邀请剩余:</b>
- <TextField
- type="email"
- size="small"
- placeholder="被邀请邮箱"
- value={inviteEmail}
- onChange={e => setInviteEmail(e.target.value)}
- sx={{ flex: 2, marginRight: 1, minWidth: 120 }}
- disabled={Number(tempUserInfo.invite_left) === 0}
- />
- <Button
- variant="contained"
- color="primary"
- onClick={handleInvite}
- disabled={Number(tempUserInfo.invite_left) === 0 || !inviteEmail}
- sx={{ marginRight: 1, minWidth: 80 }}
- >邀请</Button>
- <span style={{ color: '#888', fontSize: 15 }}>剩余:{tempUserInfo.invite_left || "0"}</span>
- </div>
- {inviteStatus && <div style={{ color: '#e53935', fontSize: 14, marginBottom: 8 }}>{inviteStatus}</div>}
- <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
- <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>学校:</b>
- <TextField
- variant="outlined"
- size="small"
- value={tempUserInfo.school}
- onChange={(e) => handleInputChange("school", e.target.value)}
- sx={{ flex: 1, minWidth: 0 }}
- />
- </div>
- <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
- <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>账号状态:</b>
- <TextField
- variant="outlined"
- size="small"
- value={tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}
- InputProps={{ readOnly: true }}
- sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
- />
- <span style={{
- display: 'inline-block',
- width: 12,
- height: 12,
- borderRadius: '50%',
- backgroundColor: tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? '#e53935' : '#43a047',
- marginLeft: 10,
- border: '1px solid #b2b2b2',
- }} />
- </div>
- <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
- <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>性别:</b>
- <TextField
- select
- variant="outlined"
- size="small"
- value={tempUserInfo.gender}
- onChange={e => handleInputChange("gender", e.target.value)}
- sx={{ flex: 1, minWidth: 0 }}
- >
- <MenuItem value="m">男性</MenuItem>
- <MenuItem value="f">女性</MenuItem>
- </TextField>
- </div> <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>
- <Button
- variant="contained"
- color="primary"
- onClick={handleSave}
- sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
- >保存</Button>
- <Button
- variant="contained"
- color="error"
- onClick={() => setAppealOpen(true)}
- sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
- >用户申诉</Button>
- <Button
- variant="contained"
- color="warning"
- onClick={() => setMigrationOpen(true)}
- sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
- >账号迁移</Button>
- </div>
- </div>
- </div>
- {/* 左下:活跃度模块 */}
- <div style={{
- gridColumn: '1 / 2',
- gridRow: '2 / 3',
- background: '#fff',
- borderRadius: 20,
- boxShadow: '0 6px 32px #e0e7ff',
- padding: '32px 28px',
- minWidth: 320,
- minHeight: 320,
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center'
- }}>
- <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>活跃度</h3>
- <div style={{
- border: '1.5px dashed #b2b2b2',
- borderRadius: 14,
- minHeight: 80,
- padding: 22,
- display: 'flex',
- flexDirection: 'column',
- gap: 14,
- fontSize: 18,
- background: '#f8faff'
- }}>
- <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
- <span>魔力值:<b style={{ color: '#1976d2' }}>{userStats.magic}</b></span>
- <TextField
- type="number"
- size="small"
- placeholder="输入兑换魔力值"
- value={exchangeMagic}
- onChange={e => setExchangeMagic(e.target.value)}
- sx={{ width: 100, marginLeft: 2, marginRight: 1 }}
- />
- <TextField
- select
- size="small"
- value={exchangeType}
- onChange={e => setExchangeType(e.target.value)}
- sx={{ minWidth: 120 }}
- >
- <MenuItem value="uploaded">上传量(增加)</MenuItem>
- <MenuItem value="downloaded">下载量(减少)</MenuItem>
- <MenuItem value="vip_downloads">VIP下载次数(增加)</MenuItem>
- </TextField> <span style={{ marginLeft: 8, color: '#43a047' }}>
- 可兑换:<b>{exchangeResult}</b> {exchangeType === 'vip_downloads' ? '次' : 'MB'}
- {!Number.isInteger(exchangeResult) && exchangeResult > 0 && (
- <span style={{ color: '#e53935', fontSize: '12px', marginLeft: 8 }}>
- (结果必须为整数)
- </span>
- )}
- </span><Button
- variant="contained"
- color="primary"
- onClick={handleExchange}
- disabled={
- !exchangeMagic ||
- isNaN(exchangeMagic) ||
- Number(exchangeMagic) <= 0 ||
- Number(exchangeMagic) > userStats.magic ||
- !Number.isInteger(exchangeResult)
- }
- sx={{
- marginLeft: 2,
- minWidth: 80,
- background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic || !Number.isInteger(exchangeResult)) ? '#ccc' : undefined
- }}
- >兑换</Button>
- </div> <div>上传量:<b style={{ color: '#43a047' }}>{(userStats.upload / 1000000)?.toFixed(2)} MB</b></div>
- <div>下载量:<b style={{ color: '#e53935' }}>{(userStats.download / 1000000)?.toFixed(2)} MB</b></div>
- <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}</b></div>
- <div>VIP下载次数:<b style={{ color: '#1976d2' }}>{userStats.viptime}</b></div>
- </div>
- </div>
- {/* 右上:个人上传种子列表 */}
- <div style={{
- gridColumn: '2 / 3',
- gridRow: '1 / 2',
- background: '#fff',
- borderRadius: 20,
- boxShadow: '0 6px 32px #e0e7ff',
- padding: '32px 36px',
- minHeight: 420,
- display: 'flex',
- flexDirection: 'column'
- }}>
- <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人上传种子列表</h3>
- <div style={{
- border: '1.5px dashed #b2b2b2',
- borderRadius: 14,
- minHeight: 80,
- padding: 16,
- background: '#f8faff'
- }}>
- {userSeeds.length === 0 ? (
- <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无上传种子)</div>
- ) : (
- <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
- {userSeeds.map((seed, idx) => (
- <li
- key={seed.seedid || idx}
- style={{
- display: 'flex',
- alignItems: 'center',
- padding: '12px 0',
- borderBottom: idx === userSeeds.length - 1 ? 'none' : '1px solid #e0e7ff',
- cursor: 'pointer',
- transition: 'background 0.15s'
- }}
- onClick={e => {
- if (e.target.classList.contains('delete-btn')) return;
- navigate(`/torrent/${seed.seed_id}`);
- }}
- onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}
- onMouseOut={e => e.currentTarget.style.background = ''}
- >
- <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline' }}>{seed.title}</span>
- <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.tags}</span>
- <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.downloadtimes}</span>
- <Button
- className="delete-btn"
- variant="contained"
- color="error"
- size="small"
- sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }} onClick={async e => {
- e.stopPropagation();
- const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
- const userid = match ? match[2] : null;
- if (!userid) {
- alert('未获取到用户ID');
- return;
- }
- try {
- const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ seed_id: seed.seed_id, userid }),
- });
- if (res.ok) {
- setUserSeeds(userSeeds.filter((s, i) => (s.seed_id || i) !== (seed.seed_id || idx)));
- } else {
- alert('删除失败,请重试');
- }
- } catch (err) {
- alert('删除失败,请检查网络');
- }
+
+ {/* 个人收藏种子列表 */}
+ <div className="profile-card favorites-card">
+ <h3 className="list-title">
+ <FavoriteIcon style={{ fontSize: 24, marginRight: 8 }} />
+ 个人收藏种子列表
+ </h3>
+
+ <div className="list-container">
+ {userFavorites.length === 0 ? (
+ <div className="empty-state">
+ <FavoriteIcon className="empty-icon" />
+ <span>暂无收藏种子</span>
+ </div>
+ ) : (
+ <ul className="seeds-list">
+ {userFavorites.map((seed, idx) => (
+ <li
+ key={seed.seedid || idx}
+ className="seed-item"
+ onClick={e => {
+ if (e.target.classList.contains('remove-favorite-btn')) return;
+ navigate(`/torrent/${seed.seedid || seed.seed_id}`);
}}
- >删除</Button>
- </li>
- ))}
- </ul>
- )}
+ >
+ <span className="seed-title">{seed.seed.title}</span>
+ <span className="seed-tags">{seed.seed.tags}</span>
+ <span className="seed-stats">人气: {seed.seed.downloadtimes}</span>
+ <div className="seed-actions">
+ <button
+ className="btn btn-warning btn-small remove-favorite-btn"
+ onClick={e => {
+ e.stopPropagation();
+ handleRemoveFavorite(seed.seedid || seed.seed_id);
+ }}
+ >
+ 取消收藏
+ </button>
+ </div>
+ </li>
+ ))}
+ </ul>
+ )}
+ </div>
</div>
- </div>
- {/* 右下:个人收藏种子列表 */}
- <div style={{
- gridColumn: '2 / 3',
- gridRow: '2 / 3',
- background: '#fff',
- borderRadius: 20,
- boxShadow: '0 6px 32px #e0e7ff',
- padding: '32px 36px',
- minHeight: 320,
- display: 'flex',
- flexDirection: 'column'
- }}>
- <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人收藏种子列表</h3>
- <div style={{
- border: '1.5px dashed #b2b2b2',
- borderRadius: 14,
- minHeight: 80,
- padding: 16,
- background: '#f8faff'
- }}>
- {userFavorites.length === 0 ? (
- <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无收藏种子)</div>
- ) : (<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
- {userFavorites.map((seed, idx) => (
- <li
- key={seed.seedid || idx}
- style={{
- display: 'flex',
- alignItems: 'center',
- padding: '12px 0',
- borderBottom: idx === userFavorites.length - 1 ? 'none' : '1px solid #e0e7ff',
- cursor: 'pointer',
- transition: 'background 0.15s'
- }} onClick={e => {
- if (e.target.classList.contains('remove-favorite-btn')) return;
- navigate(`/torrent/${seed.seedid || seed.seed_id}`);
- }}
- onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}
- onMouseOut={e => e.currentTarget.style.background = ''}
- >
- <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline', cursor: 'pointer' }}>{seed.seed.title}</span>
- <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.seed.tags}</span>
- <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.seed.downloadtimes}</span>
- <Button
- className="remove-favorite-btn"
- variant="contained"
- color="warning"
- size="small"
- sx={{ marginLeft: 2, borderRadius: 1, minWidth: 80 }} onClick={e => {
- e.stopPropagation();
- handleRemoveFavorite(seed.seedid || seed.seed_id);
- }}
- >取消收藏</Button>
- </li>
- ))}
- </ul>
- )}
- </div>
- </div>
+ </div>
{/* 申诉弹窗 */}
- <Dialog open={appealOpen} onClose={() => setAppealOpen(false)}>
- <DialogTitle>提交申诉</DialogTitle>
- <DialogContent>
- <div style={{ marginBottom: 16 }}>
+ <Dialog open={appealOpen} onClose={() => setAppealOpen(false)} maxWidth="sm" fullWidth>
+ <DialogTitle style={{ background: '#f8faff', color: '#1a237e', fontWeight: 600 }}>
+ 提交申诉
+ </DialogTitle>
+ <DialogContent style={{ padding: '24px', background: '#ffffff' }}>
+ <div style={{ marginBottom: 20 }}>
<TextField
label="申诉主题"
fullWidth
value={appealTitle}
onChange={e => setAppealTitle(e.target.value)}
- size="small"
- />
- </div> <div>
- <input
- type="file"
- accept=".pdf"
- onChange={e => {
- const file = e.target.files[0];
- if (file && file.type !== 'application/pdf') {
- alert('请选择PDF文件');
- e.target.value = '';
- setAppealFile(null);
- } else {
- setAppealFile(file);
- }
- }}
- style={{ marginTop: 8 }}
- />
- <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>
- 请选择PDF文件(最大100MB)
- </div>
- </div>
- </DialogContent>
- <DialogActions>
- <Button onClick={handleAppealSubmit} variant="contained" color="primary" disabled={!appealTitle || !appealFile}>提交</Button>
- <Button onClick={() => setAppealOpen(false)} variant="outlined">取消</Button>
- </DialogActions>
- </Dialog>
- {/* 账号迁移弹窗 */}
- <Dialog open={migrationOpen} onClose={() => setMigrationOpen(false)}>
- <DialogTitle>账号迁移</DialogTitle>
- <DialogContent>
- <div style={{ marginBottom: 16 }}>
- <TextField
- label="待发放上传量"
- type="number"
- fullWidth
- value={migrationUpload}
- onChange={e => setMigrationUpload(e.target.value)}
- size="small"
- inputProps={{ min: 1 }}
- style={{ marginBottom: 18 }}
+ variant="outlined"
+ style={{ marginBottom: 16 }}
/>
</div>
<div>
@@ -838,21 +756,135 @@
setAppealFile(file);
}
}}
- style={{ marginTop: 8 }}
+ style={{
+ marginTop: 8,
+ padding: '12px',
+ border: '2px dashed #e0e7ff',
+ borderRadius: '8px',
+ width: '100%',
+ background: '#f8faff'
+ }}
/>
- <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>
+ <div style={{
+ fontSize: 12,
+ color: '#666',
+ marginTop: 8,
+ padding: '8px 12px',
+ background: '#f0f4ff',
+ borderRadius: '6px'
+ }}>
+ 请选择PDF文件(最大100MB)
+ </div>
+ </div>
+ </DialogContent>
+ <DialogActions style={{ padding: '16px 24px', background: '#f8faff' }}>
+ <Button
+ onClick={handleAppealSubmit}
+ variant="contained"
+ disabled={!appealTitle || !appealFile}
+ style={{
+ background: (!appealTitle || !appealFile) ? '#ccc' : 'linear-gradient(135deg, #1a237e 0%, #3f51b5 100%)',
+ color: 'white',
+ fontWeight: 600
+ }}
+ >
+ 提交申诉
+ </Button>
+ <Button
+ onClick={() => setAppealOpen(false)}
+ variant="outlined"
+ style={{ color: '#1a237e', borderColor: '#1a237e' }}
+ >
+ 取消
+ </Button>
+ </DialogActions>
+ </Dialog>
+
+ {/* 账号迁移弹窗 */}
+ <Dialog open={migrationOpen} onClose={() => setMigrationOpen(false)} maxWidth="sm" fullWidth>
+ <DialogTitle style={{ background: '#f8faff', color: '#1a237e', fontWeight: 600 }}>
+ 账号迁移
+ </DialogTitle>
+ <DialogContent style={{ padding: '24px', background: '#ffffff' }}>
+ <div style={{ marginBottom: 20 }}>
+ <TextField
+ label="待发放上传量"
+ type="number"
+ fullWidth
+ value={migrationUpload}
+ onChange={e => setMigrationUpload(e.target.value)}
+ variant="outlined"
+ inputProps={{ min: 1 }}
+ style={{ marginBottom: 16 }}
+ />
+ </div>
+ <div>
+ <input
+ type="file"
+ accept=".pdf"
+ onChange={e => {
+ const file = e.target.files[0];
+ if (file && file.type !== 'application/pdf') {
+ alert('请选择PDF文件');
+ e.target.value = '';
+ setAppealFile(null);
+ } else {
+ setAppealFile(file);
+ }
+ }}
+ style={{
+ marginTop: 8,
+ padding: '12px',
+ border: '2px dashed #e0e7ff',
+ borderRadius: '8px',
+ width: '100%',
+ background: '#f8faff'
+ }}
+ />
+ <div style={{
+ fontSize: 12,
+ color: '#666',
+ marginTop: 8,
+ padding: '8px 12px',
+ background: '#f0f4ff',
+ borderRadius: '6px'
+ }}>
请选择PDF文件(最大10MB)
</div>
</div>
{migrationStatus && (
- <div style={{ color: migrationStatus.includes('成功') ? '#43a047' : '#e53935', fontSize: 14, marginTop: 8 }}>
+ <div style={{
+ color: migrationStatus.includes('成功') ? '#43a047' : '#e53935',
+ fontSize: 14,
+ marginTop: 16,
+ padding: '12px',
+ borderRadius: '8px',
+ background: migrationStatus.includes('成功') ? 'rgba(67, 160, 71, 0.1)' : 'rgba(229, 57, 53, 0.1)',
+ border: `1px solid ${migrationStatus.includes('成功') ? 'rgba(67, 160, 71, 0.3)' : 'rgba(229, 57, 53, 0.3)'}`
+ }}>
{migrationStatus}
</div>
)}
</DialogContent>
- <DialogActions>
- <Button onClick={handleMigrationSubmit} variant="contained" color="primary">提交迁移</Button>
- <Button onClick={() => setMigrationOpen(false)} variant="outlined">取消</Button>
+ <DialogActions style={{ padding: '16px 24px', background: '#f8faff' }}>
+ <Button
+ onClick={handleMigrationSubmit}
+ variant="contained"
+ style={{
+ background: 'linear-gradient(135deg, #ff9800 0%, #ffa726 100%)',
+ color: 'white',
+ fontWeight: 600
+ }}
+ >
+ 提交迁移
+ </Button>
+ <Button
+ onClick={() => setMigrationOpen(false)}
+ variant="outlined"
+ style={{ color: '#1a237e', borderColor: '#1a237e' }}
+ >
+ 取消
+ </Button>
</DialogActions>
</Dialog>
</div>
diff --git a/front/src/UserProfileNew.js b/front/src/UserProfileNew.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/front/src/UserProfileNew.js