rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 1 | import React, { useState, useEffect } from "react";
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 2 | import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 3 | import PersonIcon from "@mui/icons-material/Person";
|
| 4 | import EmailIcon from "@mui/icons-material/Email";
|
| 5 | import SchoolIcon from "@mui/icons-material/School";
|
| 6 | import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
| 7 | import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
|
| 8 | import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
|
| 9 | import FavoriteIcon from "@mui/icons-material/Favorite";
|
| 10 | import EmptyIcon from "@mui/icons-material/Inbox";
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 11 | import Button from '@mui/material/Button';
|
| 12 | import TextField from '@mui/material/TextField';
|
| 13 | import MenuItem from '@mui/material/MenuItem';
|
| 14 | import Dialog from '@mui/material/Dialog';
|
| 15 | import DialogTitle from '@mui/material/DialogTitle';
|
| 16 | import DialogContent from '@mui/material/DialogContent';
|
| 17 | import DialogActions from '@mui/material/DialogActions';
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 18 | import { useNavigate } from "react-router-dom";
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 19 | import { API_BASE_URL } from "./config";
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 20 | import "./UserProfile.css";
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 21 |
|
| 22 | export default function UserProfile() {
|
| 23 | const navigate = useNavigate();
|
| 24 | const [userInfo, setUserInfo] = useState({
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 25 | avatar_url: "",
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 26 | username: "示例用户",
|
| 27 | email: "user@example.com",
|
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 28 | invitetimes: "",
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 29 | school: "",
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 30 | account_status: "",
|
| 31 | gender: "",
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 32 | });
|
| 33 | const [tempUserInfo, setTempUserInfo] = useState({ ...userInfo });
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 34 | const [userSeeds, setUserSeeds] = useState([]);
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 35 | const [userFavorites, setUserFavorites] = useState([]);
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 36 | const [userStats, setUserStats] = useState({
|
| 37 | magic: 0,
|
| 38 | upload: 0,
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 39 | viptime: 0,
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 40 | ratio: 0,
|
| 41 | });
|
| 42 |
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 43 | // 邀请相关
|
| 44 | const [inviteEmail, setInviteEmail] = useState('');
|
| 45 | const [inviteStatus, setInviteStatus] = useState('');
|
| 46 |
|
| 47 | // 兑换相关
|
| 48 | const [exchangeType, setExchangeType] = useState('uploaded');
|
| 49 | const [exchangeMagic, setExchangeMagic] = useState('');
|
| 50 | const [exchangeResult, setExchangeResult] = useState(0);
|
| 51 |
|
| 52 | // 兑换比例
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 53 | const exchangeRate = { uploaded: 0.1, downloaded: 0.1, vip_downloads: 100 };
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 54 |
|
| 55 | // 用户申诉相关
|
| 56 | const [appealOpen, setAppealOpen] = useState(false);
|
| 57 | const [appealTitle, setAppealTitle] = useState('');
|
| 58 | const [appealFile, setAppealFile] = useState(null);
|
| 59 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 60 | // 账号迁移相关
|
| 61 | const [migrationOpen, setMigrationOpen] = useState(false);
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 62 | const [migrationUpload, setMigrationUpload] = useState('');
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 63 | const [migrationEmail, setMigrationEmail] = useState('');
|
| 64 | const [migrationPassword, setMigrationPassword] = useState('');
|
| 65 | const [migrationStatus, setMigrationStatus] = useState('');
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 66 |
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 67 | // 兑换结果计算
|
| 68 | React.useEffect(() => {
|
| 69 | if (!exchangeMagic || isNaN(exchangeMagic)) {
|
| 70 | setExchangeResult(0);
|
| 71 | return;
|
| 72 | }
|
| 73 | setExchangeResult(Number(exchangeMagic) / exchangeRate[exchangeType]);
|
| 74 | }, [exchangeMagic, exchangeType]);
|
| 75 |
|
| 76 | // 获取用户信息
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 77 | useEffect(() => {
|
| 78 | const fetchUserInfo = async () => {
|
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 79 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 80 | const userid = match ? match[2] : null;
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 81 | if (!userid) return;
|
| 82 | try {
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 83 | const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 84 | if (res.ok) {
|
| 85 | const data = await res.json();
|
| 86 | setUserInfo(data);
|
| 87 | setTempUserInfo(data);
|
| 88 | }
|
| 89 | } catch (err) {
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 90 | console.error("获取用户信息失败", err);
|
| 91 | }
|
| 92 | };
|
| 93 | fetchUserInfo();
|
| 94 | }, []);
|
| 95 |
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 96 | useEffect(() => {
|
| 97 | const fetchUserSeeds = async () => {
|
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 98 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 99 | const userid = match ? match[2] : null;
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 100 | if (!userid) return;
|
| 101 | try {
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 102 | const res = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userid}`);
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 103 | if (res.ok) {
|
| 104 | const data = await res.json();
|
| 105 | setUserSeeds(data);
|
| 106 | }
|
| 107 | } catch (err) {
|
| 108 | console.error("获取种子列表失败", err);
|
| 109 | }
|
| 110 | };
|
| 111 | fetchUserSeeds();
|
| 112 | }, []);
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 113 |
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 114 | useEffect(() => {
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 115 | const fetchUserFavorites = async () => {
|
| 116 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 117 | const userid = match ? match[2] : null;
|
| 118 | if (!userid) return;
|
| 119 | try {
|
| 120 | const res = await fetch(`${API_BASE_URL}/api/user-favorites?userid=${userid}`);
|
| 121 | if (res.ok) {
|
| 122 | const data = await res.json();
|
| 123 | // console.log("获取收藏种子列表:", data);
|
| 124 | setUserFavorites(data);
|
| 125 | }
|
| 126 | } catch (err) {
|
| 127 | console.error("获取收藏种子列表失败", err);
|
| 128 | }
|
| 129 | };
|
| 130 | fetchUserFavorites();
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 131 | }, []);
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 132 |
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 133 | useEffect(() => {
|
| 134 | const fetchUserStats = async () => {
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 135 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 136 | const userid = match ? match[2] : null;
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 137 | if (!userid) return;
|
| 138 | try {
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 139 | const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 140 | if (res.ok) {
|
| 141 | const data = await res.json();
|
| 142 | setUserStats(data);
|
| 143 | }
|
| 144 | } catch (err) {
|
| 145 | console.error("获取活跃度信息失败", err);
|
| 146 | }
|
| 147 | };
|
| 148 | fetchUserStats();
|
| 149 | }, []);
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 150 |
|
| 151 | const handleInputChange = (field, value) => {
|
| 152 | setTempUserInfo({ ...tempUserInfo, [field]: value });
|
| 153 | };
|
| 154 |
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 155 | const handleSave = async () => {
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 156 | if (tempUserInfo.gender === "男") {
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 157 | tempUserInfo.gender = "m";
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 158 | } else if (tempUserInfo.gender === "女") {
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 159 | tempUserInfo.gender = "f";
|
| 160 | }
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 161 | setUserInfo({ ...tempUserInfo });
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 162 |
|
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 163 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 164 | const userid = match ? match[2] : null;
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 165 | try {
|
22301133 | 0f9623f | 2025-06-06 00:22:05 +0800 | [diff] [blame] | 166 | const res = await fetch(`${API_BASE_URL}/api/change-profile`, {
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 167 | method: 'POST',
|
| 168 | headers: {
|
| 169 | 'Content-Type': 'application/json',
|
| 170 | },
|
| 171 | body: JSON.stringify({ userid, ...tempUserInfo }),
|
| 172 | });
|
| 173 | if (res.ok) {
|
| 174 | alert("信息已保存!");
|
| 175 | } else {
|
| 176 | alert("保存失败,请重试。");
|
| 177 | }
|
| 178 | } catch (err) {
|
| 179 | alert("保存失败,请检查网络连接。");
|
| 180 | console.error("保存用户信息失败", err);
|
| 181 | }
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 182 | };
|
| 183 |
|
| 184 | const handleAvatarClick = () => {
|
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 185 | const avatarUrl = prompt("请输入头像的URL:");
|
| 186 | if (avatarUrl) {
|
| 187 | setTempUserInfo({ ...tempUserInfo, avatar_url: avatarUrl });
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 188 | }
|
| 189 | };
|
| 190 |
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 191 | // 邀请
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 192 | const handleInvite = async () => {
|
| 193 | if (!inviteEmail) {
|
| 194 | setInviteStatus("请输入邀请邮箱");
|
| 195 | return;
|
| 196 | }
|
| 197 | // 获取userid
|
| 198 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 199 | const userid = match ? match[2] : null;
|
| 200 | if (!userid) {
|
| 201 | setInviteStatus("未获取到用户ID");
|
| 202 | return;
|
| 203 | }
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 204 | if (userInfo.invite_left <= 0) {
|
| 205 | setInviteStatus("邀请次数已用完");
|
| 206 | return;
|
| 207 | }
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 208 | try {
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 209 | const res = await fetch(`${API_BASE_URL}/api/invite`, {
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 210 | method: 'POST',
|
| 211 | headers: { 'Content-Type': 'application/json' },
|
| 212 | body: JSON.stringify({ userid, invite_email: inviteEmail }),
|
| 213 | });
|
| 214 | if (res.ok) {
|
| 215 | const data = await res.json();
|
| 216 | setInviteStatus("邀请成功");
|
| 217 | // 更新剩余次数
|
| 218 | const left = data.invite_left !== undefined ? data.invite_left : userInfo.invite_left - 1;
|
| 219 | setUserInfo(prev => ({ ...prev, invite_left: left }));
|
| 220 | setTempUserInfo(prev => ({ ...prev, invite_left: left }));
|
| 221 | setInviteEmail('');
|
| 222 | } else {
|
| 223 | const errorText = await res.text();
|
| 224 | setInviteStatus("邀请失败:" + errorText);
|
| 225 | }
|
| 226 | } catch (err) {
|
| 227 | console.error("邀请失败", err);
|
| 228 | setInviteStatus("邀请失败,请检查网络");
|
| 229 | }
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 230 | };
|
| 231 |
|
| 232 | // 兑换魔力值
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 233 | const handleExchange = async () => {
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 234 | const magic = Number(exchangeMagic);
|
| 235 | if (!magic || isNaN(magic) || magic <= 0) return;
|
| 236 | if (magic > userStats.magic) {
|
| 237 | alert("魔力值不足!");
|
| 238 | return;
|
| 239 | }
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 240 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 241 | // 检查兑换结果是否为整数
|
| 242 | const calculatedExchangeResult = magic / exchangeRate[exchangeType];
|
| 243 | if (!Number.isInteger(calculatedExchangeResult)) {
|
| 244 | alert("兑换结果必须为整数,请调整魔力值!");
|
| 245 | return;
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 246 | }
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 247 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 248 | // 获取userid
|
| 249 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 250 | const userid = match ? match[2] : null;
|
| 251 | if (!userid) {
|
| 252 | alert("未获取到用户ID");
|
| 253 | return;
|
| 254 | }
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 255 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 256 | console.log("兑换请求参数:", { userid, magic, exchangeType, exchangeResult: calculatedExchangeResult });
|
| 257 | try {
|
| 258 | // 发送兑换请求到后端
|
| 259 | const res = await fetch(`${API_BASE_URL}/api/exchange`, {
|
| 260 | method: 'POST',
|
| 261 | headers: { 'Content-Type': 'application/json' },
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 262 | body: JSON.stringify({
|
| 263 | userid,
|
| 264 | magic,
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 265 | exchangeType,
|
| 266 | exchangeResult: calculatedExchangeResult
|
| 267 | }),
|
| 268 | });
|
| 269 | // console.log("兑换请求结果:", res);
|
| 270 | if (res.ok) {
|
| 271 | // 兑换成功后重新获取用户数据
|
| 272 | const statsRes = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);
|
| 273 | if (statsRes.ok) {
|
| 274 | const updatedStats = await statsRes.json();
|
| 275 | setUserStats(updatedStats);
|
| 276 | }
|
| 277 | setExchangeMagic('');
|
| 278 | alert("兑换成功!");
|
| 279 | } else {
|
| 280 | const errorText = await res.text();
|
| 281 | alert("兑换失败:" + errorText);
|
| 282 | }
|
| 283 | } catch (err) {
|
| 284 | console.error("兑换失败", err);
|
| 285 | alert("兑换失败,请检查网络");
|
| 286 | }
|
| 287 | };
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 288 | // 删除种子
|
| 289 | const handleDeleteSeed = (seedid) => {
|
| 290 | setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));
|
| 291 | };
|
| 292 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 293 | // 取消收藏
|
| 294 | const handleRemoveFavorite = async (seedid) => {
|
| 295 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 296 | const userid = match ? match[2] : null;
|
| 297 | if (!userid) {
|
| 298 | alert('未获取到用户ID');
|
| 299 | return;
|
| 300 | }
|
| 301 | try {
|
| 302 | const res = await fetch(`${API_BASE_URL}/api/remove-favorite`, {
|
| 303 | method: 'POST',
|
| 304 | headers: { 'Content-Type': 'application/json' },
|
| 305 | body: JSON.stringify({ userid, seedid }),
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 306 | }); if (res.ok) {
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 307 | setUserFavorites(userFavorites.filter((s) => (s.seedid || s.seed_id) !== seedid));
|
| 308 | alert('已取消收藏');
|
| 309 | } else {
|
| 310 | alert('取消收藏失败,请重试');
|
| 311 | }
|
| 312 | } catch (err) {
|
| 313 | console.error('取消收藏失败', err);
|
| 314 | alert('取消收藏失败,请检查网络');
|
| 315 | }
|
| 316 | };
|
| 317 |
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 318 | // 申诉提交逻辑
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 319 | const handleAppealSubmit = async () => {
|
| 320 | if (!appealTitle || !appealFile) return;
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 321 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 322 | const userid = match ? match[2] : null;
|
| 323 | if (!userid) {
|
| 324 | alert('未获取到用户ID');
|
| 325 | return;
|
| 326 | }
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 327 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 328 | const formData = new FormData();
|
| 329 | formData.append('userid', userid);
|
| 330 | formData.append('content', appealTitle);
|
| 331 | formData.append('file', appealFile);
|
| 332 | try {
|
| 333 | const res = await fetch(`${API_BASE_URL}/api/submit-appeal`, {
|
| 334 | method: 'POST',
|
| 335 | body: formData,
|
| 336 | });
|
| 337 | if (res.ok) {
|
| 338 | alert('申诉已提交');
|
| 339 | setAppealOpen(false);
|
| 340 | setAppealTitle('');
|
| 341 | setAppealFile(null);
|
| 342 | } else {
|
| 343 | const errorText = await res.text();
|
| 344 | alert('申诉失败:' + errorText);
|
| 345 | }
|
| 346 | } catch (err) {
|
| 347 | console.error('申诉失败', err);
|
| 348 | alert('申诉失败,请检查网络');
|
| 349 | }
|
| 350 | };
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 351 |
|
| 352 | // 账号迁移
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 353 | const handleMigrationSubmit = async () => {
|
| 354 | if (!appealFile) {
|
| 355 | setMigrationStatus('请选择PDF文件');
|
| 356 | return;
|
| 357 | }
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 358 | if (!migrationUpload || isNaN(migrationUpload) || Number(migrationUpload) <= 0) {
|
| 359 | setMigrationStatus('请输入有效的待发放上传量');
|
| 360 | return;
|
| 361 | }
|
| 362 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 363 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 364 | const currentUserId = match ? match[2] : null;
|
| 365 | if (!currentUserId) {
|
| 366 | setMigrationStatus('未获取到当前用户ID');
|
| 367 | return;
|
| 368 | }
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 369 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 370 | try {
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 371 | const formData = new FormData();
|
| 372 | formData.append('userid', currentUserId);
|
| 373 | formData.append('file', appealFile);
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 374 | formData.append('uploadtogive', migrationUpload);
|
| 375 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 376 | const res = await fetch(`${API_BASE_URL}/api/migrate-account`, {
|
| 377 | method: 'POST',
|
| 378 | body: formData,
|
| 379 | });
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 380 |
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 381 | if (res.ok) {
|
| 382 | setMigrationStatus('账号迁移申请已提交,请等待管理员审核');
|
| 383 | setTimeout(() => {
|
| 384 | setMigrationOpen(false);
|
| 385 | setAppealFile(null);
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 386 | setMigrationUpload('');
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 387 | setMigrationStatus('');
|
| 388 | }, 2000);
|
| 389 | } else {
|
| 390 | const errorText = await res.text();
|
| 391 | setMigrationStatus('迁移失败:' + errorText);
|
| 392 | }
|
| 393 | } catch (err) {
|
| 394 | console.error('账号迁移失败', err);
|
| 395 | setMigrationStatus('迁移失败,请检查网络');
|
| 396 | }
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 397 | }; return (
|
| 398 | <div className="user-profile-container">
|
| 399 | <div className="profile-grid">
|
| 400 | {/* 用户基本信息卡片 */}
|
| 401 | <div className="profile-card user-info-card">
|
| 402 | <div className="user-avatar-section">
|
| 403 | <div className="avatar-container" onClick={handleAvatarClick}>
|
| 404 | {tempUserInfo.avatar_url ? (
|
| 405 | <img
|
| 406 | src={tempUserInfo.avatar_url}
|
| 407 | alt="用户头像"
|
| 408 | className="user-avatar"
|
| 409 | />
|
| 410 | ) : (
|
| 411 | <AccountCircleIcon style={{ fontSize: 120, color: '#1a237e' }} />
|
| 412 | )}
|
| 413 | <div className="avatar-overlay">
|
| 414 | <span>点击更换头像</span>
|
| 415 | </div>
|
| 416 | </div>
|
| 417 | <h2 className="user-title">用户个人资料</h2>
|
| 418 | </div>
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 419 |
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 420 | <div className="user-form">
|
| 421 | <div className="form-group">
|
| 422 | <label className="form-label">
|
| 423 | <PersonIcon style={{ fontSize: 16, marginRight: 4 }} />
|
| 424 | 用户名
|
| 425 | </label>
|
| 426 | <input
|
| 427 | className="form-input"
|
| 428 | value={tempUserInfo.username}
|
| 429 | onChange={(e) => handleInputChange("username", e.target.value)}
|
| 430 | placeholder="请输入用户名"
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 431 | />
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 432 | </div>
|
| 433 |
|
| 434 | <div className="form-group">
|
| 435 | <label className="form-label">
|
| 436 | <EmailIcon style={{ fontSize: 16, marginRight: 4 }} />
|
| 437 | 邮箱
|
| 438 | </label>
|
| 439 | <input
|
| 440 | className="form-input"
|
| 441 | value={tempUserInfo.email}
|
| 442 | readOnly
|
| 443 | style={{ cursor: 'not-allowed' }}
|
| 444 | />
|
| 445 | </div>
|
| 446 |
|
| 447 | <div className="form-group">
|
| 448 | <label className="form-label">
|
| 449 | <SchoolIcon style={{ fontSize: 16, marginRight: 4 }} />
|
| 450 | 学校
|
| 451 | </label>
|
| 452 | <input
|
| 453 | className="form-input"
|
| 454 | value={tempUserInfo.school}
|
| 455 | onChange={(e) => handleInputChange("school", e.target.value)}
|
| 456 | placeholder="请输入学校名称"
|
| 457 | />
|
| 458 | </div>
|
| 459 |
|
| 460 | <div className="form-group">
|
| 461 | <label className="form-label">账号状态</label>
|
| 462 | <div className="status-indicator">
|
| 463 | <input
|
| 464 | className="form-input"
|
| 465 | value={tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}
|
| 466 | readOnly
|
| 467 | />
|
| 468 | <div className={`status-dot ${tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? 'banned' : 'active'}`}></div>
|
| 469 | </div>
|
| 470 | </div>
|
| 471 |
|
| 472 | <div className="form-group">
|
| 473 | <label className="form-label">性别</label>
|
| 474 | <select
|
| 475 | className="form-input"
|
| 476 | value={tempUserInfo.gender}
|
| 477 | onChange={(e) => handleInputChange("gender", e.target.value)}
|
| 478 | >
|
| 479 | <option value="">请选择性别</option>
|
| 480 | <option value="m">男性</option>
|
| 481 | <option value="f">女性</option>
|
| 482 | </select>
|
| 483 | </div>
|
| 484 |
|
| 485 | {/* 邀请功能 */}
|
| 486 | <div className="invite-section">
|
| 487 | <label className="form-label">邀请功能</label>
|
| 488 | <div className="invite-form">
|
| 489 | <input
|
| 490 | className="form-input invite-input"
|
| 491 | type="email"
|
| 492 | placeholder="输入邀请邮箱"
|
| 493 | value={inviteEmail}
|
| 494 | onChange={e => setInviteEmail(e.target.value)}
|
| 495 | disabled={Number(tempUserInfo.invite_left) === 0}
|
| 496 | />
|
| 497 | <button
|
| 498 | className="btn btn-primary btn-small"
|
| 499 | onClick={handleInvite}
|
| 500 | disabled={Number(tempUserInfo.invite_left) === 0 || !inviteEmail}
|
| 501 | >
|
| 502 | 邀请
|
| 503 | </button>
|
| 504 | </div>
|
| 505 | <div className="invite-counter">
|
| 506 | 剩余邀请次数:{tempUserInfo.invite_left || "0"}
|
| 507 | </div>
|
| 508 | {inviteStatus && (
|
| 509 | <div className="invite-status">{inviteStatus}</div>
|
| 510 | )}
|
| 511 | </div>
|
| 512 |
|
| 513 | <div className="btn-group">
|
| 514 | <button className="btn btn-primary" onClick={handleSave}>
|
| 515 | 保存信息
|
| 516 | </button>
|
| 517 | <button className="btn btn-danger" onClick={() => setAppealOpen(true)}>
|
| 518 | 用户申诉
|
| 519 | </button>
|
| 520 | <button className="btn btn-warning" onClick={() => setMigrationOpen(true)}>
|
| 521 | 账号迁移
|
| 522 | </button> </div>
|
| 523 | </div>
|
| 524 | </div>
|
| 525 |
|
| 526 | {/* 活跃度卡片 */}
|
| 527 | <div className="profile-card activity-card">
|
| 528 | <h3 className="activity-title">
|
| 529 | <AutoAwesomeIcon style={{ fontSize: 24, marginRight: 8 }} />
|
| 530 | 活跃度统计
|
| 531 | </h3>
|
| 532 |
|
| 533 | <div className="activity-content">
|
| 534 | {/* 魔力值兑换 */}
|
| 535 | <div className="magic-exchange">
|
| 536 | <div className="stat-item">
|
| 537 | <span className="stat-label">当前魔力值</span>
|
| 538 | <span className="stat-value magic">{userStats.magic}</span>
|
| 539 | </div>
|
| 540 |
|
| 541 | <div className="exchange-form">
|
| 542 | <input
|
| 543 | className="form-input exchange-input"
|
| 544 | type="number"
|
| 545 | placeholder="输入兑换魔力值"
|
| 546 | value={exchangeMagic}
|
| 547 | onChange={e => setExchangeMagic(e.target.value)}
|
| 548 | />
|
| 549 | <select
|
| 550 | className="form-input exchange-input"
|
| 551 | value={exchangeType}
|
| 552 | onChange={e => setExchangeType(e.target.value)}
|
| 553 | >
|
| 554 | <option value="uploaded">上传量(增加)</option>
|
| 555 | <option value="downloaded">下载量(减少)</option>
|
| 556 | <option value="vip_downloads">VIP下载次数(增加)</option>
|
| 557 | </select>
|
| 558 | <button
|
| 559 | className="btn btn-primary btn-small"
|
| 560 | onClick={handleExchange}
|
| 561 | disabled={
|
| 562 | !exchangeMagic ||
|
| 563 | isNaN(exchangeMagic) ||
|
| 564 | Number(exchangeMagic) <= 0 ||
|
| 565 | Number(exchangeMagic) > userStats.magic ||
|
| 566 | !Number.isInteger(exchangeResult)
|
| 567 | }
|
| 568 | >
|
| 569 | 兑换
|
| 570 | </button>
|
| 571 | </div>
|
| 572 |
|
| 573 | {exchangeMagic && (
|
| 574 | <div className="exchange-result">
|
| 575 | 可兑换:{exchangeResult} {exchangeType === 'vip_downloads' ? '次' : 'MB'}
|
| 576 | {!Number.isInteger(exchangeResult) && exchangeResult > 0 && (
|
| 577 | <span style={{ color: '#e53935', marginLeft: 8 }}>
|
| 578 | (结果必须为整数)
|
| 579 | </span>
|
| 580 | )}
|
| 581 | </div>
|
| 582 | )}
|
| 583 | </div>
|
| 584 |
|
| 585 | {/* 统计数据 */}
|
| 586 | <div className="stats-grid">
|
| 587 | <div className="stat-item">
|
| 588 | <span className="stat-label">
|
| 589 | <CloudUploadIcon style={{ fontSize: 16, marginRight: 4 }} />
|
| 590 | 上传量
|
| 591 | </span>
|
| 592 | <span className="stat-value upload">
|
| 593 | {(userStats.upload / 1000000)?.toFixed(2)} MB
|
| 594 | </span>
|
| 595 | </div>
|
| 596 |
|
| 597 | <div className="stat-item">
|
| 598 | <span className="stat-label">
|
| 599 | <CloudDownloadIcon style={{ fontSize: 16, marginRight: 4 }} />
|
| 600 | 下载量
|
| 601 | </span>
|
| 602 | <span className="stat-value download">
|
| 603 | {(userStats.download / 1000000)?.toFixed(2)} MB
|
| 604 | </span>
|
| 605 | </div>
|
| 606 |
|
| 607 | <div className="stat-item">
|
| 608 | <span className="stat-label">上传/下载比</span>
|
| 609 | <span className="stat-value ratio">
|
| 610 | {userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}
|
| 611 | </span>
|
| 612 | </div>
|
| 613 |
|
| 614 | <div className="stat-item">
|
| 615 | <span className="stat-label">VIP下载次数</span>
|
| 616 | <span className="stat-value vip">{userStats.viptime}</span>
|
| 617 | </div>
|
| 618 | </div>
|
| 619 | </div>
|
| 620 | </div> {/* 个人上传种子列表 */}
|
| 621 | <div className="profile-card seeds-card">
|
| 622 | <h3 className="list-title">
|
| 623 | <CloudUploadIcon style={{ fontSize: 24, marginRight: 8 }} />
|
| 624 | 个人上传种子列表
|
| 625 | </h3>
|
| 626 |
|
| 627 | <div className="list-container">
|
| 628 | {userSeeds.length === 0 ? (
|
| 629 | <div className="empty-state">
|
| 630 | <EmptyIcon className="empty-icon" />
|
| 631 | <span>暂无上传种子</span>
|
| 632 | </div>
|
| 633 | ) : (
|
| 634 | <ul className="seeds-list">
|
| 635 | {userSeeds.map((seed, idx) => (
|
| 636 | <li
|
| 637 | key={seed.seedid || idx}
|
| 638 | className="seed-item"
|
| 639 | onClick={e => {
|
| 640 | if (e.target.classList.contains('delete-btn')) return;
|
| 641 | navigate(`/torrent/${seed.seed_id}`);
|
| 642 | }}
|
| 643 | >
|
| 644 | <span className="seed-title">{seed.title}</span>
|
| 645 | <span className="seed-tags">{seed.tags}</span>
|
| 646 | <span className="seed-stats">人气: {seed.downloadtimes}</span>
|
| 647 | <div className="seed-actions">
|
| 648 | <button
|
| 649 | className="btn btn-danger btn-small delete-btn"
|
| 650 | onClick={async e => {
|
| 651 | e.stopPropagation();
|
| 652 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
|
| 653 | const userid = match ? match[2] : null;
|
| 654 | if (!userid) {
|
| 655 | alert('未获取到用户ID');
|
| 656 | return;
|
| 657 | }
|
| 658 | try {
|
| 659 | const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {
|
| 660 | method: 'POST',
|
| 661 | headers: { 'Content-Type': 'application/json' },
|
| 662 | body: JSON.stringify({ seed_id: seed.seed_id, userid }),
|
| 663 | });
|
| 664 | if (res.ok) {
|
| 665 | setUserSeeds(userSeeds.filter((s, i) => (s.seed_id || i) !== (seed.seed_id || idx)));
|
| 666 | } else {
|
| 667 | alert('删除失败,请重试');
|
| 668 | }
|
| 669 | } catch (err) {
|
| 670 | alert('删除失败,请检查网络');
|
| 671 | }
|
| 672 | }}
|
| 673 | >
|
| 674 | 删除
|
| 675 | </button>
|
| 676 | </div>
|
| 677 | </li>
|
| 678 | ))}
|
| 679 | </ul>
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 680 | )}
|
| 681 | </div>
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 682 | </div>
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 683 |
|
| 684 | {/* 个人收藏种子列表 */}
|
| 685 | <div className="profile-card favorites-card">
|
| 686 | <h3 className="list-title">
|
| 687 | <FavoriteIcon style={{ fontSize: 24, marginRight: 8 }} />
|
| 688 | 个人收藏种子列表
|
| 689 | </h3>
|
| 690 |
|
| 691 | <div className="list-container">
|
| 692 | {userFavorites.length === 0 ? (
|
| 693 | <div className="empty-state">
|
| 694 | <FavoriteIcon className="empty-icon" />
|
| 695 | <span>暂无收藏种子</span>
|
| 696 | </div>
|
| 697 | ) : (
|
| 698 | <ul className="seeds-list">
|
| 699 | {userFavorites.map((seed, idx) => (
|
| 700 | <li
|
| 701 | key={seed.seedid || idx}
|
| 702 | className="seed-item"
|
| 703 | onClick={e => {
|
| 704 | if (e.target.classList.contains('remove-favorite-btn')) return;
|
| 705 | navigate(`/torrent/${seed.seedid || seed.seed_id}`);
|
rhj | c6a4ee0 | 2025-06-06 00:45:18 +0800 | [diff] [blame] | 706 | }}
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 707 | >
|
| 708 | <span className="seed-title">{seed.seed.title}</span>
|
| 709 | <span className="seed-tags">{seed.seed.tags}</span>
|
| 710 | <span className="seed-stats">人气: {seed.seed.downloadtimes}</span>
|
| 711 | <div className="seed-actions">
|
| 712 | <button
|
| 713 | className="btn btn-warning btn-small remove-favorite-btn"
|
| 714 | onClick={e => {
|
| 715 | e.stopPropagation();
|
| 716 | handleRemoveFavorite(seed.seedid || seed.seed_id);
|
| 717 | }}
|
| 718 | >
|
| 719 | 取消收藏
|
| 720 | </button>
|
| 721 | </div>
|
| 722 | </li>
|
| 723 | ))}
|
| 724 | </ul>
|
| 725 | )}
|
| 726 | </div>
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 727 | </div>
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 728 | </div>
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 729 | {/* 申诉弹窗 */}
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 730 | <Dialog open={appealOpen} onClose={() => setAppealOpen(false)} maxWidth="sm" fullWidth>
|
| 731 | <DialogTitle style={{ background: '#f8faff', color: '#1a237e', fontWeight: 600 }}>
|
| 732 | 提交申诉
|
| 733 | </DialogTitle>
|
| 734 | <DialogContent style={{ padding: '24px', background: '#ffffff' }}>
|
| 735 | <div style={{ marginBottom: 20 }}>
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 736 | <TextField
|
| 737 | label="申诉主题"
|
| 738 | fullWidth
|
| 739 | value={appealTitle}
|
| 740 | onChange={e => setAppealTitle(e.target.value)}
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 741 | variant="outlined"
|
| 742 | style={{ marginBottom: 16 }}
|
wht | 338fc03 | 2025-06-09 17:16:22 +0800 | [diff] [blame] | 743 | />
|
| 744 | </div>
|
| 745 | <div>
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 746 | <input
|
| 747 | type="file"
|
| 748 | accept=".pdf"
|
| 749 | onChange={e => {
|
| 750 | const file = e.target.files[0];
|
| 751 | if (file && file.type !== 'application/pdf') {
|
| 752 | alert('请选择PDF文件');
|
| 753 | e.target.value = '';
|
| 754 | setAppealFile(null);
|
| 755 | } else {
|
| 756 | setAppealFile(file);
|
| 757 | }
|
| 758 | }}
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 759 | style={{
|
| 760 | marginTop: 8,
|
| 761 | padding: '12px',
|
| 762 | border: '2px dashed #e0e7ff',
|
| 763 | borderRadius: '8px',
|
| 764 | width: '100%',
|
| 765 | background: '#f8faff'
|
| 766 | }}
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 767 | />
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 768 | <div style={{
|
| 769 | fontSize: 12,
|
| 770 | color: '#666',
|
| 771 | marginTop: 8,
|
| 772 | padding: '8px 12px',
|
| 773 | background: '#f0f4ff',
|
| 774 | borderRadius: '6px'
|
| 775 | }}>
|
| 776 | 请选择PDF文件(最大100MB)
|
| 777 | </div>
|
| 778 | </div>
|
| 779 | </DialogContent>
|
| 780 | <DialogActions style={{ padding: '16px 24px', background: '#f8faff' }}>
|
| 781 | <Button
|
| 782 | onClick={handleAppealSubmit}
|
| 783 | variant="contained"
|
| 784 | disabled={!appealTitle || !appealFile}
|
| 785 | style={{
|
| 786 | background: (!appealTitle || !appealFile) ? '#ccc' : 'linear-gradient(135deg, #1a237e 0%, #3f51b5 100%)',
|
| 787 | color: 'white',
|
| 788 | fontWeight: 600
|
| 789 | }}
|
| 790 | >
|
| 791 | 提交申诉
|
| 792 | </Button>
|
| 793 | <Button
|
| 794 | onClick={() => setAppealOpen(false)}
|
| 795 | variant="outlined"
|
| 796 | style={{ color: '#1a237e', borderColor: '#1a237e' }}
|
| 797 | >
|
| 798 | 取消
|
| 799 | </Button>
|
| 800 | </DialogActions>
|
| 801 | </Dialog>
|
| 802 |
|
| 803 | {/* 账号迁移弹窗 */}
|
| 804 | <Dialog open={migrationOpen} onClose={() => setMigrationOpen(false)} maxWidth="sm" fullWidth>
|
| 805 | <DialogTitle style={{ background: '#f8faff', color: '#1a237e', fontWeight: 600 }}>
|
| 806 | 账号迁移
|
| 807 | </DialogTitle>
|
| 808 | <DialogContent style={{ padding: '24px', background: '#ffffff' }}>
|
| 809 | <div style={{ marginBottom: 20 }}>
|
| 810 | <TextField
|
| 811 | label="待发放上传量"
|
| 812 | type="number"
|
| 813 | fullWidth
|
| 814 | value={migrationUpload}
|
| 815 | onChange={e => setMigrationUpload(e.target.value)}
|
| 816 | variant="outlined"
|
| 817 | inputProps={{ min: 1 }}
|
| 818 | style={{ marginBottom: 16 }}
|
| 819 | />
|
| 820 | </div>
|
| 821 | <div>
|
| 822 | <input
|
| 823 | type="file"
|
| 824 | accept=".pdf"
|
| 825 | onChange={e => {
|
| 826 | const file = e.target.files[0];
|
| 827 | if (file && file.type !== 'application/pdf') {
|
| 828 | alert('请选择PDF文件');
|
| 829 | e.target.value = '';
|
| 830 | setAppealFile(null);
|
| 831 | } else {
|
| 832 | setAppealFile(file);
|
| 833 | }
|
| 834 | }}
|
| 835 | style={{
|
| 836 | marginTop: 8,
|
| 837 | padding: '12px',
|
| 838 | border: '2px dashed #e0e7ff',
|
| 839 | borderRadius: '8px',
|
| 840 | width: '100%',
|
| 841 | background: '#f8faff'
|
| 842 | }}
|
| 843 | />
|
| 844 | <div style={{
|
| 845 | fontSize: 12,
|
| 846 | color: '#666',
|
| 847 | marginTop: 8,
|
| 848 | padding: '8px 12px',
|
| 849 | background: '#f0f4ff',
|
| 850 | borderRadius: '6px'
|
| 851 | }}>
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 852 | 请选择PDF文件(最大10MB)
|
| 853 | </div>
|
| 854 | </div>
|
| 855 | {migrationStatus && (
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 856 | <div style={{
|
| 857 | color: migrationStatus.includes('成功') ? '#43a047' : '#e53935',
|
| 858 | fontSize: 14,
|
| 859 | marginTop: 16,
|
| 860 | padding: '12px',
|
| 861 | borderRadius: '8px',
|
| 862 | background: migrationStatus.includes('成功') ? 'rgba(67, 160, 71, 0.1)' : 'rgba(229, 57, 53, 0.1)',
|
| 863 | border: `1px solid ${migrationStatus.includes('成功') ? 'rgba(67, 160, 71, 0.3)' : 'rgba(229, 57, 53, 0.3)'}`
|
| 864 | }}>
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 865 | {migrationStatus}
|
| 866 | </div>
|
| 867 | )}
|
| 868 | </DialogContent>
|
wht | 3058782 | 2025-06-09 23:33:09 +0800 | [diff] [blame] | 869 | <DialogActions style={{ padding: '16px 24px', background: '#f8faff' }}>
|
| 870 | <Button
|
| 871 | onClick={handleMigrationSubmit}
|
| 872 | variant="contained"
|
| 873 | style={{
|
| 874 | background: 'linear-gradient(135deg, #ff9800 0%, #ffa726 100%)',
|
| 875 | color: 'white',
|
| 876 | fontWeight: 600
|
| 877 | }}
|
| 878 | >
|
| 879 | 提交迁移
|
| 880 | </Button>
|
| 881 | <Button
|
| 882 | onClick={() => setMigrationOpen(false)}
|
| 883 | variant="outlined"
|
| 884 | style={{ color: '#1a237e', borderColor: '#1a237e' }}
|
| 885 | >
|
| 886 | 取消
|
| 887 | </Button>
|
wht | 2bf8f80 | 2025-06-08 15:52:18 +0800 | [diff] [blame] | 888 | </DialogActions>
|
wht | 1564218 | 2025-06-08 00:16:52 +0800 | [diff] [blame] | 889 | </Dialog>
|
956303669 | a32fc2c | 2025-06-02 19:45:53 +0800 | [diff] [blame] | 890 | </div>
|
| 891 | );
|
| 892 | } |