修改个人主页,优化推荐系统
Change-Id: I533e36dc891b1b60fcb1e4a71b522b3ba9d77984
diff --git a/front/src/UserProfile.js b/front/src/UserProfile.js
index 16fdcd2..cf38e7f 100644
--- a/front/src/UserProfile.js
+++ b/front/src/UserProfile.js
@@ -1,5 +1,12 @@
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";
@@ -17,6 +24,7 @@
});
const [tempUserInfo, setTempUserInfo] = useState({ ...userInfo });
const [userSeeds, setUserSeeds] = useState([]);
+ const [userFavorites, setUserFavorites] = useState([]);
const [userStats, setUserStats] = useState({
magic: 0,
upload: 0,
@@ -24,35 +32,55 @@
ratio: 0,
});
- // 新增:根据userid从后端获取用户信息
+ // 邀请相关
+ 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: 10, downloaded: 10, vip_downloads: 100 };
+
+ // 用户申诉相关
+ const [appealOpen, setAppealOpen] = useState(false);
+ const [appealTitle, setAppealTitle] = useState('');
+ const [appealFile, setAppealFile] = useState(null);
+
+ // 兑换结果计算
+ 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 userid = "550e8400-e29b-41d4-a716-446655440000";
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);
-
if (res.ok) {
const data = await res.json();
setUserInfo(data);
setTempUserInfo(data);
- // console.log(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 userid = "550e8400-e29b-41d4-a716-446655440000";
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userid}`);
@@ -67,11 +95,17 @@
fetchUserSeeds();
}, []);
- // 动态加载用户活跃度信息
+ // 收藏种子(示例数据)
+ useEffect(() => {
+ setUserFavorites([
+ { seedid: 'fav1', title: '收藏种子1', tags: '标签A', downloadtimes: 10 },
+ ]);
+ }, []);
+
+ // 获取活跃度
useEffect(() => {
const fetchUserStats = async () => {
- // const userid = localStorage.getItem("userid");
- const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
+ const userid = "550e8400-e29b-41d4-a716-446655440000";
if (!userid) return;
try {
const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
@@ -97,10 +131,7 @@
tempUserInfo.gender = "f";
}
setUserInfo({ ...tempUserInfo });
- console.log("保存的用户信息:", tempUserInfo);
- // 获取userid
- // const userid = localStorage.getItem("userid");
- const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
+ const userid = "550e8400-e29b-41d4-a716-446655440000";
try {
const res = await fetch(`${API_BASE_URL}/api/change-profile`, {
method: 'POST',
@@ -127,13 +158,92 @@
}
};
+ // 邀请
+ const handleInvite = () => {
+ if (!inviteEmail) return;
+ if (userInfo.invite_left <= 0) {
+ setInviteStatus("邀请次数已用完");
+ return;
+ }
+ setInviteStatus("邀请成功!(示例,无后端)");
+ setUserInfo((prev) => ({
+ ...prev,
+ invite_left: prev.invite_left - 1,
+ }));
+ setTempUserInfo((prev) => ({
+ ...prev,
+ invite_left: prev.invite_left - 1,
+ }));
+ setInviteEmail('');
+ };
+
+ // 兑换
+ const handleExchange = () => {
+ const magic = Number(exchangeMagic);
+ if (!magic || isNaN(magic) || magic <= 0) return;
+ if (magic > userStats.magic) {
+ alert("魔力值不足!");
+ return;
+ }
+ let newStats = { ...userStats };
+ if (exchangeType === "uploaded") {
+ newStats.upload += magic / exchangeRate.uploaded;
+ } else if (exchangeType === "downloaded") {
+ newStats.download = Math.max(0, newStats.download - magic / exchangeRate.downloaded);
+ } else if (exchangeType === "vip_downloads") {
+ newStats.vip_downloads += magic / exchangeRate.vip_downloads;
+ }
+ newStats.magic -= magic;
+ setUserStats(newStats);
+ setExchangeMagic('');
+ alert("兑换成功!(示例,无后端)");
+ };
+
+ // 删除种子
+ const handleDeleteSeed = (seedid) => {
+ setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));
+ };
+
+ // 申诉提交逻辑
+ const handleAppealSubmit = () => {
+ alert('申诉已提交!(示例,无后端)');
+ setAppealOpen(false);
+ setAppealTitle('');
+ setAppealFile(null);
+ };
+
return (
- <div className="container" style={{ minHeight: '100vh', background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)', display: 'grid', gridTemplateColumns: '1fr 2fr', gridTemplateRows: 'auto 1fr', gap: '20px', padding: '40px' }}>
- {/* 左侧:用户资料 */}
- <div style={{ gridColumn: '1 / 2', gridRow: '1 / 3', display: 'flex', flexDirection: 'column', alignItems: 'center', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 16 }}>
+ <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: 90, color: '#1a237e', marginBottom: 12 }} />
+ <AccountCircleIcon style={{ fontSize: 96, color: '#1a237e', marginBottom: 12 }} />
{tempUserInfo.avatar_url && (
<img
src={tempUserInfo.avatar_url}
@@ -142,204 +252,339 @@
position: 'absolute',
top: 0,
left: 0,
- width: 90,
- height: 90,
+ 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: 24 }}>用户个人资料</h2>
+ <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 26, letterSpacing: 1 }}>用户个人资料</h2>
</div>
- <div className="card" style={{ padding: 28, width: '100%', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', flex: 1 }}>
- <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <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>
- <input
- type="text"
+ <TextField
+ variant="outlined"
+ size="small"
value={tempUserInfo.username}
onChange={(e) => handleInputChange("username", e.target.value)}
- style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ sx={{ flex: 1, minWidth: 0 }}
/>
</div>
- <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
<b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邮箱:</b>
- <span
- style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15, backgroundColor: '#f5f5f5', color: '#888' }}
- >
- {tempUserInfo.email}
- </span>
+ <TextField
+ variant="outlined"
+ size="small"
+ value={tempUserInfo.email}
+ InputProps={{ readOnly: true }}
+ sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
+ />
</div>
- <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ {/* 邀请功能 */}
+ <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
<b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邀请剩余:</b>
- <span
- style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15, backgroundColor: '#f5f5f5', color: '#888' }}
- >
- {tempUserInfo.invite_left || "0"}
- </span>
+ <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>
- <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ {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>
- <input
- type="text"
+ <TextField
+ variant="outlined"
+ size="small"
value={tempUserInfo.school}
onChange={(e) => handleInputChange("school", e.target.value)}
- style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}
+ sx={{ flex: 1, minWidth: 0 }}
/>
</div>
- <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>
+ <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
<b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>账号状态:</b>
- <span
- style={{ flex: 1, display: 'flex', alignItems: 'center', padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15, backgroundColor: '#f5f5f5', color: '#888' }}
- >
- {tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}
- <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',
- }} />
- </span>
+ <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: 18, display: 'flex', alignItems: 'center' }}>
+ <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
<b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>性别:</b>
- <div style={{ position: 'relative', flex: 1 }}>
- <button
- onClick={() => setTempUserInfo({ ...tempUserInfo, showGenderOptions: !tempUserInfo.showGenderOptions })}
- style={{
- width: '100%',
- padding: '6px 10px',
- borderRadius: 7,
- border: '1px solid #b2b2b2',
- textAlign: 'left',
- backgroundColor: '#fff',
- fontSize: 15,
- cursor: 'pointer',
- }}
- >
- {tempUserInfo.gender === 'm' ? '男性'
- : tempUserInfo.gender === 'f' ? '女性'
- : '性别'}
- </button>
- {tempUserInfo.showGenderOptions && (
- <ul
- style={{
- position: 'absolute',
- top: '100%',
- left: 0,
- right: 0,
- backgroundColor: '#fff',
- border: '1px solid #b2b2b2',
- borderRadius: 7,
- listStyle: 'none',
- margin: 0,
- padding: 0,
- zIndex: 10,
- }}
- >
- {[{ value: 'm', label: '男性' }, { value: 'f', label: '女性' }].map(opt => (
- <li
- key={opt.value}
- onClick={() => setTempUserInfo({ ...tempUserInfo, gender: opt.value, showGenderOptions: false })}
- style={{
- padding: '6px 10px',
- cursor: 'pointer',
- borderBottom: '1px solid #e0e0e0',
- backgroundColor: tempUserInfo.gender === opt.value ? '#f0f0f0' : '#fff',
- }}
- >
- {opt.label}
- </li>
- ))}
- </ul>
- )}
- </div>
+ <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>
- <button
- onClick={handleSave}
- style={{
- marginTop: 20,
- padding: '10px 20px',
- backgroundColor: '#1a237e',
- color: '#fff',
- border: 'none',
- borderRadius: 7,
- cursor: 'pointer',
- fontSize: 16,
- alignSelf: 'center', // Center the button horizontally
- }}
- onMouseOver={(e) => (e.target.style.backgroundColor = '#0d1b5e')}
- onMouseOut={(e) => (e.target.style.backgroundColor = '#1a237e')}
- >
- 保存
- </button>
+ <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={handleSave}
+ sx={{ fontSize: 16, borderRadius: 2, padding: '10px 24px' }}
+ >保存</Button>
+ <Button
+ variant="contained"
+ color="error"
+ onClick={() => setAppealOpen(true)}
+ sx={{ fontSize: 16, borderRadius: 2, padding: '10px 24px' }}
+ >用户申诉</Button>
+ </div>
</div>
</div>
- {/* 上传种子列表 */}
- <div style={{ gridColumn: '2 / 3', gridRow: '1 / 2', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>
- <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18 }}>个人上传种子列表</h3>
- <div style={{ border: '1px dashed #b2b2b2', borderRadius: 12, minHeight: 60, padding: 12 }}>
+ {/* 左下:活跃度模块 */}
+ <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' ? '次' : 'GB'}
+ </span>
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={handleExchange}
+ disabled={
+ !exchangeMagic ||
+ isNaN(exchangeMagic) ||
+ Number(exchangeMagic) <= 0 ||
+ Number(exchangeMagic) > userStats.magic
+ }
+ sx={{
+ marginLeft: 2,
+ minWidth: 80,
+ background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic) ? '#ccc' : undefined
+ }}
+ >兑换</Button>
+ </div>
+ <div>上传量:<b style={{ color: '#43a047' }}>{userStats.upload?.toFixed(2)} GB</b></div>
+ <div>下载量:<b style={{ color: '#e53935' }}>{userStats.download?.toFixed(2)} GB</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.vip_downloads}</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.seed_id || idx}
- style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid #e0e7ff', cursor: 'pointer' }}
+ 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.seedid}`);
}}
+ 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
+ <Button
className="delete-btn"
- style={{ marginLeft: 18, background: '#e53935', color: '#fff', border: 'none', borderRadius: 6, padding: '4px 14px', cursor: 'pointer', fontSize: 14 }}
- onClick={async (e) => {
+ variant="contained"
+ color="error"
+ size="small"
+ sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }}
+ onClick={e => {
e.stopPropagation();
- // const userid = localStorage.getItem("userid");
- // const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
- try {
-
- const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ seedid: seed.seedid }),
- });
- // console.log(seed.seedid);
- if (res.ok) {
- setUserSeeds(userSeeds.filter((s, i) => (s.seedid || i) !== (seed.seedid || idx)));
- } else {
- alert('删除失败,请重试');
- }
- } catch (err) {
- alert('删除失败,请检查网络');
- }
+ handleDeleteSeed(seed.seedid);
}}
- >删除</button>
+ >删除</Button>
</li>
))}
</ul>
)}
</div>
</div>
- {/* 活跃度模块 */}
- <div style={{ gridColumn: '2 / 3', gridRow: '2 / 3', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>
- <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18 }}>活跃度</h3>
- <div style={{ border: '1px dashed #b2b2b2', borderRadius: 12, minHeight: 60, padding: 18, display: 'flex', flexDirection: 'column', gap: 12, fontSize: 18 }}>
- <div>魔力值:<b style={{ color: '#1976d2' }}>{userStats.magic}</b></div>
- <div>上传量:<b style={{ color: '#43a047' }}>{userStats.upload} GB</b></div>
- <div>下载量:<b style={{ color: '#e53935' }}>{userStats.download} GB</b></div>
- <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.ratio}</b></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 => {
+ navigate(`/torrent/${seed.seedid}`);
+ }}
+ 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>
+ </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"
+ onChange={e => setAppealFile(e.target.files[0])}
+ style={{ marginTop: 8 }}
+ />
+ </div>
+ </DialogContent>
+ <DialogActions>
+ <Button onClick={handleAppealSubmit} variant="contained" color="primary" disabled={!appealTitle || !appealFile}>提交</Button>
+ <Button onClick={() => setAppealOpen(false)} variant="outlined">取消</Button>
+ </DialogActions>
+ </Dialog>
</div>
);
}
\ No newline at end of file