final
Change-Id: Icf4ec3950a6bd1e066fa4a1976af36721af62a06
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>