blob: eed84dafa0bcde49ca630d0ee105bb998bb861cb [file] [log] [blame]
import React, { useState, useEffect } from "react";
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import MenuItem from '@mui/material/MenuItem';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import { useNavigate } from "react-router-dom";
import { API_BASE_URL } from "./config";
import "./App.css";
export default function UserProfile() {
const navigate = useNavigate();
const [userInfo, setUserInfo] = useState({
avatar_url: "",
username: "示例用户",
email: "user@example.com",
invitetimes: "",
school: "",
account_status: "",
gender: "",
});
const [tempUserInfo, setTempUserInfo] = useState({ ...userInfo });
const [userSeeds, setUserSeeds] = useState([]);
const [userFavorites, setUserFavorites] = useState([]);
const [userStats, setUserStats] = useState({
magic: 0,
upload: 0,
viptime: 0,
ratio: 0,
});
// 邀请相关
const [inviteEmail, setInviteEmail] = useState('');
const [inviteStatus, setInviteStatus] = useState('');
// 兑换相关
const [exchangeType, setExchangeType] = useState('uploaded');
const [exchangeMagic, setExchangeMagic] = useState('');
const [exchangeResult, setExchangeResult] = useState(0);
// 兑换比例
const exchangeRate = { uploaded: 0.1, downloaded: 0.1, vip_downloads: 100 };
// 用户申诉相关
const [appealOpen, setAppealOpen] = useState(false);
const [appealTitle, setAppealTitle] = useState('');
const [appealFile, setAppealFile] = useState(null);
// 账号迁移相关
const [migrationOpen, setMigrationOpen] = useState(false);
const [migrationEmail, setMigrationEmail] = useState('');
const [migrationPassword, setMigrationPassword] = useState('');
const [migrationStatus, setMigrationStatus] = useState('');
// 兑换结果计算
React.useEffect(() => {
if (!exchangeMagic || isNaN(exchangeMagic)) {
setExchangeResult(0);
return;
}
setExchangeResult(Number(exchangeMagic) / exchangeRate[exchangeType]);
}, [exchangeMagic, exchangeType]);
// 获取用户信息
useEffect(() => {
const fetchUserInfo = async () => {
// 假设userid存储在localStorage或其他地方
// const userid = localStorage.getItem("userid");
// const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);
if (res.ok) {
const data = await res.json();
// console.log("获取用户信息:", data);
setUserInfo(data);
setTempUserInfo(data);
}
} catch (err) {
console.error("获取用户信息失败", err);
}
};
fetchUserInfo();
}, []);
// 获取上传种子
useEffect(() => {
const fetchUserSeeds = async () => {
// const userid = localStorage.getItem("userid");
// const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userid}`);
if (res.ok) {
const data = await res.json();
setUserSeeds(data);
}
} catch (err) {
console.error("获取种子列表失败", err);
}
};
fetchUserSeeds();
}, []);
// 获取收藏种子
useEffect(() => {
const fetchUserFavorites = async () => {
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-favorites?userid=${userid}`);
if (res.ok) {
const data = await res.json();
// console.log("获取收藏种子列表:", data);
setUserFavorites(data);
}
} catch (err) {
console.error("获取收藏种子列表失败", err);
}
};
fetchUserFavorites();
}, []);
// 获取活跃度
useEffect(() => {
const fetchUserStats = async () => {
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
if (res.ok) {
const data = await res.json();
setUserStats(data);
}
} catch (err) {
console.error("获取活跃度信息失败", err);
}
};
fetchUserStats();
}, []);
const handleInputChange = (field, value) => {
setTempUserInfo({ ...tempUserInfo, [field]: value });
};
const handleSave = async () => {
if (tempUserInfo.gender === "男"){
tempUserInfo.gender = "m";
}else if (tempUserInfo.gender === "女"){
tempUserInfo.gender = "f";
}
setUserInfo({ ...tempUserInfo });
// 获取userid
// const userid = localStorage.getItem("userid");
// const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
try {
const res = await fetch(`${API_BASE_URL}/api/change-profile`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ userid, ...tempUserInfo }),
});
if (res.ok) {
alert("信息已保存!");
} else {
alert("保存失败,请重试。");
}
} catch (err) {
alert("保存失败,请检查网络连接。");
console.error("保存用户信息失败", err);
}
};
const handleAvatarClick = () => {
const avatarUrl = prompt("请输入头像的URL:");
if (avatarUrl) {
setTempUserInfo({ ...tempUserInfo, avatar_url: avatarUrl });
}
};
// 邀请
const handleInvite = async () => {
if (!inviteEmail) {
setInviteStatus("请输入邀请邮箱");
return;
}
// 获取userid
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) {
setInviteStatus("未获取到用户ID");
return;
}
if (userInfo.invite_left <= 0) {
setInviteStatus("邀请次数已用完");
return;
}
try {
const res = await fetch(`${API_BASE_URL}/api/invite`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userid, invite_email: inviteEmail }),
});
if (res.ok) {
const data = await res.json();
setInviteStatus("邀请成功");
// 更新剩余次数
const left = data.invite_left !== undefined ? data.invite_left : userInfo.invite_left - 1;
setUserInfo(prev => ({ ...prev, invite_left: left }));
setTempUserInfo(prev => ({ ...prev, invite_left: left }));
setInviteEmail('');
} else {
const errorText = await res.text();
setInviteStatus("邀请失败:" + errorText);
}
} catch (err) {
console.error("邀请失败", err);
setInviteStatus("邀请失败,请检查网络");
}
}; // 兑换
const handleExchange = async () => {
const magic = Number(exchangeMagic);
if (!magic || isNaN(magic) || magic <= 0) return;
if (magic > userStats.magic) {
alert("魔力值不足!");
return;
}
// 检查兑换结果是否为整数
const calculatedExchangeResult = magic / exchangeRate[exchangeType];
if (!Number.isInteger(calculatedExchangeResult)) {
alert("兑换结果必须为整数,请调整魔力值!");
return;
}
// 获取userid
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) {
alert("未获取到用户ID");
return;
}
console.log("兑换请求参数:", { userid, magic, exchangeType, exchangeResult: calculatedExchangeResult });
try {
// 发送兑换请求到后端
const res = await fetch(`${API_BASE_URL}/api/exchange`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userid,
magic,
exchangeType,
exchangeResult: calculatedExchangeResult
}),
});
// console.log("兑换请求结果:", res);
if (res.ok) {
// 兑换成功后重新获取用户数据
const statsRes = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
if (statsRes.ok) {
const updatedStats = await statsRes.json();
setUserStats(updatedStats);
}
setExchangeMagic('');
alert("兑换成功!");
} else {
const errorText = await res.text();
alert("兑换失败:" + errorText);
}
} catch (err) {
console.error("兑换失败", err);
alert("兑换失败,请检查网络");
}
};
// 删除种子
const handleDeleteSeed = (seedid) => {
setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));
};
// 取消收藏
const handleRemoveFavorite = async (seedid) => {
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/remove-favorite`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userid, seedid }),
}); if (res.ok) {
setUserFavorites(userFavorites.filter((s) => (s.seedid || s.seed_id) !== seedid));
alert('已取消收藏');
} else {
alert('取消收藏失败,请重试');
}
} catch (err) {
console.error('取消收藏失败', err);
alert('取消收藏失败,请检查网络');
}
};
// 申诉提交逻辑
const handleAppealSubmit = async () => {
if (!appealTitle || !appealFile) return;
// 获取userid
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const userid = match ? match[2] : null;
if (!userid) {
alert('未获取到用户ID');
return;
}
// 构建表单数据
const formData = new FormData();
formData.append('userid', userid);
formData.append('content', appealTitle);
formData.append('file', appealFile);
try {
const res = await fetch(`${API_BASE_URL}/api/submit-appeal`, {
method: 'POST',
body: formData,
});
if (res.ok) {
alert('申诉已提交');
setAppealOpen(false);
setAppealTitle('');
setAppealFile(null);
} else {
const errorText = await res.text();
alert('申诉失败:' + errorText);
}
} catch (err) {
console.error('申诉失败', err);
alert('申诉失败,请检查网络');
}
};
// 账号迁移提交逻辑
const handleMigrationSubmit = async () => {
if (!appealFile) {
setMigrationStatus('请选择PDF文件');
return;
}
// 获取当前用户ID
const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
const currentUserId = match ? match[2] : null;
if (!currentUserId) {
setMigrationStatus('未获取到当前用户ID');
return;
}
try {
// 构建表单数据
const formData = new FormData();
formData.append('userid', currentUserId);
formData.append('file', appealFile);
const res = await fetch(`${API_BASE_URL}/api/migrate-account`, {
method: 'POST',
body: formData,
});
if (res.ok) {
setMigrationStatus('账号迁移申请已提交,请等待管理员审核');
setTimeout(() => {
setMigrationOpen(false);
setAppealFile(null);
setMigrationStatus('');
}, 2000);
} else {
const errorText = await res.text();
setMigrationStatus('迁移失败:' + errorText);
}
} catch (err) {
console.error('账号迁移失败', err);
setMigrationStatus('迁移失败,请检查网络');
}
};
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>
<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('删除失败,请检查网络');
}
}}
>删除</Button>
</li>
))}
</ul>
)}
</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>
{/* 申诉弹窗 */}
<Dialog open={appealOpen} onClose={() => setAppealOpen(false)}>
<DialogTitle>提交申诉</DialogTitle>
<DialogContent>
<div style={{ marginBottom: 16 }}>
<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 }}>
</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文件(最大10MB
</div>
</div>
{migrationStatus && (
<div style={{ color: migrationStatus.includes('成功') ? '#43a047' : '#e53935', fontSize: 14, marginTop: 8 }}>
{migrationStatus}
</div>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleMigrationSubmit} variant="contained" color="primary">提交迁移</Button>
<Button onClick={() => setMigrationOpen(false)} variant="outlined">取消</Button>
</DialogActions>
</Dialog>
</div>
);
}