blob: fb44b43ca75d6c061e47de7d61f425fde44fdab4 [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);
wht338fc032025-06-09 17:16:22 +080054 const [migrationUpload, setMigrationUpload] = useState('');
wht2bf8f802025-06-08 15:52:18 +080055 const [migrationEmail, setMigrationEmail] = useState('');
56 const [migrationPassword, setMigrationPassword] = useState('');
57 const [migrationStatus, setMigrationStatus] = useState('');
wht338fc032025-06-09 17:16:22 +080058
wht15642182025-06-08 00:16:52 +080059 // 兑换结果计算
60 React.useEffect(() => {
61 if (!exchangeMagic || isNaN(exchangeMagic)) {
62 setExchangeResult(0);
63 return;
64 }
65 setExchangeResult(Number(exchangeMagic) / exchangeRate[exchangeType]);
66 }, [exchangeMagic, exchangeType]);
67
68 // 获取用户信息
rhjc6a4ee02025-06-06 00:45:18 +080069 useEffect(() => {
70 const fetchUserInfo = async () => {
223011339e292152025-06-08 00:34:37 +080071 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
wht338fc032025-06-09 17:16:22 +080072 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +080073 if (!userid) return;
74 try {
223011330f9623f2025-06-06 00:22:05 +080075 const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);
rhjc6a4ee02025-06-06 00:45:18 +080076 if (res.ok) {
77 const data = await res.json();
78 setUserInfo(data);
79 setTempUserInfo(data);
80 }
81 } catch (err) {
rhjc6a4ee02025-06-06 00:45:18 +080082 console.error("获取用户信息失败", err);
83 }
84 };
85 fetchUserInfo();
86 }, []);
87
rhjc6a4ee02025-06-06 00:45:18 +080088 useEffect(() => {
89 const fetchUserSeeds = async () => {
223011339e292152025-06-08 00:34:37 +080090 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
wht338fc032025-06-09 17:16:22 +080091 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +080092 if (!userid) return;
93 try {
223011330f9623f2025-06-06 00:22:05 +080094 const res = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userid}`);
rhjc6a4ee02025-06-06 00:45:18 +080095 if (res.ok) {
96 const data = await res.json();
97 setUserSeeds(data);
98 }
99 } catch (err) {
100 console.error("获取种子列表失败", err);
101 }
102 };
103 fetchUserSeeds();
104 }, []);
wht338fc032025-06-09 17:16:22 +0800105
wht15642182025-06-08 00:16:52 +0800106 useEffect(() => {
wht2bf8f802025-06-08 15:52:18 +0800107 const fetchUserFavorites = async () => {
108 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
109 const userid = match ? match[2] : null;
110 if (!userid) return;
111 try {
112 const res = await fetch(`${API_BASE_URL}/api/user-favorites?userid=${userid}`);
113 if (res.ok) {
114 const data = await res.json();
115 // console.log("获取收藏种子列表:", data);
116 setUserFavorites(data);
117 }
118 } catch (err) {
119 console.error("获取收藏种子列表失败", err);
120 }
121 };
122 fetchUserFavorites();
wht15642182025-06-08 00:16:52 +0800123 }, []);
wht338fc032025-06-09 17:16:22 +0800124
rhjc6a4ee02025-06-06 00:45:18 +0800125 useEffect(() => {
126 const fetchUserStats = async () => {
wht2bf8f802025-06-08 15:52:18 +0800127 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
128 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +0800129 if (!userid) return;
130 try {
223011330f9623f2025-06-06 00:22:05 +0800131 const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
rhjc6a4ee02025-06-06 00:45:18 +0800132 if (res.ok) {
133 const data = await res.json();
134 setUserStats(data);
135 }
136 } catch (err) {
137 console.error("获取活跃度信息失败", err);
138 }
139 };
140 fetchUserStats();
141 }, []);
956303669a32fc2c2025-06-02 19:45:53 +0800142
143 const handleInputChange = (field, value) => {
144 setTempUserInfo({ ...tempUserInfo, [field]: value });
145 };
146
rhjc6a4ee02025-06-06 00:45:18 +0800147 const handleSave = async () => {
wht338fc032025-06-09 17:16:22 +0800148 if (tempUserInfo.gender === "男") {
223011330f9623f2025-06-06 00:22:05 +0800149 tempUserInfo.gender = "m";
wht338fc032025-06-09 17:16:22 +0800150 } else if (tempUserInfo.gender === "女") {
223011330f9623f2025-06-06 00:22:05 +0800151 tempUserInfo.gender = "f";
152 }
956303669a32fc2c2025-06-02 19:45:53 +0800153 setUserInfo({ ...tempUserInfo });
wht338fc032025-06-09 17:16:22 +0800154
223011339e292152025-06-08 00:34:37 +0800155 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
156 const userid = match ? match[2] : null;
rhjc6a4ee02025-06-06 00:45:18 +0800157 try {
223011330f9623f2025-06-06 00:22:05 +0800158 const res = await fetch(`${API_BASE_URL}/api/change-profile`, {
rhjc6a4ee02025-06-06 00:45:18 +0800159 method: 'POST',
160 headers: {
161 'Content-Type': 'application/json',
162 },
163 body: JSON.stringify({ userid, ...tempUserInfo }),
164 });
165 if (res.ok) {
166 alert("信息已保存!");
167 } else {
168 alert("保存失败,请重试。");
169 }
170 } catch (err) {
171 alert("保存失败,请检查网络连接。");
172 console.error("保存用户信息失败", err);
173 }
956303669a32fc2c2025-06-02 19:45:53 +0800174 };
175
176 const handleAvatarClick = () => {
223011339e292152025-06-08 00:34:37 +0800177 const avatarUrl = prompt("请输入头像的URL:");
178 if (avatarUrl) {
179 setTempUserInfo({ ...tempUserInfo, avatar_url: avatarUrl });
956303669a32fc2c2025-06-02 19:45:53 +0800180 }
181 };
182
wht15642182025-06-08 00:16:52 +0800183 // 邀请
wht2bf8f802025-06-08 15:52:18 +0800184 const handleInvite = async () => {
185 if (!inviteEmail) {
186 setInviteStatus("请输入邀请邮箱");
187 return;
188 }
189 // 获取userid
190 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
191 const userid = match ? match[2] : null;
192 if (!userid) {
193 setInviteStatus("未获取到用户ID");
194 return;
195 }
wht15642182025-06-08 00:16:52 +0800196 if (userInfo.invite_left <= 0) {
197 setInviteStatus("邀请次数已用完");
198 return;
199 }
wht2bf8f802025-06-08 15:52:18 +0800200 try {
wht338fc032025-06-09 17:16:22 +0800201 const res = await fetch(`${API_BASE_URL}/api/invite`, {
wht2bf8f802025-06-08 15:52:18 +0800202 method: 'POST',
203 headers: { 'Content-Type': 'application/json' },
204 body: JSON.stringify({ userid, invite_email: inviteEmail }),
205 });
206 if (res.ok) {
207 const data = await res.json();
208 setInviteStatus("邀请成功");
209 // 更新剩余次数
210 const left = data.invite_left !== undefined ? data.invite_left : userInfo.invite_left - 1;
211 setUserInfo(prev => ({ ...prev, invite_left: left }));
212 setTempUserInfo(prev => ({ ...prev, invite_left: left }));
213 setInviteEmail('');
214 } else {
215 const errorText = await res.text();
216 setInviteStatus("邀请失败:" + errorText);
217 }
218 } catch (err) {
219 console.error("邀请失败", err);
220 setInviteStatus("邀请失败,请检查网络");
221 }
wht338fc032025-06-09 17:16:22 +0800222 };
223
224 // 兑换魔力值
wht2bf8f802025-06-08 15:52:18 +0800225 const handleExchange = async () => {
wht15642182025-06-08 00:16:52 +0800226 const magic = Number(exchangeMagic);
227 if (!magic || isNaN(magic) || magic <= 0) return;
228 if (magic > userStats.magic) {
229 alert("魔力值不足!");
230 return;
231 }
wht338fc032025-06-09 17:16:22 +0800232
wht2bf8f802025-06-08 15:52:18 +0800233 // 检查兑换结果是否为整数
234 const calculatedExchangeResult = magic / exchangeRate[exchangeType];
235 if (!Number.isInteger(calculatedExchangeResult)) {
236 alert("兑换结果必须为整数,请调整魔力值!");
237 return;
wht15642182025-06-08 00:16:52 +0800238 }
wht338fc032025-06-09 17:16:22 +0800239
wht2bf8f802025-06-08 15:52:18 +0800240 // 获取userid
241 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
242 const userid = match ? match[2] : null;
243 if (!userid) {
244 alert("未获取到用户ID");
245 return;
246 }
wht15642182025-06-08 00:16:52 +0800247
wht2bf8f802025-06-08 15:52:18 +0800248 console.log("兑换请求参数:", { userid, magic, exchangeType, exchangeResult: calculatedExchangeResult });
249 try {
250 // 发送兑换请求到后端
251 const res = await fetch(`${API_BASE_URL}/api/exchange`, {
252 method: 'POST',
253 headers: { 'Content-Type': 'application/json' },
wht338fc032025-06-09 17:16:22 +0800254 body: JSON.stringify({
255 userid,
256 magic,
wht2bf8f802025-06-08 15:52:18 +0800257 exchangeType,
258 exchangeResult: calculatedExchangeResult
259 }),
260 });
261 // console.log("兑换请求结果:", res);
262 if (res.ok) {
263 // 兑换成功后重新获取用户数据
264 const statsRes = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
265 if (statsRes.ok) {
266 const updatedStats = await statsRes.json();
267 setUserStats(updatedStats);
268 }
269 setExchangeMagic('');
270 alert("兑换成功!");
271 } else {
272 const errorText = await res.text();
273 alert("兑换失败:" + errorText);
274 }
275 } catch (err) {
276 console.error("兑换失败", err);
277 alert("兑换失败,请检查网络");
278 }
279 };
wht15642182025-06-08 00:16:52 +0800280 // 删除种子
281 const handleDeleteSeed = (seedid) => {
282 setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));
283 };
284
wht2bf8f802025-06-08 15:52:18 +0800285 // 取消收藏
286 const handleRemoveFavorite = async (seedid) => {
287 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
288 const userid = match ? match[2] : null;
289 if (!userid) {
290 alert('未获取到用户ID');
291 return;
292 }
293 try {
294 const res = await fetch(`${API_BASE_URL}/api/remove-favorite`, {
295 method: 'POST',
296 headers: { 'Content-Type': 'application/json' },
297 body: JSON.stringify({ userid, seedid }),
wht338fc032025-06-09 17:16:22 +0800298 }); if (res.ok) {
wht2bf8f802025-06-08 15:52:18 +0800299 setUserFavorites(userFavorites.filter((s) => (s.seedid || s.seed_id) !== seedid));
300 alert('已取消收藏');
301 } else {
302 alert('取消收藏失败,请重试');
303 }
304 } catch (err) {
305 console.error('取消收藏失败', err);
306 alert('取消收藏失败,请检查网络');
307 }
308 };
309
wht15642182025-06-08 00:16:52 +0800310 // 申诉提交逻辑
wht2bf8f802025-06-08 15:52:18 +0800311 const handleAppealSubmit = async () => {
312 if (!appealTitle || !appealFile) return;
wht2bf8f802025-06-08 15:52:18 +0800313 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
314 const userid = match ? match[2] : null;
315 if (!userid) {
316 alert('未获取到用户ID');
317 return;
318 }
wht338fc032025-06-09 17:16:22 +0800319
wht2bf8f802025-06-08 15:52:18 +0800320 const formData = new FormData();
321 formData.append('userid', userid);
322 formData.append('content', appealTitle);
323 formData.append('file', appealFile);
324 try {
325 const res = await fetch(`${API_BASE_URL}/api/submit-appeal`, {
326 method: 'POST',
327 body: formData,
328 });
329 if (res.ok) {
330 alert('申诉已提交');
331 setAppealOpen(false);
332 setAppealTitle('');
333 setAppealFile(null);
334 } else {
335 const errorText = await res.text();
336 alert('申诉失败:' + errorText);
337 }
338 } catch (err) {
339 console.error('申诉失败', err);
340 alert('申诉失败,请检查网络');
341 }
342 };
wht338fc032025-06-09 17:16:22 +0800343
344 // 账号迁移
wht2bf8f802025-06-08 15:52:18 +0800345 const handleMigrationSubmit = async () => {
346 if (!appealFile) {
347 setMigrationStatus('请选择PDF文件');
348 return;
349 }
wht338fc032025-06-09 17:16:22 +0800350 if (!migrationUpload || isNaN(migrationUpload) || Number(migrationUpload) <= 0) {
351 setMigrationStatus('请输入有效的待发放上传量');
352 return;
353 }
354
wht2bf8f802025-06-08 15:52:18 +0800355 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
356 const currentUserId = match ? match[2] : null;
357 if (!currentUserId) {
358 setMigrationStatus('未获取到当前用户ID');
359 return;
360 }
wht338fc032025-06-09 17:16:22 +0800361
wht2bf8f802025-06-08 15:52:18 +0800362 try {
wht2bf8f802025-06-08 15:52:18 +0800363 const formData = new FormData();
364 formData.append('userid', currentUserId);
365 formData.append('file', appealFile);
wht338fc032025-06-09 17:16:22 +0800366 formData.append('uploadtogive', migrationUpload);
367
wht2bf8f802025-06-08 15:52:18 +0800368 const res = await fetch(`${API_BASE_URL}/api/migrate-account`, {
369 method: 'POST',
370 body: formData,
371 });
wht338fc032025-06-09 17:16:22 +0800372
wht2bf8f802025-06-08 15:52:18 +0800373 if (res.ok) {
374 setMigrationStatus('账号迁移申请已提交,请等待管理员审核');
375 setTimeout(() => {
376 setMigrationOpen(false);
377 setAppealFile(null);
wht338fc032025-06-09 17:16:22 +0800378 setMigrationUpload('');
wht2bf8f802025-06-08 15:52:18 +0800379 setMigrationStatus('');
380 }, 2000);
381 } else {
382 const errorText = await res.text();
383 setMigrationStatus('迁移失败:' + errorText);
384 }
385 } catch (err) {
386 console.error('账号迁移失败', err);
387 setMigrationStatus('迁移失败,请检查网络');
388 }
wht15642182025-06-08 00:16:52 +0800389 };
390
956303669a32fc2c2025-06-02 19:45:53 +0800391 return (
wht15642182025-06-08 00:16:52 +0800392 <div
393 className="container"
394 style={{
395 minHeight: '100vh',
396 background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)',
397 display: 'grid',
398 gridTemplateColumns: '1.1fr 1.9fr',
399 gridTemplateRows: 'auto auto',
400 gap: '12px',
401 padding: '24px 3vw',
402 boxSizing: 'border-box'
403 }}
404 >
405 {/* 左上:用户资料 */}
406 <div style={{
407 gridColumn: '1 / 2',
408 gridRow: '1 / 2',
409 display: 'flex',
410 flexDirection: 'column',
411 alignItems: 'center',
412 background: '#fff',
413 borderRadius: 20,
414 boxShadow: '0 6px 32px #e0e7ff',
415 padding: '32px 28px',
416 minWidth: 320,
417 minHeight: 420,
418 transition: 'box-shadow 0.2s',
419 }}>
420 <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 18 }}>
956303669a32fc2c2025-06-02 19:45:53 +0800421 <div onClick={handleAvatarClick} style={{ cursor: 'pointer', position: 'relative' }}>
wht15642182025-06-08 00:16:52 +0800422 <AccountCircleIcon style={{ fontSize: 96, color: '#1a237e', marginBottom: 12 }} />
223011330f9623f2025-06-06 00:22:05 +0800423 {tempUserInfo.avatar_url && (
956303669a32fc2c2025-06-02 19:45:53 +0800424 <img
rhjc6a4ee02025-06-06 00:45:18 +0800425 src={tempUserInfo.avatar_url}
956303669a32fc2c2025-06-02 19:45:53 +0800426 alt="用户头像"
427 style={{
428 position: 'absolute',
429 top: 0,
430 left: 0,
wht15642182025-06-08 00:16:52 +0800431 width: 96,
432 height: 96,
956303669a32fc2c2025-06-02 19:45:53 +0800433 borderRadius: '50%',
434 objectFit: 'cover',
wht15642182025-06-08 00:16:52 +0800435 border: '2px solid #e0e7ff',
436 boxShadow: '0 2px 8px #bfcfff'
956303669a32fc2c2025-06-02 19:45:53 +0800437 }}
438 />
439 )}
440 </div>
wht15642182025-06-08 00:16:52 +0800441 <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 26, letterSpacing: 1 }}>用户个人资料</h2>
956303669a32fc2c2025-06-02 19:45:53 +0800442 </div>
wht15642182025-06-08 00:16:52 +0800443 <div className="card" style={{
444 padding: 32,
445 width: '100%',
446 background: '#fff',
447 borderRadius: 18,
448 boxShadow: '0 2px 12px #e0e7ff',
449 flex: 1,
450 minWidth: 0
451 }}>
452 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800453 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>用户名:</b>
wht15642182025-06-08 00:16:52 +0800454 <TextField
455 variant="outlined"
456 size="small"
956303669a32fc2c2025-06-02 19:45:53 +0800457 value={tempUserInfo.username}
458 onChange={(e) => handleInputChange("username", e.target.value)}
wht15642182025-06-08 00:16:52 +0800459 sx={{ flex: 1, minWidth: 0 }}
956303669a32fc2c2025-06-02 19:45:53 +0800460 />
461 </div>
wht15642182025-06-08 00:16:52 +0800462 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800463 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邮箱:</b>
wht15642182025-06-08 00:16:52 +0800464 <TextField
465 variant="outlined"
466 size="small"
467 value={tempUserInfo.email}
468 InputProps={{ readOnly: true }}
469 sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
470 />
956303669a32fc2c2025-06-02 19:45:53 +0800471 </div>
wht15642182025-06-08 00:16:52 +0800472 {/* 邀请功能 */}
473 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
rhjc6a4ee02025-06-06 00:45:18 +0800474 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邀请剩余:</b>
wht15642182025-06-08 00:16:52 +0800475 <TextField
476 type="email"
477 size="small"
478 placeholder="被邀请邮箱"
479 value={inviteEmail}
480 onChange={e => setInviteEmail(e.target.value)}
481 sx={{ flex: 2, marginRight: 1, minWidth: 120 }}
482 disabled={Number(tempUserInfo.invite_left) === 0}
483 />
484 <Button
485 variant="contained"
486 color="primary"
487 onClick={handleInvite}
488 disabled={Number(tempUserInfo.invite_left) === 0 || !inviteEmail}
489 sx={{ marginRight: 1, minWidth: 80 }}
490 >邀请</Button>
491 <span style={{ color: '#888', fontSize: 15 }}>剩余:{tempUserInfo.invite_left || "0"}</span>
956303669a32fc2c2025-06-02 19:45:53 +0800492 </div>
wht15642182025-06-08 00:16:52 +0800493 {inviteStatus && <div style={{ color: '#e53935', fontSize: 14, marginBottom: 8 }}>{inviteStatus}</div>}
494 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800495 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>学校:</b>
wht15642182025-06-08 00:16:52 +0800496 <TextField
497 variant="outlined"
498 size="small"
956303669a32fc2c2025-06-02 19:45:53 +0800499 value={tempUserInfo.school}
500 onChange={(e) => handleInputChange("school", e.target.value)}
wht15642182025-06-08 00:16:52 +0800501 sx={{ flex: 1, minWidth: 0 }}
956303669a32fc2c2025-06-02 19:45:53 +0800502 />
503 </div>
wht15642182025-06-08 00:16:52 +0800504 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
rhjc6a4ee02025-06-06 00:45:18 +0800505 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>账号状态:</b>
wht15642182025-06-08 00:16:52 +0800506 <TextField
507 variant="outlined"
508 size="small"
509 value={tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}
510 InputProps={{ readOnly: true }}
511 sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}
512 />
513 <span style={{
514 display: 'inline-block',
515 width: 12,
516 height: 12,
517 borderRadius: '50%',
518 backgroundColor: tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? '#e53935' : '#43a047',
519 marginLeft: 10,
520 border: '1px solid #b2b2b2',
521 }} />
956303669a32fc2c2025-06-02 19:45:53 +0800522 </div>
wht15642182025-06-08 00:16:52 +0800523 <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>
956303669a32fc2c2025-06-02 19:45:53 +0800524 <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>性别:</b>
wht15642182025-06-08 00:16:52 +0800525 <TextField
526 select
527 variant="outlined"
528 size="small"
529 value={tempUserInfo.gender}
530 onChange={e => handleInputChange("gender", e.target.value)}
531 sx={{ flex: 1, minWidth: 0 }}
532 >
533 <MenuItem value="m">男性</MenuItem>
534 <MenuItem value="f">女性</MenuItem>
535 </TextField>
wht2bf8f802025-06-08 15:52:18 +0800536 </div> <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>
wht15642182025-06-08 00:16:52 +0800537 <Button
538 variant="contained"
539 color="primary"
540 onClick={handleSave}
wht2bf8f802025-06-08 15:52:18 +0800541 sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
wht15642182025-06-08 00:16:52 +0800542 >保存</Button>
543 <Button
544 variant="contained"
545 color="error"
546 onClick={() => setAppealOpen(true)}
wht2bf8f802025-06-08 15:52:18 +0800547 sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
wht15642182025-06-08 00:16:52 +0800548 >用户申诉</Button>
wht2bf8f802025-06-08 15:52:18 +0800549 <Button
550 variant="contained"
551 color="warning"
552 onClick={() => setMigrationOpen(true)}
553 sx={{ fontSize: 16, borderRadius: 2, padding: '6px 12px' }}
554 >账号迁移</Button>
wht15642182025-06-08 00:16:52 +0800555 </div>
956303669a32fc2c2025-06-02 19:45:53 +0800556 </div>
557 </div>
wht15642182025-06-08 00:16:52 +0800558 {/* 左下:活跃度模块 */}
559 <div style={{
560 gridColumn: '1 / 2',
561 gridRow: '2 / 3',
562 background: '#fff',
563 borderRadius: 20,
564 boxShadow: '0 6px 32px #e0e7ff',
565 padding: '32px 28px',
566 minWidth: 320,
567 minHeight: 320,
568 display: 'flex',
569 flexDirection: 'column',
570 justifyContent: 'center'
571 }}>
572 <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>活跃度</h3>
573 <div style={{
574 border: '1.5px dashed #b2b2b2',
575 borderRadius: 14,
576 minHeight: 80,
577 padding: 22,
578 display: 'flex',
579 flexDirection: 'column',
580 gap: 14,
581 fontSize: 18,
582 background: '#f8faff'
583 }}>
584 <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
585 <span>魔力值:<b style={{ color: '#1976d2' }}>{userStats.magic}</b></span>
586 <TextField
587 type="number"
588 size="small"
589 placeholder="输入兑换魔力值"
590 value={exchangeMagic}
591 onChange={e => setExchangeMagic(e.target.value)}
592 sx={{ width: 100, marginLeft: 2, marginRight: 1 }}
593 />
594 <TextField
595 select
596 size="small"
597 value={exchangeType}
598 onChange={e => setExchangeType(e.target.value)}
599 sx={{ minWidth: 120 }}
600 >
601 <MenuItem value="uploaded">上传量(增加)</MenuItem>
602 <MenuItem value="downloaded">下载量(减少)</MenuItem>
603 <MenuItem value="vip_downloads">VIP下载次数(增加)</MenuItem>
wht2bf8f802025-06-08 15:52:18 +0800604 </TextField> <span style={{ marginLeft: 8, color: '#43a047' }}>
605 可兑换:<b>{exchangeResult}</b> {exchangeType === 'vip_downloads' ? '次' : 'MB'}
606 {!Number.isInteger(exchangeResult) && exchangeResult > 0 && (
607 <span style={{ color: '#e53935', fontSize: '12px', marginLeft: 8 }}>
608 (结果必须为整数)
609 </span>
610 )}
611 </span><Button
wht15642182025-06-08 00:16:52 +0800612 variant="contained"
613 color="primary"
614 onClick={handleExchange}
615 disabled={
616 !exchangeMagic ||
617 isNaN(exchangeMagic) ||
618 Number(exchangeMagic) <= 0 ||
wht2bf8f802025-06-08 15:52:18 +0800619 Number(exchangeMagic) > userStats.magic ||
620 !Number.isInteger(exchangeResult)
wht15642182025-06-08 00:16:52 +0800621 }
622 sx={{
623 marginLeft: 2,
624 minWidth: 80,
wht2bf8f802025-06-08 15:52:18 +0800625 background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic || !Number.isInteger(exchangeResult)) ? '#ccc' : undefined
wht15642182025-06-08 00:16:52 +0800626 }}
627 >兑换</Button>
wht2bf8f802025-06-08 15:52:18 +0800628 </div> <div>上传量:<b style={{ color: '#43a047' }}>{(userStats.upload / 1000000)?.toFixed(2)} MB</b></div>
629 <div>下载量:<b style={{ color: '#e53935' }}>{(userStats.download / 1000000)?.toFixed(2)} MB</b></div>
wht15642182025-06-08 00:16:52 +0800630 <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}</b></div>
wht2bf8f802025-06-08 15:52:18 +0800631 <div>VIP下载次数:<b style={{ color: '#1976d2' }}>{userStats.viptime}</b></div>
wht15642182025-06-08 00:16:52 +0800632 </div>
633 </div>
634 {/* 右上:个人上传种子列表 */}
635 <div style={{
636 gridColumn: '2 / 3',
637 gridRow: '1 / 2',
638 background: '#fff',
639 borderRadius: 20,
640 boxShadow: '0 6px 32px #e0e7ff',
641 padding: '32px 36px',
642 minHeight: 420,
643 display: 'flex',
644 flexDirection: 'column'
645 }}>
646 <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人上传种子列表</h3>
647 <div style={{
648 border: '1.5px dashed #b2b2b2',
649 borderRadius: 14,
650 minHeight: 80,
651 padding: 16,
652 background: '#f8faff'
653 }}>
rhjc6a4ee02025-06-06 00:45:18 +0800654 {userSeeds.length === 0 ? (
655 <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无上传种子)</div>
656 ) : (
657 <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
658 {userSeeds.map((seed, idx) => (
659 <li
wht15642182025-06-08 00:16:52 +0800660 key={seed.seedid || idx}
661 style={{
662 display: 'flex',
663 alignItems: 'center',
664 padding: '12px 0',
665 borderBottom: idx === userSeeds.length - 1 ? 'none' : '1px solid #e0e7ff',
666 cursor: 'pointer',
667 transition: 'background 0.15s'
668 }}
rhjc6a4ee02025-06-06 00:45:18 +0800669 onClick={e => {
rhjc6a4ee02025-06-06 00:45:18 +0800670 if (e.target.classList.contains('delete-btn')) return;
223011339e292152025-06-08 00:34:37 +0800671 navigate(`/torrent/${seed.seed_id}`);
rhjc6a4ee02025-06-06 00:45:18 +0800672 }}
wht15642182025-06-08 00:16:52 +0800673 onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}
674 onMouseOut={e => e.currentTarget.style.background = ''}
rhjc6a4ee02025-06-06 00:45:18 +0800675 >
676 <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline' }}>{seed.title}</span>
677 <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.tags}</span>
223011330f9623f2025-06-06 00:22:05 +0800678 <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.downloadtimes}</span>
wht15642182025-06-08 00:16:52 +0800679 <Button
rhjc6a4ee02025-06-06 00:45:18 +0800680 className="delete-btn"
wht15642182025-06-08 00:16:52 +0800681 variant="contained"
682 color="error"
683 size="small"
wht338fc032025-06-09 17:16:22 +0800684 sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }} onClick={async e => {
rhjc6a4ee02025-06-06 00:45:18 +0800685 e.stopPropagation();
wht2bf8f802025-06-08 15:52:18 +0800686 const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
687 const userid = match ? match[2] : null;
688 if (!userid) {
689 alert('未获取到用户ID');
690 return;
691 }
223011339e292152025-06-08 00:34:37 +0800692 try {
693 const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {
694 method: 'POST',
695 headers: { 'Content-Type': 'application/json' },
696 body: JSON.stringify({ seed_id: seed.seed_id, userid }),
697 });
698 if (res.ok) {
699 setUserSeeds(userSeeds.filter((s, i) => (s.seed_id || i) !== (seed.seed_id || idx)));
700 } else {
701 alert('删除失败,请重试');
702 }
703 } catch (err) {
704 alert('删除失败,请检查网络');
705 }
rhjc6a4ee02025-06-06 00:45:18 +0800706 }}
wht15642182025-06-08 00:16:52 +0800707 >删除</Button>
rhjc6a4ee02025-06-06 00:45:18 +0800708 </li>
709 ))}
710 </ul>
711 )}
956303669a32fc2c2025-06-02 19:45:53 +0800712 </div>
713 </div>
wht15642182025-06-08 00:16:52 +0800714 {/* 右下:个人收藏种子列表 */}
715 <div style={{
716 gridColumn: '2 / 3',
717 gridRow: '2 / 3',
718 background: '#fff',
719 borderRadius: 20,
720 boxShadow: '0 6px 32px #e0e7ff',
721 padding: '32px 36px',
722 minHeight: 320,
723 display: 'flex',
724 flexDirection: 'column'
725 }}>
726 <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人收藏种子列表</h3>
727 <div style={{
728 border: '1.5px dashed #b2b2b2',
729 borderRadius: 14,
730 minHeight: 80,
731 padding: 16,
732 background: '#f8faff'
733 }}>
734 {userFavorites.length === 0 ? (
735 <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无收藏种子)</div>
wht338fc032025-06-09 17:16:22 +0800736 ) : (<ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
737 {userFavorites.map((seed, idx) => (
738 <li
739 key={seed.seedid || idx}
740 style={{
741 display: 'flex',
742 alignItems: 'center',
743 padding: '12px 0',
744 borderBottom: idx === userFavorites.length - 1 ? 'none' : '1px solid #e0e7ff',
745 cursor: 'pointer',
746 transition: 'background 0.15s'
747 }} onClick={e => {
748 if (e.target.classList.contains('remove-favorite-btn')) return;
749 navigate(`/torrent/${seed.seedid || seed.seed_id}`);
750 }}
751 onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}
752 onMouseOut={e => e.currentTarget.style.background = ''}
753 >
754 <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);
wht15642182025-06-08 00:16:52 +0800765 }}
wht338fc032025-06-09 17:16:22 +0800766 >取消收藏</Button>
767 </li>
768 ))}
769 </ul>
wht15642182025-06-08 00:16:52 +0800770 )}
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>
wht338fc032025-06-09 17:16:22 +0800810 </Dialog>
811 {/* 账号迁移弹窗 */}
wht2bf8f802025-06-08 15:52:18 +0800812 <Dialog open={migrationOpen} onClose={() => setMigrationOpen(false)}>
813 <DialogTitle>账号迁移</DialogTitle>
814 <DialogContent>
815 <div style={{ marginBottom: 16 }}>
wht338fc032025-06-09 17:16:22 +0800816 <TextField
817 label="待发放上传量"
818 type="number"
819 fullWidth
820 value={migrationUpload}
821 onChange={e => setMigrationUpload(e.target.value)}
822 size="small"
823 inputProps={{ min: 1 }}
824 style={{ marginBottom: 18 }}
825 />
826 </div>
827 <div>
wht2bf8f802025-06-08 15:52:18 +0800828 <input
829 type="file"
830 accept=".pdf"
831 onChange={e => {
832 const file = e.target.files[0];
833 if (file && file.type !== 'application/pdf') {
834 alert('请选择PDF文件');
835 e.target.value = '';
836 setAppealFile(null);
837 } else {
838 setAppealFile(file);
839 }
840 }}
841 style={{ marginTop: 8 }}
842 />
843 <div style={{ fontSize: 12, color: '#666', marginTop: 4 }}>
844 请选择PDF文件(最大10MB
845 </div>
846 </div>
847 {migrationStatus && (
848 <div style={{ color: migrationStatus.includes('成功') ? '#43a047' : '#e53935', fontSize: 14, marginTop: 8 }}>
849 {migrationStatus}
850 </div>
851 )}
852 </DialogContent>
853 <DialogActions>
854 <Button onClick={handleMigrationSubmit} variant="contained" color="primary">提交迁移</Button>
855 <Button onClick={() => setMigrationOpen(false)} variant="outlined">取消</Button>
856 </DialogActions>
wht15642182025-06-08 00:16:52 +0800857 </Dialog>
956303669a32fc2c2025-06-02 19:45:53 +0800858 </div>
859 );
860}