修改个人主页,优化推荐系统

Change-Id: I533e36dc891b1b60fcb1e4a71b522b3ba9d77984
diff --git a/front/src/UserProfile.js b/front/src/UserProfile.js
index 16fdcd2..cf38e7f 100644
--- a/front/src/UserProfile.js
+++ b/front/src/UserProfile.js
@@ -1,5 +1,12 @@
 import React, { useState, useEffect } from "react";

 import AccountCircleIcon from "@mui/icons-material/AccountCircle";

+import Button from '@mui/material/Button';

+import TextField from '@mui/material/TextField';

+import MenuItem from '@mui/material/MenuItem';

+import Dialog from '@mui/material/Dialog';

+import DialogTitle from '@mui/material/DialogTitle';

+import DialogContent from '@mui/material/DialogContent';

+import DialogActions from '@mui/material/DialogActions';

 import { useNavigate } from "react-router-dom";

 import { API_BASE_URL } from "./config";

 import "./App.css";

@@ -17,6 +24,7 @@
   });

   const [tempUserInfo, setTempUserInfo] = useState({ ...userInfo });

   const [userSeeds, setUserSeeds] = useState([]);

+  const [userFavorites, setUserFavorites] = useState([]);

   const [userStats, setUserStats] = useState({

     magic: 0,

     upload: 0,

@@ -24,35 +32,55 @@
     ratio: 0,

   });

 

-  // 新增:根据userid从后端获取用户信息

+  // 邀请相关

+  const [inviteEmail, setInviteEmail] = useState('');

+  const [inviteStatus, setInviteStatus] = useState('');

+

+  // 兑换相关

+  const [exchangeType, setExchangeType] = useState('uploaded');

+  const [exchangeMagic, setExchangeMagic] = useState('');

+  const [exchangeResult, setExchangeResult] = useState(0);

+

+  // 兑换比例

+  const exchangeRate = { uploaded: 10, downloaded: 10, vip_downloads: 100 };

+

+  // 用户申诉相关

+  const [appealOpen, setAppealOpen] = useState(false);

+  const [appealTitle, setAppealTitle] = useState('');

+  const [appealFile, setAppealFile] = useState(null);

+

+  // 兑换结果计算

+  React.useEffect(() => {

+    if (!exchangeMagic || isNaN(exchangeMagic)) {

+      setExchangeResult(0);

+      return;

+    }

+    setExchangeResult(Number(exchangeMagic) / exchangeRate[exchangeType]);

+  }, [exchangeMagic, exchangeType]);

+

+  // 获取用户信息

   useEffect(() => {

     const fetchUserInfo = async () => {

-      // 假设userid存储在localStorage或其他地方

-      // const userid = localStorage.getItem("userid");

-      const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid

+      const userid = "550e8400-e29b-41d4-a716-446655440000";

       if (!userid) return;

       try {

         const res = await fetch(`${API_BASE_URL}/api/user-profile?userid=${userid}`);

-

         if (res.ok) {

           const data = await res.json();

           setUserInfo(data);

           setTempUserInfo(data);

-          // console.log(data);

         }

       } catch (err) {

-        // 可以根据需要处理错误

         console.error("获取用户信息失败", err);

       }

     };

     fetchUserInfo();

   }, []);

 

-  // 动态加载用户上传种子列表

+  // 获取上传种子

   useEffect(() => {

     const fetchUserSeeds = async () => {

-      // const userid = localStorage.getItem("userid");

-      const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid

+      const userid = "550e8400-e29b-41d4-a716-446655440000";

       if (!userid) return;

       try {

         const res = await fetch(`${API_BASE_URL}/api/user-seeds?userid=${userid}`);

@@ -67,11 +95,17 @@
     fetchUserSeeds();

   }, []);

 

-  // 动态加载用户活跃度信息

+  // 收藏种子(示例数据)

+  useEffect(() => {

+    setUserFavorites([

+      { seedid: 'fav1', title: '收藏种子1', tags: '标签A', downloadtimes: 10 },

+    ]);

+  }, []);

+

+  // 获取活跃度

   useEffect(() => {

     const fetchUserStats = async () => {

-      // const userid = localStorage.getItem("userid");

-      const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid

+      const userid = "550e8400-e29b-41d4-a716-446655440000";

       if (!userid) return;

       try {

         const res = await fetch(`${API_BASE_URL}/api/user-stats?userid=${userid}`);

@@ -97,10 +131,7 @@
       tempUserInfo.gender = "f";

     }

     setUserInfo({ ...tempUserInfo });

-    console.log("保存的用户信息:", tempUserInfo);

-    // 获取userid

-    // const userid = localStorage.getItem("userid");

-    const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid

+    const userid = "550e8400-e29b-41d4-a716-446655440000";

     try {

       const res = await fetch(`${API_BASE_URL}/api/change-profile`, {

         method: 'POST',

@@ -127,13 +158,92 @@
     }

   };

 

+  // 邀请

+  const handleInvite = () => {

+    if (!inviteEmail) return;

+    if (userInfo.invite_left <= 0) {

+      setInviteStatus("邀请次数已用完");

+      return;

+    }

+    setInviteStatus("邀请成功!(示例,无后端)");

+    setUserInfo((prev) => ({

+      ...prev,

+      invite_left: prev.invite_left - 1,

+    }));

+    setTempUserInfo((prev) => ({

+      ...prev,

+      invite_left: prev.invite_left - 1,

+    }));

+    setInviteEmail('');

+  };

+

+  // 兑换

+  const handleExchange = () => {

+    const magic = Number(exchangeMagic);

+    if (!magic || isNaN(magic) || magic <= 0) return;

+    if (magic > userStats.magic) {

+      alert("魔力值不足!");

+      return;

+    }

+    let newStats = { ...userStats };

+    if (exchangeType === "uploaded") {

+      newStats.upload += magic / exchangeRate.uploaded;

+    } else if (exchangeType === "downloaded") {

+      newStats.download = Math.max(0, newStats.download - magic / exchangeRate.downloaded);

+    } else if (exchangeType === "vip_downloads") {

+      newStats.vip_downloads += magic / exchangeRate.vip_downloads;

+    }

+    newStats.magic -= magic;

+    setUserStats(newStats);

+    setExchangeMagic('');

+    alert("兑换成功!(示例,无后端)");

+  };

+

+  // 删除种子

+  const handleDeleteSeed = (seedid) => {

+    setUserSeeds(userSeeds.filter((s) => s.seedid !== seedid));

+  };

+

+  // 申诉提交逻辑

+  const handleAppealSubmit = () => {

+    alert('申诉已提交!(示例,无后端)');

+    setAppealOpen(false);

+    setAppealTitle('');

+    setAppealFile(null);

+  };

+

   return (

-    <div className="container" style={{ minHeight: '100vh', background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)', display: 'grid', gridTemplateColumns: '1fr 2fr', gridTemplateRows: 'auto 1fr', gap: '20px', padding: '40px' }}>

-      {/* 左侧:用户资料 */}

-      <div style={{ gridColumn: '1 / 2', gridRow: '1 / 3', display: 'flex', flexDirection: 'column', alignItems: 'center', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>

-        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 16 }}>

+    <div

+      className="container"

+      style={{

+        minHeight: '100vh',

+        background: 'linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%)',

+        display: 'grid',

+        gridTemplateColumns: '1.1fr 1.9fr',

+        gridTemplateRows: 'auto auto',

+        gap: '12px',

+        padding: '24px 3vw',

+        boxSizing: 'border-box'

+      }}

+    >

+      {/* 左上:用户资料 */}

+      <div style={{

+        gridColumn: '1 / 2',

+        gridRow: '1 / 2',

+        display: 'flex',

+        flexDirection: 'column',

+        alignItems: 'center',

+        background: '#fff',

+        borderRadius: 20,

+        boxShadow: '0 6px 32px #e0e7ff',

+        padding: '32px 28px',

+        minWidth: 320,

+        minHeight: 420,

+        transition: 'box-shadow 0.2s',

+      }}>

+        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 18 }}>

           <div onClick={handleAvatarClick} style={{ cursor: 'pointer', position: 'relative' }}>

-            <AccountCircleIcon style={{ fontSize: 90, color: '#1a237e', marginBottom: 12 }} />

+            <AccountCircleIcon style={{ fontSize: 96, color: '#1a237e', marginBottom: 12 }} />

             {tempUserInfo.avatar_url && (

               <img

                 src={tempUserInfo.avatar_url}

@@ -142,204 +252,339 @@
                   position: 'absolute',

                   top: 0,

                   left: 0,

-                  width: 90,

-                  height: 90,

+                  width: 96,

+                  height: 96,

                   borderRadius: '50%',

                   objectFit: 'cover',

+                  border: '2px solid #e0e7ff',

+                  boxShadow: '0 2px 8px #bfcfff'

                 }}

               />

             )}

           </div>

-          <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 24 }}>用户个人资料</h2>

+          <h2 style={{ color: '#1a237e', marginBottom: 0, fontSize: 26, letterSpacing: 1 }}>用户个人资料</h2>

         </div>

-        <div className="card" style={{ padding: 28, width: '100%', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', flex: 1 }}>

-          <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>

+        <div className="card" style={{

+          padding: 32,

+          width: '100%',

+          background: '#fff',

+          borderRadius: 18,

+          boxShadow: '0 2px 12px #e0e7ff',

+          flex: 1,

+          minWidth: 0

+        }}>

+          <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>

             <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>用户名:</b>

-            <input

-              type="text"

+            <TextField

+              variant="outlined"

+              size="small"

               value={tempUserInfo.username}

               onChange={(e) => handleInputChange("username", e.target.value)}

-              style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}

+              sx={{ flex: 1, minWidth: 0 }}

             />

           </div>

-          <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>

+          <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>

             <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邮箱:</b>

-            <span

-              style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15, backgroundColor: '#f5f5f5', color: '#888' }}

-            >

-              {tempUserInfo.email}

-            </span>

+            <TextField

+              variant="outlined"

+              size="small"

+              value={tempUserInfo.email}

+              InputProps={{ readOnly: true }}

+              sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}

+            />

           </div>

-          <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>

+          {/* 邀请功能 */}

+          <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>

             <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>邀请剩余:</b>

-            <span

-              style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15, backgroundColor: '#f5f5f5', color: '#888' }}

-            >

-              {tempUserInfo.invite_left || "0"}

-            </span>

+            <TextField

+              type="email"

+              size="small"

+              placeholder="被邀请邮箱"

+              value={inviteEmail}

+              onChange={e => setInviteEmail(e.target.value)}

+              sx={{ flex: 2, marginRight: 1, minWidth: 120 }}

+              disabled={Number(tempUserInfo.invite_left) === 0}

+            />

+            <Button

+              variant="contained"

+              color="primary"

+              onClick={handleInvite}

+              disabled={Number(tempUserInfo.invite_left) === 0 || !inviteEmail}

+              sx={{ marginRight: 1, minWidth: 80 }}

+            >邀请</Button>

+            <span style={{ color: '#888', fontSize: 15 }}>剩余:{tempUserInfo.invite_left || "0"}</span>

           </div>

-          <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>

+          {inviteStatus && <div style={{ color: '#e53935', fontSize: 14, marginBottom: 8 }}>{inviteStatus}</div>}

+          <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>

             <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>学校:</b>

-            <input

-              type="text"

+            <TextField

+              variant="outlined"

+              size="small"

               value={tempUserInfo.school}

               onChange={(e) => handleInputChange("school", e.target.value)}

-              style={{ flex: 1, padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15 }}

+              sx={{ flex: 1, minWidth: 0 }}

             />

           </div>

-          <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>

+          <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>

             <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>账号状态:</b>

-            <span

-              style={{ flex: 1, display: 'flex', alignItems: 'center', padding: '6px 10px', borderRadius: 7, border: '1px solid #b2b2b2', minWidth: 0, fontSize: 15, backgroundColor: '#f5f5f5', color: '#888' }}

-            >

-              {tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}

-              <span style={{

-                display: 'inline-block',

-                width: 12,

-                height: 12,

-                borderRadius: '50%',

-                backgroundColor: tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? '#e53935' : '#43a047',

-                marginLeft: 10,

-                border: '1px solid #b2b2b2',

-              }} />

-            </span>

+            <TextField

+              variant="outlined"

+              size="small"

+              value={tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? "封禁" : "正常"}

+              InputProps={{ readOnly: true }}

+              sx={{ flex: 1, minWidth: 0, background: '#f5f5f5' }}

+            />

+            <span style={{

+              display: 'inline-block',

+              width: 12,

+              height: 12,

+              borderRadius: '50%',

+              backgroundColor: tempUserInfo.account_status === 1 || tempUserInfo.account_status === "1" ? '#e53935' : '#43a047',

+              marginLeft: 10,

+              border: '1px solid #b2b2b2',

+            }} />

           </div>

-          <div style={{ marginBottom: 18, display: 'flex', alignItems: 'center' }}>

+          <div style={{ marginBottom: 20, display: 'flex', alignItems: 'center' }}>

             <b style={{ width: 72, textAlign: 'left', marginRight: 0, fontSize: 16 }}>性别:</b>

-            <div style={{ position: 'relative', flex: 1 }}>

-              <button

-                onClick={() => setTempUserInfo({ ...tempUserInfo, showGenderOptions: !tempUserInfo.showGenderOptions })}

-                style={{

-                  width: '100%',

-                  padding: '6px 10px',

-                  borderRadius: 7,

-                  border: '1px solid #b2b2b2',

-                  textAlign: 'left',

-                  backgroundColor: '#fff',

-                  fontSize: 15,

-                  cursor: 'pointer',

-                }}

-              >

-                {tempUserInfo.gender === 'm' ? '男性'

-                  : tempUserInfo.gender === 'f' ? '女性'

-                    : '性别'}

-              </button>

-              {tempUserInfo.showGenderOptions && (

-                <ul

-                  style={{

-                    position: 'absolute',

-                    top: '100%',

-                    left: 0,

-                    right: 0,

-                    backgroundColor: '#fff',

-                    border: '1px solid #b2b2b2',

-                    borderRadius: 7,

-                    listStyle: 'none',

-                    margin: 0,

-                    padding: 0,

-                    zIndex: 10,

-                  }}

-                >

-                  {[{ value: 'm', label: '男性' }, { value: 'f', label: '女性' }].map(opt => (

-                    <li

-                      key={opt.value}

-                      onClick={() => setTempUserInfo({ ...tempUserInfo, gender: opt.value, showGenderOptions: false })}

-                      style={{

-                        padding: '6px 10px',

-                        cursor: 'pointer',

-                        borderBottom: '1px solid #e0e0e0',

-                        backgroundColor: tempUserInfo.gender === opt.value ? '#f0f0f0' : '#fff',

-                      }}

-                    >

-                      {opt.label}

-                    </li>

-                  ))}

-                </ul>

-              )}

-            </div>

+            <TextField

+              select

+              variant="outlined"

+              size="small"

+              value={tempUserInfo.gender}

+              onChange={e => handleInputChange("gender", e.target.value)}

+              sx={{ flex: 1, minWidth: 0 }}

+            >

+              <MenuItem value="m">男性</MenuItem>

+              <MenuItem value="f">女性</MenuItem>

+            </TextField>

           </div>

-          <button

-            onClick={handleSave}

-            style={{

-              marginTop: 20,

-              padding: '10px 20px',

-              backgroundColor: '#1a237e',

-              color: '#fff',

-              border: 'none',

-              borderRadius: 7,

-              cursor: 'pointer',

-              fontSize: 16,

-              alignSelf: 'center', // Center the button horizontally

-            }}

-            onMouseOver={(e) => (e.target.style.backgroundColor = '#0d1b5e')}

-            onMouseOut={(e) => (e.target.style.backgroundColor = '#1a237e')}

-          >

-            保存

-          </button>

+          <div style={{ display: 'flex', gap: 16, marginTop: 24, justifyContent: 'flex-end' }}>

+            <Button

+              variant="contained"

+              color="primary"

+              onClick={handleSave}

+              sx={{ fontSize: 16, borderRadius: 2, padding: '10px 24px' }}

+            >保存</Button>

+            <Button

+              variant="contained"

+              color="error"

+              onClick={() => setAppealOpen(true)}

+              sx={{ fontSize: 16, borderRadius: 2, padding: '10px 24px' }}

+            >用户申诉</Button>

+          </div>

         </div>

       </div>

-      {/* 上传种子列表 */}

-      <div style={{ gridColumn: '2 / 3', gridRow: '1 / 2', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>

-        <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18 }}>个人上传种子列表</h3>

-        <div style={{ border: '1px dashed #b2b2b2', borderRadius: 12, minHeight: 60, padding: 12 }}>

+      {/* 左下:活跃度模块 */}

+      <div style={{

+        gridColumn: '1 / 2',

+        gridRow: '2 / 3',

+        background: '#fff',

+        borderRadius: 20,

+        boxShadow: '0 6px 32px #e0e7ff',

+        padding: '32px 28px',

+        minWidth: 320,

+        minHeight: 320,

+        display: 'flex',

+        flexDirection: 'column',

+        justifyContent: 'center'

+      }}>

+        <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>活跃度</h3>

+        <div style={{

+          border: '1.5px dashed #b2b2b2',

+          borderRadius: 14,

+          minHeight: 80,

+          padding: 22,

+          display: 'flex',

+          flexDirection: 'column',

+          gap: 14,

+          fontSize: 18,

+          background: '#f8faff'

+        }}>

+          <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>

+            <span>魔力值:<b style={{ color: '#1976d2' }}>{userStats.magic}</b></span>

+            <TextField

+              type="number"

+              size="small"

+              placeholder="输入兑换魔力值"

+              value={exchangeMagic}

+              onChange={e => setExchangeMagic(e.target.value)}

+              sx={{ width: 100, marginLeft: 2, marginRight: 1 }}

+            />

+            <TextField

+              select

+              size="small"

+              value={exchangeType}

+              onChange={e => setExchangeType(e.target.value)}

+              sx={{ minWidth: 120 }}

+            >

+              <MenuItem value="uploaded">上传量(增加)</MenuItem>

+              <MenuItem value="downloaded">下载量(减少)</MenuItem>

+              <MenuItem value="vip_downloads">VIP下载次数(增加)</MenuItem>

+            </TextField>

+            <span style={{ marginLeft: 8, color: '#43a047' }}>

+              可兑换:<b>{exchangeResult}</b> {exchangeType === 'vip_downloads' ? '次' : 'GB'}

+            </span>

+            <Button

+              variant="contained"

+              color="primary"

+              onClick={handleExchange}

+              disabled={

+                !exchangeMagic ||

+                isNaN(exchangeMagic) ||

+                Number(exchangeMagic) <= 0 ||

+                Number(exchangeMagic) > userStats.magic

+              }

+              sx={{

+                marginLeft: 2,

+                minWidth: 80,

+                background: (!exchangeMagic || isNaN(exchangeMagic) || Number(exchangeMagic) <= 0 || Number(exchangeMagic) > userStats.magic) ? '#ccc' : undefined

+              }}

+            >兑换</Button>

+          </div>

+          <div>上传量:<b style={{ color: '#43a047' }}>{userStats.upload?.toFixed(2)} GB</b></div>

+          <div>下载量:<b style={{ color: '#e53935' }}>{userStats.download?.toFixed(2)} GB</b></div>

+          <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.download === 0 ? "∞" : (userStats.upload / userStats.download).toFixed(2)}</b></div>

+          <div>VIP下载次数:<b style={{ color: '#1976d2' }}>{userStats.vip_downloads}</b></div>

+        </div>

+      </div>

+      {/* 右上:个人上传种子列表 */}

+      <div style={{

+        gridColumn: '2 / 3',

+        gridRow: '1 / 2',

+        background: '#fff',

+        borderRadius: 20,

+        boxShadow: '0 6px 32px #e0e7ff',

+        padding: '32px 36px',

+        minHeight: 420,

+        display: 'flex',

+        flexDirection: 'column'

+      }}>

+        <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人上传种子列表</h3>

+        <div style={{

+          border: '1.5px dashed #b2b2b2',

+          borderRadius: 14,

+          minHeight: 80,

+          padding: 16,

+          background: '#f8faff'

+        }}>

           {userSeeds.length === 0 ? (

             <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无上传种子)</div>

           ) : (

             <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>

               {userSeeds.map((seed, idx) => (

                 <li

-                  key={seed.seed_id || idx}

-                  style={{ display: 'flex', alignItems: 'center', padding: '10px 0', borderBottom: '1px solid #e0e7ff', cursor: 'pointer' }}

+                  key={seed.seedid || idx}

+                  style={{

+                    display: 'flex',

+                    alignItems: 'center',

+                    padding: '12px 0',

+                    borderBottom: idx === userSeeds.length - 1 ? 'none' : '1px solid #e0e7ff',

+                    cursor: 'pointer',

+                    transition: 'background 0.15s'

+                  }}

                   onClick={e => {

-                    // 阻止点击删除按钮时跳转

                     if (e.target.classList.contains('delete-btn')) return;

                     navigate(`/torrent/${seed.seedid}`);

                   }}

+                  onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}

+                  onMouseOut={e => e.currentTarget.style.background = ''}

                 >

                   <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline' }}>{seed.title}</span>

                   <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.tags}</span>

                   <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.downloadtimes}</span>

-                  <button

+                  <Button

                     className="delete-btn"

-                    style={{ marginLeft: 18, background: '#e53935', color: '#fff', border: 'none', borderRadius: 6, padding: '4px 14px', cursor: 'pointer', fontSize: 14 }}

-                    onClick={async (e) => {

+                    variant="contained"

+                    color="error"

+                    size="small"

+                    sx={{ marginLeft: 2, borderRadius: 1, minWidth: 60 }}

+                    onClick={e => {

                       e.stopPropagation();

-                      // const userid = localStorage.getItem("userid");

-                      // const userid = "550e8400-e29b-41d4-a716-446655440000"; // 示例userid

-                      try {

-

-                        const res = await fetch(`${API_BASE_URL}/api/delete-seed`, {

-                          method: 'POST',

-                          headers: { 'Content-Type': 'application/json' },

-                          body: JSON.stringify({ seedid: seed.seedid }),

-                        });

-                        // console.log(seed.seedid);

-                        if (res.ok) {

-                          setUserSeeds(userSeeds.filter((s, i) => (s.seedid || i) !== (seed.seedid || idx)));

-                        } else {

-                          alert('删除失败,请重试');

-                        }

-                      } catch (err) {

-                        alert('删除失败,请检查网络');

-                      }

+                      handleDeleteSeed(seed.seedid);

                     }}

-                  >删除</button>

+                  >删除</Button>

                 </li>

               ))}

             </ul>

           )}

         </div>

       </div>

-      {/* 活跃度模块 */}

-      <div style={{ gridColumn: '2 / 3', gridRow: '2 / 3', background: '#fff', borderRadius: 18, boxShadow: '0 4px 24px #e0e7ff', padding: '20px' }}>

-        <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18 }}>活跃度</h3>

-        <div style={{ border: '1px dashed #b2b2b2', borderRadius: 12, minHeight: 60, padding: 18, display: 'flex', flexDirection: 'column', gap: 12, fontSize: 18 }}>

-          <div>魔力值:<b style={{ color: '#1976d2' }}>{userStats.magic}</b></div>

-          <div>上传量:<b style={{ color: '#43a047' }}>{userStats.upload} GB</b></div>

-          <div>下载量:<b style={{ color: '#e53935' }}>{userStats.download} GB</b></div>

-          <div>上传/下载值:<b style={{ color: '#ff9800' }}>{userStats.ratio}</b></div>

+      {/* 右下:个人收藏种子列表 */}

+      <div style={{

+        gridColumn: '2 / 3',

+        gridRow: '2 / 3',

+        background: '#fff',

+        borderRadius: 20,

+        boxShadow: '0 6px 32px #e0e7ff',

+        padding: '32px 36px',

+        minHeight: 320,

+        display: 'flex',

+        flexDirection: 'column'

+      }}>

+        <h3 style={{ color: '#1a237e', fontSize: 22, marginBottom: 18, letterSpacing: 1 }}>个人收藏种子列表</h3>

+        <div style={{

+          border: '1.5px dashed #b2b2b2',

+          borderRadius: 14,

+          minHeight: 80,

+          padding: 16,

+          background: '#f8faff'

+        }}>

+          {userFavorites.length === 0 ? (

+            <div style={{ color: '#b2b2b2', fontSize: 18, textAlign: 'center' }}>(暂无收藏种子)</div>

+          ) : (

+            <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>

+              {userFavorites.map((seed, idx) => (

+                <li

+                  key={seed.seedid || idx}

+                  style={{

+                    display: 'flex',

+                    alignItems: 'center',

+                    padding: '12px 0',

+                    borderBottom: idx === userFavorites.length - 1 ? 'none' : '1px solid #e0e7ff',

+                    cursor: 'pointer',

+                    transition: 'background 0.15s'

+                  }}

+                  onClick={e => {

+                    navigate(`/torrent/${seed.seedid}`);

+                  }}

+                  onMouseOver={e => e.currentTarget.style.background = '#f3f6ff'}

+                  onMouseOut={e => e.currentTarget.style.background = ''}

+                >

+                  <span style={{ flex: 2, fontWeight: 500, color: '#1a237e', textDecoration: 'underline' }}>{seed.title}</span>

+                  <span style={{ flex: 1, color: '#5c6bc0' }}>{seed.tags}</span>

+                  <span style={{ flex: 1, color: '#ff9800', textAlign: 'right' }}>人气: {seed.downloadtimes}</span>

+                </li>

+              ))}

+            </ul>

+          )}

         </div>

       </div>

+      {/* 申诉弹窗 */}

+      <Dialog open={appealOpen} onClose={() => setAppealOpen(false)}>

+        <DialogTitle>提交申诉</DialogTitle>

+        <DialogContent>

+          <div style={{ marginBottom: 16 }}>

+            <TextField

+              label="申诉主题"

+              fullWidth

+              value={appealTitle}

+              onChange={e => setAppealTitle(e.target.value)}

+              size="small"

+            />

+          </div>

+          <div>

+            <input

+              type="file"

+              onChange={e => setAppealFile(e.target.files[0])}

+              style={{ marginTop: 8 }}

+            />

+          </div>

+        </DialogContent>

+        <DialogActions>

+          <Button onClick={handleAppealSubmit} variant="contained" color="primary" disabled={!appealTitle || !appealFile}>提交</Button>

+          <Button onClick={() => setAppealOpen(false)} variant="outlined">取消</Button>

+        </DialogActions>

+      </Dialog>

     </div>

   );

 }
\ No newline at end of file
diff --git a/recommend/inference.py b/recommend/inference.py
index 346209c..9601230 100644
--- a/recommend/inference.py
+++ b/recommend/inference.py
@@ -1,53 +1,57 @@
 import sys
 sys.path.append('./')
 
-from os import path
-from utils.parse_args import args
-from utils.data_loader import EdgeListData
-from model.LightGCN import LightGCN
+import time
 import torch
 import numpy as np
-import time
+from os import path
+from model.LightGCN import LightGCN
+from utils.parse_args import args
+from utils.data_loader import EdgeListData
+from utils.data_generator import build_user_seed_graph
 
-# 计时:脚本开始
-t_start = time.time()
-
-# 配置参数
 args.device = 'cuda:7'
 args.data_path = './user_seed_graph.txt'
 args.pre_model_path = './model/LightGCN_pretrained.pt'
 
+def run_inference(user_id=1):
+    # 1. 实时生成user-seed交互图
+    print("正在生成用户-种子交互文件...")
+    build_user_seed_graph()
 
-# 1. 加载数据集
-t_data_start = time.time()
-dataset = EdgeListData(args.data_path, args.data_path)
-t_data_end = time.time()
+    # 2. 加载数据集
+    print("正在加载数据集...")
+    t_data_start = time.time()
+    dataset = EdgeListData(args.data_path, args.data_path)
+    t_data_end = time.time()
 
+    # 3. 加载LightGCN模型
+    print("正在加载模型参数...")
+    pretrained_dict = torch.load(args.pre_model_path, map_location=args.device, weights_only=True)
+    pretrained_dict['user_embedding'] = pretrained_dict['user_embedding'][:dataset.num_users]
+    pretrained_dict['item_embedding'] = pretrained_dict['item_embedding'][:dataset.num_items]
 
-# 2. 加载LightGCN模型
-pretrained_dict = torch.load(args.pre_model_path, map_location=args.device, weights_only=True)
-pretrained_dict['user_embedding'] = pretrained_dict['user_embedding'][:dataset.num_users]
-pretrained_dict['item_embedding'] = pretrained_dict['item_embedding'][:dataset.num_items]
+    model = LightGCN(dataset, phase='vanilla').to(args.device)
+    model.load_state_dict(pretrained_dict, strict=False)
+    model.eval()
 
-model = LightGCN(dataset, phase='vanilla').to(args.device)
-model.load_state_dict(pretrained_dict, strict=False)
-model.eval()
+    # 4. 推理
+    print(f"正在为用户 {user_id} 推理推荐结果...")
+    t_infer_start = time.time()
+    with torch.no_grad():
+        user_emb, item_emb = model.generate()
+        user_vec = user_emb[user_id].unsqueeze(0)
+        scores = model.rating(user_vec, item_emb).squeeze(0)
+        pred_item = torch.argmax(scores).item()
+    t_infer_end = time.time()
 
-# 3. 输入用户ID
-user_id = 1
+    print(f"用户{user_id}下一个最可能点击的物品ID为: {pred_item}")
+    print(f"加载数据集耗时: {t_data_end - t_data_start:.4f} 秒")
+    print(f"推理耗时: {t_infer_end - t_infer_start:.4f} 秒")
 
-# 4. 推理:获取embedding并打分
-t_infer_start = time.time()
-with torch.no_grad():
-    user_emb, item_emb = model.generate()
-    user_vec = user_emb[user_id].unsqueeze(0)
-    scores = model.rating(user_vec, item_emb).squeeze(0)
-    pred_item = torch.argmax(scores).item()
-t_infer_end = time.time()
-
-t_end = time.time()
-
-print(f"用户{user_id}下一个最可能点击的物品ID为: {pred_item}")
-print(f"加载数据集耗时: {t_data_end - t_data_start:.4f} 秒")
-print(f"推理耗时: {t_infer_end - t_infer_start:.4f} 秒")
-print(f"脚本总耗时: {t_end - t_start:.4f} 秒")
\ No newline at end of file
+if __name__ == "__main__":
+    t_start = time.time()
+    user_id = 1
+    run_inference(user_id)
+    t_end = time.time()
+    print(f"脚本总耗时: {t_end - t_start:.4f} 秒")
\ No newline at end of file
diff --git a/recommend/utils/data_generator.py b/recommend/utils/data_generator.py
index e50b4e9..d3ef9cf 100644
--- a/recommend/utils/data_generator.py
+++ b/recommend/utils/data_generator.py
@@ -67,8 +67,6 @@
         sid = seed2idx[seed_id]
         user_items[uid].append(sid)
         user_times[uid].append(ts)
-    print(user_items)
-    print(user_times)
     with open(output_path, "w", encoding="utf-8") as f:
         for uid in sorted(user_items.keys()):
             items = " ".join(str(item) for item in user_items[uid])
@@ -76,12 +74,8 @@
             f.write(f"{uid}\t{items}\t{times}\n")
 
 
-def main():
+def build_user_seed_graph():
     download_rows, favorite_rows = fetch_data()
     records, user_set, seed_set = process_records(download_rows, favorite_rows)
     user2idx, seed2idx = build_id_maps(user_set, seed_set)
     group_and_write(records, user2idx, seed2idx)
-
-
-if __name__ == "__main__":
-    main()
\ No newline at end of file