blob: eed84dafa0bcde49ca630d0ee105bb998bb861cb [file] [log] [blame]
rhjc6a4ee02025-06-06 00:45:18 +08001import React, { useState, useEffect } from "react";
956303669a32fc2c2025-06-02 19:45:53 +08002import AccountCircleIcon from "@mui/icons-material/AccountCircle";
wht15642182025-06-08 00:16:52 +08003import Button from '@mui/material/Button';
4import TextField from '@mui/material/TextField';
5import MenuItem from '@mui/material/MenuItem';
6import Dialog from '@mui/material/Dialog';
7import DialogTitle from '@mui/material/DialogTitle';
8import DialogContent from '@mui/material/DialogContent';
9import DialogActions from '@mui/material/DialogActions';
956303669a32fc2c2025-06-02 19:45:53 +080010import { useNavigate } from "react-router-dom";
223011330f9623f2025-06-06 00:22:05 +080011import { API_BASE_URL } from "./config";
956303669a32fc2c2025-06-02 19:45:53 +080012import "./App.css";
13
14export default function UserProfile() {
15 const navigate = useNavigate();
16 const [userInfo, setUserInfo] = useState({
rhjc6a4ee02025-06-06 00:45:18 +080017 avatar_url: "",
956303669a32fc2c2025-06-02 19:45:53 +080018 username: "示例用户",
19 email: "user@example.com",
223011339e292152025-06-08 00:34:37 +080020 invitetimes: "",
956303669a32fc2c2025-06-02 19:45:53 +080021 school: "",
rhjc6a4ee02025-06-06 00:45:18 +080022 account_status: "",
23 gender: "",
956303669a32fc2c2025-06-02 19:45:53 +080024 });
25 const [tempUserInfo, setTempUserInfo] = useState({ ...userInfo });
rhjc6a4ee02025-06-06 00:45:18 +080026 const [userSeeds, setUserSeeds] = useState([]);
wht15642182025-06-08 00:16:52 +080027 const [userFavorites, setUserFavorites] = useState([]);
rhjc6a4ee02025-06-06 00:45:18 +080028 const [userStats, setUserStats] = useState({
29 magic: 0,
30 upload: 0,
wht2bf8f802025-06-08 15:52:18 +080031 viptime: 0,
rhjc6a4ee02025-06-06 00:45:18 +080032 ratio: 0,
33 });
34
wht15642182025-06-08 00:16:52 +080035 // 邀请相关
36 const [inviteEmail, setInviteEmail] = useState('');
37 const [inviteStatus, setInviteStatus] = useState('');
38
39 // 兑换相关
40 const [exchangeType, setExchangeType] = useState('uploaded');
41 const [exchangeMagic, setExchangeMagic] = useState('');
42 const [exchangeResult, setExchangeResult] = useState(0);
43
44 // 兑换比例
wht2bf8f802025-06-08 15:52:18 +080045 const exchangeRate = { uploaded: 0.1, downloaded: 0.1, vip_downloads: 100 };
wht15642182025-06-08 00:16:52 +080046
47 // 用户申诉相关
48 const [appealOpen, setAppealOpen] = useState(false);
49 const [appealTitle, setAppealTitle] = useState('');
50 const [appealFile, setAppealFile] = useState(null);
51
wht2bf8f802025-06-08 15:52:18 +080052 // 账号迁移相关
53 const [migrationOpen, setMigrationOpen] = useState(false);
54 const [migrationEmail, setMigrationEmail] = useState('');
55 const [migrationPassword, setMigrationPassword] = useState('');
56 const [migrationStatus, setMigrationStatus] = useState('');
wht15642182025-06-08 00:16:52 +080057 // 兑换结果计算
58 React.useEffect(() => {
59 if (!exchangeMagic || isNaN(exchangeMagic)) {
60 setExchangeResult(0);
61 return;
62 }
63 setExchangeResult(Number(exchangeMagic) / exchangeRate[exchangeType]);
64 }, [exchangeMagic, exchangeType]);
65
66 // 获取用户信息
rhjc6a4ee02025-06-06 00:45:18 +080067 useEffect(() => {
68 const fetchUserInfo = async () => {
223011339e292152025-06-08 00:34:37 +080069 // 假设userid存储在localStorage或其他地方
70 // const userid = localStorage.getItem("userid");
71 // const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
72 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
73 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +080074 if (!userid) return;
75 try {
223011330f9623f2025-06-06 00:22:05 +080076 const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);
rhjc6a4ee02025-06-06 00:45:18 +080077 if (res.ok) {
78 const data = await res.json();
wht2bf8f802025-06-08 15:52:18 +080079 // console.log("获取用户信息:", data);
rhjc6a4ee02025-06-06 00:45:18 +080080 setUserInfo(data);
81 setTempUserInfo(data);
82 }
83 } catch (err) {
rhjc6a4ee02025-06-06 00:45:18 +080084 console.error("获取用户信息失败", err);
85 }
86 };
87 fetchUserInfo();
88 }, []);
89
wht15642182025-06-08 00:16:52 +080090 // 获取上传种子
rhjc6a4ee02025-06-06 00:45:18 +080091 useEffect(() => {
92 const fetchUserSeeds = async () => {
223011339e292152025-06-08 00:34:37 +080093 // const userid = localStorage.getItem("userid");
94 // const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
95 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
96 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +080097 if (!userid) return;
98 try {
223011330f9623f2025-06-06 00:22:05 +080099 const res = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userid}`);
rhjc6a4ee02025-06-06 00:45:18 +0800100 if (res.ok) {
101 const data = await res.json();
102 setUserSeeds(data);
103 }
104 } catch (err) {
105 console.error("获取种子列表失败", err);
106 }
107 };
108 fetchUserSeeds();
109 }, []);
wht2bf8f802025-06-08 15:52:18 +0800110 // 获取收藏种子
wht15642182025-06-08 00:16:52 +0800111 useEffect(() => {
wht2bf8f802025-06-08 15:52:18 +0800112 const fetchUserFavorites = async () => {
113 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
114 const userid = match ? match[2] : null;
115 if (!userid) return;
116 try {
117 const res = await fetch(`${API_BASE_URL}/api/user-favorites?userid=${userid}`);
118 if (res.ok) {
119 const data = await res.json();
120 // console.log("获取收藏种子列表:", data);
121 setUserFavorites(data);
122 }
123 } catch (err) {
124 console.error("获取收藏种子列表失败", err);
125 }
126 };
127 fetchUserFavorites();
wht15642182025-06-08 00:16:52 +0800128 }, []);
wht15642182025-06-08 00:16:52 +0800129 // 获取活跃度
rhjc6a4ee02025-06-06 00:45:18 +0800130 useEffect(() => {
131 const fetchUserStats = async () => {
wht2bf8f802025-06-08 15:52:18 +0800132 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
133 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +0800134 if (!userid) return;
135 try {
223011330f9623f2025-06-06 00:22:05 +0800136 const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
rhjc6a4ee02025-06-06 00:45:18 +0800137 if (res.ok) {
138 const data = await res.json();
139 setUserStats(data);
140 }
141 } catch (err) {
142 console.error("获取活跃度信息失败", err);
143 }
144 };
145 fetchUserStats();
146 }, []);
956303669a32fc2c2025-06-02 19:45:53 +0800147
148 const handleInputChange = (field, value) => {
149 setTempUserInfo({ ...tempUserInfo, [field]: value });
150 };
151
rhjc6a4ee02025-06-06 00:45:18 +0800152 const handleSave = async () => {
223011339e292152025-06-08 00:34:37 +0800153 if (tempUserInfo.gender === "男"){
223011330f9623f2025-06-06 00:22:05 +0800154 tempUserInfo.gender = "m";
223011339e292152025-06-08 00:34:37 +0800155 }else if (tempUserInfo.gender === "女"){
223011330f9623f2025-06-06 00:22:05 +0800156 tempUserInfo.gender = "f";
157 }
956303669a32fc2c2025-06-02 19:45:53 +0800158 setUserInfo({ ...tempUserInfo });
223011339e292152025-06-08 00:34:37 +0800159 // 获取userid
160 // const userid = localStorage.getItem("userid");
161 // const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid
162 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
163 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +0800164 try {
223011330f9623f2025-06-06 00:22:05 +0800165 const res = await fetch(`${API_BASE_URL}/api/change-profile`, {
rhjc6a4ee02025-06-06 00:45:18 +0800166 method: 'POST',
167 headers: {
168 'Content-Type': 'application/json',
169 },
170 body: JSON.stringify({ userid, ...tempUserInfo }),
171 });
172 if (res.ok) {
173 alert("信息已保存!");
174 } else {
175 alert("保存失败,请重试。");
176 }
177 } catch (err) {
178 alert("保存失败,请检查网络连接。");
179 console.error("保存用户信息失败", err);
180 }
956303669a32fc2c2025-06-02 19:45:53 +0800181 };
182
183 const handleAvatarClick = () => {
223011339e292152025-06-08 00:34:37 +0800184 const avatarUrl = prompt("请输入头像的URL:");
185 if (avatarUrl) {
186 setTempUserInfo({ ...tempUserInfo, avatar_url: avatarUrl });
956303669a32fc2c2025-06-02 19:45:53 +0800187 }
188 };
189
wht15642182025-06-08 00:16:52 +0800190 // 邀请
wht2bf8f802025-06-08 15:52:18 +0800191 const handleInvite = async () => {
192 if (!inviteEmail) {
193 setInviteStatus("请输入邀请邮箱");
194 return;
195 }
196 // 获取userid
197 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
198 const userid = match ? match[2] : null;
199 if (!userid) {
200 setInviteStatus("未获取到用户ID");
201 return;
202 }
wht15642182025-06-08 00:16:52 +0800203 if (userInfo.invite_left <= 0) {
204 setInviteStatus("邀请次数已用完");
205 return;
206 }
wht2bf8f802025-06-08 15:52:18 +0800207 try {
208 const res = await fetch(`${API_BASE_URL}/api/invite`, {
209 method: 'POST',
210 headers: { 'Content-Type': 'application/json' },
211 body: JSON.stringify({ userid, invite_email: inviteEmail }),
212 });
213 if (res.ok) {
214 const data = await res.json();
215 setInviteStatus("邀请成功");
216 // 更新剩余次数
217 const left = data.invite_left !== undefined ? data.invite_left : userInfo.invite_left - 1;
218 setUserInfo(prev => ({ ...prev, invite_left: left }));
219 setTempUserInfo(prev => ({ ...prev, invite_left: left }));
220 setInviteEmail('');
221 } else {
222 const errorText = await res.text();
223 setInviteStatus("邀请失败:" + errorText);
224 }
225 } catch (err) {
226 console.error("邀请失败", err);
227 setInviteStatus("邀请失败,请检查网络");
228 }
229 }; // 兑换
230 const handleExchange = async () => {
wht15642182025-06-08 00:16:52 +0800231 const magic = Number(exchangeMagic);
232 if (!magic || isNaN(magic) || magic <= 0) return;
233 if (magic > userStats.magic) {
234 alert("魔力值不足!");
235 return;
236 }
wht2bf8f802025-06-08 15:52:18 +0800237
238 // 检查兑换结果是否为整数
239 const calculatedExchangeResult = magic / exchangeRate[exchangeType];
240 if (!Number.isInteger(calculatedExchangeResult)) {
241 alert("兑换结果必须为整数,请调整魔力值!");
242 return;
wht15642182025-06-08 00:16:52 +0800243 }
wht2bf8f802025-06-08 15:52:18 +0800244
245 // 获取userid
246 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
247 const userid = match ? match[2] : null;
248 if (!userid) {
249 alert("未获取到用户ID");
250 return;
251 }
wht15642182025-06-08 00:16:52 +0800252
wht2bf8f802025-06-08 15:52:18 +0800253 console.log("兑换请求参数:", { userid, magic, exchangeType, exchangeResult: calculatedExchangeResult });
254 try {
255 // 发送兑换请求到后端
256 const res = await fetch(`${API_BASE_URL}/api/exchange`, {
257 method: 'POST',
258 headers: { 'Content-Type': 'application/json' },
259 body: JSON.stringify({
260 userid,
261 magic,
262 exchangeType,
263 exchangeResult: calculatedExchangeResult
264 }),
265 });
266 // console.log("兑换请求结果:", res);
267 if (res.ok) {
268 // 兑换成功后重新获取用户数据
269 const statsRes = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
270 if (statsRes.ok) {
271 const updatedStats = await statsRes.json();
272 setUserStats(updatedStats);
273 }
274 setExchangeMagic('');
275 alert("兑换成功!");
276 } else {
277 const errorText = await res.text();
278 alert("兑换失败:" + errorText);
279 }
280 } catch (err) {
281 console.error("兑换失败", err);
282 alert("兑换失败,请检查网络");
283 }
284 };
wht15642182025-06-08 00:16:52 +0800285 // 删除种子
286 const handleDeleteSeed = (seedid) => {
287 setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));
288 };
289
wht2bf8f802025-06-08 15:52:18 +0800290 // 取消收藏
291 const handleRemoveFavorite = async (seedid) => {
292 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
293 const userid = match ? match[2] : null;
294 if (!userid) {
295 alert('未获取到用户ID');
296 return;
297 }
298 try {
299 const res = await fetch(`${API_BASE_URL}/api/remove-favorite`, {
300 method: 'POST',
301 headers: { 'Content-Type': 'application/json' },
302 body: JSON.stringify({ userid, seedid }),
303 }); if (res.ok) {
304 setUserFavorites(userFavorites.filter((s) => (s.seedid || s.seed_id) !== seedid));
305 alert('已取消收藏');
306 } else {
307 alert('取消收藏失败,请重试');
308 }
309 } catch (err) {
310 console.error('取消收藏失败', err);
311 alert('取消收藏失败,请检查网络');
312 }
313 };
314
wht15642182025-06-08 00:16:52 +0800315 // 申诉提交逻辑
wht2bf8f802025-06-08 15:52:18 +0800316 const handleAppealSubmit = async () => {
317 if (!appealTitle || !appealFile) return;
318 // 获取userid
319 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
320 const userid = match ? match[2] : null;
321 if (!userid) {
322 alert('未获取到用户ID');
323 return;
324 }
325 // 构建表单数据
326 const formData = new FormData();
327 formData.append('userid', userid);
328 formData.append('content', appealTitle);
329 formData.append('file', appealFile);
330 try {
331 const res = await fetch(`${API_BASE_URL}/api/submit-appeal`, {
332 method: 'POST',
333 body: formData,
334 });
335 if (res.ok) {
336 alert('申诉已提交');
337 setAppealOpen(false);
338 setAppealTitle('');
339 setAppealFile(null);
340 } else {
341 const errorText = await res.text();
342 alert('申诉失败:' + errorText);
343 }
344 } catch (err) {
345 console.error('申诉失败', err);
346 alert('申诉失败,请检查网络');
347 }
348 };
349 // 账号迁移提交逻辑
350 const handleMigrationSubmit = async () => {
351 if (!appealFile) {
352 setMigrationStatus('请选择PDF文件');
353 return;
354 }
355
356 // 获取当前用户ID
357 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
358 const currentUserId = match ? match[2] : null;
359 if (!currentUserId) {
360 setMigrationStatus('未获取到当前用户ID');
361 return;
362 }
363
364 try {
365 // 构建表单数据
366 const formData = new FormData();
367 formData.append('userid', currentUserId);
368 formData.append('file', appealFile);
369
370 const res = await fetch(`${API_BASE_URL}/api/migrate-account`, {
371 method: 'POST',
372 body: formData,
373 });
374
375 if (res.ok) {
376 setMigrationStatus('账号迁移申请已提交,请等待管理员审核');
377 setTimeout(() => {
378 setMigrationOpen(false);
379 setAppealFile(null);
380 setMigrationStatus('');
381 }, 2000);
382 } else {
383 const errorText = await res.text();
384 setMigrationStatus('迁移失败:' + errorText);
385 }
386 } catch (err) {
387 console.error('账号迁移失败', err);
388 setMigrationStatus('迁移失败,请检查网络');
389 }
wht15642182025-06-08 00:16:52 +0800390 };
391
956303669a32fc2c2025-06-02 19:45:53 +0800392 return (
wht15642182025-06-08 00:16:52 +0800393 <div
394 className="container"
395 style={{
396 minHeight: '100vh',
397 background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)',
398 display: 'grid',
399 gridTemplateColumns: '1.1fr 1.9fr',
400 gridTemplateRows: 'auto auto',
401 gap: '12px',
402 padding: '24px 3vw',
403 boxSizing: 'border-box'
404 }}
405 >
406 {/* 左上:用户资料 */}
407 <div style={{
408 gridColumn: '1 / 2',
409 gridRow: '1 / 2',
410 display: 'flex',
411 flexDirection: 'column',
412 alignItems: 'center',
413 background: '#fff',
414 borderRadius: 20,
415 boxShadow: '0 6px 32px #e0e7ff',
416 padding: '32px 28px',
417 minWidth: 320,
418 minHeight: 420,
419 transition: 'box-shadow 0.2s',
420 }}>
421 <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 18 }}>
956303669a32fc2c2025-06-02 19:45:53 +0800422 <div onClick={handleAvatarClick} style={{ cursor: 'pointer', position: 'relative' }}>
wht15642182025-06-08 00:16:52 +0800423 <AccountCircleIcon style={{ fontSize: 96, color: '#1a237e', marginBottom: 12 }} />
223011330f9623f2025-06-06 00:22:05 +0800424 {tempUserInfo.avatar_url && (
956303669a32fc2c2025-06-02 19:45:53 +0800425 <img
rhjc6a4ee02025-06-06 00:45:18 +0800426 src={tempUserInfo.avatar_url}
956303669a32fc2c2025-06-02 19:45:53 +0800427 alt="用户头像"
428 style={{
429 position: 'absolute',
430 top: 0,
431 left: 0,
wht15642182025-06-08 00:16:52 +0800432 width: 96,
433 height: 96,
956303669a32fc2c2025-06-02 19:45:53 +0800434 borderRadius: '50%',
435 objectFit: 'cover',
wht15642182025-06-08 00:16:52 +0800436 border: '2px solid #e0e7ff',
437 boxShadow: '0 2px 8px #bfcfff'
956303669a32fc2c2025-06-02 19:45:53 +0800438 }}
439 />
440 )}
441 </div>
wht15642182025-06-08 00:16:52 +0800442 <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 26, letterSpacing: 1 }}>用户个人资料</h2>
956303669a32fc2c2025-06-02 19:45:53 +0800443 </div>
wht15642182025-06-08 00:16:52 +0800444 <div className="card" style={{
445 padding: 32,
446 width: '100%',
447 background: '#fff',
448 borderRadius: 18,
449 boxShadow: '0 2px 12px #e0e7ff',
450 flex: 1,
451 minWidth: 0
452 }}>
453 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800454 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>用户名:</b>
wht15642182025-06-08 00:16:52 +0800455 <TextField
456 variant="outlined"
457 size="small"
956303669a32fc2c2025-06-02 19:45:53 +0800458 value={tempUserInfo.username}
459 onChange={(e) => handleInputChange("username", e.target.value)}
wht15642182025-06-08 00:16:52 +0800460 sx={{ flex: 1, minWidth: 0 }}
956303669a32fc2c2025-06-02 19:45:53 +0800461 />
462 </div>
wht15642182025-06-08 00:16:52 +0800463 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800464 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邮箱:</b>
wht15642182025-06-08 00:16:52 +0800465 <TextField
466 variant="outlined"
467 size="small"
468 value={tempUserInfo.email}
469 InputProps={{ readOnly: true }}
470 sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
471 />
956303669a32fc2c2025-06-02 19:45:53 +0800472 </div>
wht15642182025-06-08 00:16:52 +0800473 {/* 邀请功能 */}
474 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
rhjc6a4ee02025-06-06 00:45:18 +0800475 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邀请剩余:</b>
wht15642182025-06-08 00:16:52 +0800476 <TextField
477 type="email"
478 size="small"
479 placeholder="被邀请邮箱"
480 value={inviteEmail}
481 onChange={e => setInviteEmail(e.target.value)}
482 sx={{ flex: 2, marginRight: 1, minWidth: 120 }}
483 disabled={Number(tempUserInfo.invite_left) === 0}
484 />
485 <Button
486 variant="contained"
487 color="primary"
488 onClick={handleInvite}
489 disabled={Number(tempUserInfo.invite_left) === 0 || !inviteEmail}
490 sx={{ marginRight: 1, minWidth: 80 }}
491 >邀请</Button>
492 <span style={{ color: '#888', fontSize: 15 }}>剩余:{tempUserInfo.invite_left || "0"}</span>
956303669a32fc2c2025-06-02 19:45:53 +0800493 </div>
wht15642182025-06-08 00:16:52 +0800494 {inviteStatus && <div style={{ color: '#e53935', fontSize: 14, marginBottom: 8 }}>{inviteStatus}</div>}
495 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800496 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>学校:</b>
wht15642182025-06-08 00:16:52 +0800497 <TextField
498 variant="outlined"
499 size="small"
956303669a32fc2c2025-06-02 19:45:53 +0800500 value={tempUserInfo.school}
501 onChange={(e) => handleInputChange("school", e.target.value)}
wht15642182025-06-08 00:16:52 +0800502 sx={{ flex: 1, minWidth: 0 }}
956303669a32fc2c2025-06-02 19:45:53 +0800503 />
504 </div>
wht15642182025-06-08 00:16:52 +0800505 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
rhjc6a4ee02025-06-06 00:45:18 +0800506 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>账号状态:</b>
wht15642182025-06-08 00:16:52 +0800507 <TextField
508 variant="outlined"
509 size="small"
510 value={tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}
511 InputProps={{ readOnly: true }}
512 sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
513 />
514 <span style={{
515 display: 'inline-block',
516 width: 12,
517 height: 12,
518 borderRadius: '50%',
519 backgroundColor: tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? '#e53935' : '#43a047',
520 marginLeft: 10,
521 border: '1px solid #b2b2b2',
522 }} />
956303669a32fc2c2025-06-02 19:45:53 +0800523 </div>
wht15642182025-06-08 00:16:52 +0800524 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800525 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>性别:</b>
wht15642182025-06-08 00:16:52 +0800526 <TextField
527 select
528 variant="outlined"
529 size="small"
530 value={tempUserInfo.gender}
531 onChange={e => handleInputChange("gender", e.target.value)}
532 sx={{ flex: 1, minWidth: 0 }}
533 >
534 <MenuItem value="m">男性</MenuItem>
535 <MenuItem value="f">女性</MenuItem>
536 </TextField>
wht2bf8f802025-06-08 15:52:18 +0800537 </div> <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>
wht15642182025-06-08 00:16:52 +0800538 <Button
539 variant="contained"
540 color="primary"
541 onClick={handleSave}
wht2bf8f802025-06-08 15:52:18 +0800542 sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
wht15642182025-06-08 00:16:52 +0800543 >保存</Button>
544 <Button
545 variant="contained"
546 color="error"
547 onClick={() => setAppealOpen(true)}
wht2bf8f802025-06-08 15:52:18 +0800548 sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
wht15642182025-06-08 00:16:52 +0800549 >用户申诉</Button>
wht2bf8f802025-06-08 15:52:18 +0800550 <Button
551 variant="contained"
552 color="warning"
553 onClick={() => setMigrationOpen(true)}
554 sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
555 >账号迁移</Button>
wht15642182025-06-08 00:16:52 +0800556 </div>
956303669a32fc2c2025-06-02 19:45:53 +0800557 </div>
558 </div>
wht15642182025-06-08 00:16:52 +0800559 {/* 左下:活跃度模块 */}
560 <div style={{
561 gridColumn: '1 / 2',
562 gridRow: '2 / 3',
563 background: '#fff',
564 borderRadius: 20,
565 boxShadow: '0 6px 32px #e0e7ff',
566 padding: '32px 28px',
567 minWidth: 320,
568 minHeight: 320,
569 display: 'flex',
570 flexDirection: 'column',
571 justifyContent: 'center'
572 }}>
573 <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>活跃度</h3>
574 <div style={{
575 border: '1.5px dashed #b2b2b2',
576 borderRadius: 14,
577 minHeight: 80,
578 padding: 22,
579 display: 'flex',
580 flexDirection: 'column',
581 gap: 14,
582 fontSize: 18,
583 background: '#f8faff'
584 }}>
585 <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
586 <span>魔力值:<b style={{ color: '#1976d2' }}>{userStats.magic}</b></span>
587 <TextField
588 type="number"
589 size="small"
590 placeholder="输入兑换魔力值"
591 value={exchangeMagic}
592 onChange={e => setExchangeMagic(e.target.value)}
593 sx={{ width: 100, marginLeft: 2, marginRight: 1 }}
594 />
595 <TextField
596 select
597 size="small"
598 value={exchangeType}
599 onChange={e => setExchangeType(e.target.value)}
600 sx={{ minWidth: 120 }}
601 >
602 <MenuItem value="uploaded">上传量(增加)</MenuItem>
603 <MenuItem value="downloaded">下载量(减少)</MenuItem>
604 <MenuItem value="vip_downloads">VIP下载次数(增加)</MenuItem>
wht2bf8f802025-06-08 15:52:18 +0800605 </TextField> <span style={{ marginLeft: 8, color: '#43a047' }}>
606 可兑换:<b>{exchangeResult}</b> {exchangeType === 'vip_downloads' ? '次' : 'MB'}
607 {!Number.isInteger(exchangeResult) && exchangeResult > 0 && (
608 <span style={{ color: '#e53935', fontSize: '12px', marginLeft: 8 }}>
609 (结果必须为整数)
610 </span>
611 )}
612 </span><Button
wht15642182025-06-08 00:16:52 +0800613 variant="contained"
614 color="primary"
615 onClick={handleExchange}
616 disabled={
617 !exchangeMagic ||
618 isNaN(exchangeMagic) ||
619 Number(exchangeMagic) <= 0 ||
wht2bf8f802025-06-08 15:52:18 +0800620 Number(exchangeMagic) > userStats.magic ||
621 !Number.isInteger(exchangeResult)
wht15642182025-06-08 00:16:52 +0800622 }
623 sx={{
624 marginLeft: 2,
625 minWidth: 80,
wht2bf8f802025-06-08 15:52:18 +0800626 background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic || !Number.isInteger(exchangeResult)) ? '#ccc' : undefined
wht15642182025-06-08 00:16:52 +0800627 }}
628 >兑换</Button>
wht2bf8f802025-06-08 15:52:18 +0800629 </div> <div>上传量:<b style={{ color: '#43a047' }}>{(userStats.upload / 1000000)?.toFixed(2)} MB</b></div>
630 <div>下载量:<b style={{ color: '#e53935' }}>{(userStats.download / 1000000)?.toFixed(2)} MB</b></div>
wht15642182025-06-08 00:16:52 +0800631 <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}</b></div>
wht2bf8f802025-06-08 15:52:18 +0800632 <div>VIP下载次数:<b style={{ color: '#1976d2' }}>{userStats.viptime}</b></div>
wht15642182025-06-08 00:16:52 +0800633 </div>
634 </div>
635 {/* 右上:个人上传种子列表 */}
636 <div style={{
637 gridColumn: '2 / 3',
638 gridRow: '1 / 2',
639 background: '#fff',
640 borderRadius: 20,
641 boxShadow: '0 6px 32px #e0e7ff',
642 padding: '32px 36px',
643 minHeight: 420,
644 display: 'flex',
645 flexDirection: 'column'
646 }}>
647 <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人上传种子列表</h3>
648 <div style={{
649 border: '1.5px dashed #b2b2b2',
650 borderRadius: 14,
651 minHeight: 80,
652 padding: 16,
653 background: '#f8faff'
654 }}>
rhjc6a4ee02025-06-06 00:45:18 +0800655 {userSeeds.length === 0 ? (
656 <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无上传种子)</div>
657 ) : (
658 <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
659 {userSeeds.map((seed, idx) => (
660 <li
wht15642182025-06-08 00:16:52 +0800661 key={seed.seedid || idx}
662 style={{
663 display: 'flex',
664 alignItems: 'center',
665 padding: '12px 0',
666 borderBottom: idx === userSeeds.length - 1 ? 'none' : '1px solid #e0e7ff',
667 cursor: 'pointer',
668 transition: 'background 0.15s'
669 }}
rhjc6a4ee02025-06-06 00:45:18 +0800670 onClick={e => {
rhjc6a4ee02025-06-06 00:45:18 +0800671 if (e.target.classList.contains('delete-btn')) return;
223011339e292152025-06-08 00:34:37 +0800672 navigate(`/torrent/${seed.seed_id}`);
rhjc6a4ee02025-06-06 00:45:18 +0800673 }}
wht15642182025-06-08 00:16:52 +0800674 onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}
675 onMouseOut={e => e.currentTarget.style.background = ''}
rhjc6a4ee02025-06-06 00:45:18 +0800676 >
677 <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline' }}>{seed.title}</span>
678 <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.tags}</span>
223011330f9623f2025-06-06 00:22:05 +0800679 <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.downloadtimes}</span>
wht15642182025-06-08 00:16:52 +0800680 <Button
rhjc6a4ee02025-06-06 00:45:18 +0800681 className="delete-btn"
wht15642182025-06-08 00:16:52 +0800682 variant="contained"
683 color="error"
684 size="small"
wht2bf8f802025-06-08 15:52:18 +0800685 sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }} onClick={async e => {
rhjc6a4ee02025-06-06 00:45:18 +0800686 e.stopPropagation();
wht2bf8f802025-06-08 15:52:18 +0800687 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
688 const userid = match ? match[2] : null;
689 if (!userid) {
690 alert('未获取到用户ID');
691 return;
692 }
223011339e292152025-06-08 00:34:37 +0800693 try {
694 const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {
695 method: 'POST',
696 headers: { 'Content-Type': 'application/json' },
697 body: JSON.stringify({ seed_id: seed.seed_id, userid }),
698 });
699 if (res.ok) {
700 setUserSeeds(userSeeds.filter((s, i) => (s.seed_id || i) !== (seed.seed_id || idx)));
701 } else {
702 alert('删除失败,请重试');
703 }
704 } catch (err) {
705 alert('删除失败,请检查网络');
706 }
rhjc6a4ee02025-06-06 00:45:18 +0800707 }}
wht15642182025-06-08 00:16:52 +0800708 >删除</Button>
rhjc6a4ee02025-06-06 00:45:18 +0800709 </li>
710 ))}
711 </ul>
712 )}
956303669a32fc2c2025-06-02 19:45:53 +0800713 </div>
714 </div>
wht15642182025-06-08 00:16:52 +0800715 {/* 右下:个人收藏种子列表 */}
716 <div style={{
717 gridColumn: '2 / 3',
718 gridRow: '2 / 3',
719 background: '#fff',
720 borderRadius: 20,
721 boxShadow: '0 6px 32px #e0e7ff',
722 padding: '32px 36px',
723 minHeight: 320,
724 display: 'flex',
725 flexDirection: 'column'
726 }}>
727 <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人收藏种子列表</h3>
728 <div style={{
729 border: '1.5px dashed #b2b2b2',
730 borderRadius: 14,
731 minHeight: 80,
732 padding: 16,
733 background: '#f8faff'
734 }}>
735 {userFavorites.length === 0 ? (
736 <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无收藏种子)</div>
wht2bf8f802025-06-08 15:52:18 +0800737 ) : ( <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
wht15642182025-06-08 00:16:52 +0800738 {userFavorites.map((seed, idx) => (
739 <li
740 key={seed.seedid || idx}
741 style={{
742 display: 'flex',
743 alignItems: 'center',
744 padding: '12px 0',
745 borderBottom: idx === userFavorites.length - 1 ? 'none' : '1px solid #e0e7ff',
746 cursor: 'pointer',
747 transition: 'background 0.15s'
wht2bf8f802025-06-08 15:52:18 +0800748 }} onClick={e => { if (e.target.classList.contains('remove-favorite-btn')) return;
749 navigate(`/torrent/${seed.seedid || seed.seed_id}`);
wht15642182025-06-08 00:16:52 +0800750 }}
751 onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}
752 onMouseOut={e => e.currentTarget.style.background = ''}
753 >
wht2bf8f802025-06-08 15:52:18 +0800754 <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline', cursor: 'pointer' }}>{seed.seed.title}</span>
755 <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.seed.tags}</span>
756 <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.seed.downloadtimes}</span>
757 <Button
758 className="remove-favorite-btn"
759 variant="contained"
760 color="warning"
761 size="small"
762 sx={{ marginLeft: 2, borderRadius: 1, minWidth: 80 }} onClick={e => {
763 e.stopPropagation();
764 handleRemoveFavorite(seed.seedid || seed.seed_id);
765 }}
766 >取消收藏</Button>
wht15642182025-06-08 00:16:52 +0800767 </li>
768 ))}
769 </ul>
770 )}
956303669a32fc2c2025-06-02 19:45:53 +0800771 </div>
772 </div>
wht15642182025-06-08 00:16:52 +0800773 {/* 申诉弹窗 */}
774 <Dialog open={appealOpen} onClose={() => setAppealOpen(false)}>
775 <DialogTitle>提交申诉</DialogTitle>
776 <DialogContent>
777 <div style={{ marginBottom: 16 }}>
778 <TextField
779 label="申诉主题"
780 fullWidth
781 value={appealTitle}
782 onChange={e => setAppealTitle(e.target.value)}
783 size="small"
784 />
wht2bf8f802025-06-08 15:52:18 +0800785 </div> <div>
wht15642182025-06-08 00:16:52 +0800786 <input
787 type="file"
wht2bf8f802025-06-08 15:52:18 +0800788 accept=".pdf"
789 onChange={e => {
790 const file = e.target.files[0];
791 if (file && file.type !== 'application/pdf') {
792 alert('请选择PDF文件');
793 e.target.value = '';
794 setAppealFile(null);
795 } else {
796 setAppealFile(file);
797 }
798 }}
wht15642182025-06-08 00:16:52 +0800799 style={{ marginTop: 8 }}
800 />
wht2bf8f802025-06-08 15:52:18 +0800801 <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>
802 请选择PDF文件(最大100MB
803 </div>
wht15642182025-06-08 00:16:52 +0800804 </div>
805 </DialogContent>
806 <DialogActions>
807 <Button onClick={handleAppealSubmit} variant="contained" color="primary" disabled={!appealTitle || !appealFile}>提交</Button>
808 <Button onClick={() => setAppealOpen(false)} variant="outlined">取消</Button>
809 </DialogActions>
wht2bf8f802025-06-08 15:52:18 +0800810 </Dialog> {/* 账号迁移弹窗 */}
811 <Dialog open={migrationOpen} onClose={() => setMigrationOpen(false)}>
812 <DialogTitle>账号迁移</DialogTitle>
813 <DialogContent>
814 <div style={{ marginBottom: 16 }}>
815 </div> <div>
816 <input
817 type="file"
818 accept=".pdf"
819 onChange={e => {
820 const file = e.target.files[0];
821 if (file && file.type !== 'application/pdf') {
822 alert('请选择PDF文件');
823 e.target.value = '';
824 setAppealFile(null);
825 } else {
826 setAppealFile(file);
827 }
828 }}
829 style={{ marginTop: 8 }}
830 />
831 <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>
832 请选择PDF文件(最大10MB
833 </div>
834 </div>
835 {migrationStatus && (
836 <div style={{ color: migrationStatus.includes('成功') ? '#43a047' : '#e53935', fontSize: 14, marginTop: 8 }}>
837 {migrationStatus}
838 </div>
839 )}
840 </DialogContent>
841 <DialogActions>
842 <Button onClick={handleMigrationSubmit} variant="contained" color="primary">提交迁移</Button>
843 <Button onClick={() => setMigrationOpen(false)} variant="outlined">取消</Button>
844 </DialogActions>
wht15642182025-06-08 00:16:52 +0800845 </Dialog>
956303669a32fc2c2025-06-02 19:45:53 +0800846 </div>
847 );
848}