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> | |
); | |
} |