新增后台接口

Change-Id: Ibd8183079a77aad05b2c81658c2d6fe9b648e042
diff --git a/front/src/AdminPage.js b/front/src/AdminPage.js
index 4862449..dac05cb 100644
--- a/front/src/AdminPage.js
+++ b/front/src/AdminPage.js
@@ -1,5 +1,6 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
 import { useNavigate } from "react-router-dom";
+import { API_BASE_URL } from "./config";
 
 // 示例数据
 const initialConfig = {
@@ -9,19 +10,46 @@
     CheatTime: 5,
 };
 
-const cheatUsers = [
-    { user_id: "u001", email: "cheat1@example.com", username: "cheater1", account_status: 1 },
-    { user_id: "u002", email: "cheat2@example.com", username: "cheater2", account_status: 0 },
-];
-
-const suspiciousUsers = [
-    { user_id: "u101", email: "suspect1@example.com", username: "suspect1", account_status: 0 },
-    { user_id: "u102", email: "suspect2@example.com", username: "suspect2", account_status: 0 },
-];
-
 export default function AdminPage() {
     const navigate = useNavigate();
     const [config, setConfig] = useState(initialConfig);
+    // state for users fetched from backend
+    const [cheatUsers, setCheatUsers] = useState([]);
+    const [suspiciousUsers, setSuspiciousUsers] = useState([]);
+
+    // helper to get admin userId from cookie
+    const getUserIdFromCookie = () => {
+        const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+        return match ? match[2] : null;
+    };
+
+    // fetch cheat users list from backend
+    const fetchCheatUsers = async () => {
+        const adminId = getUserIdFromCookie();
+        if (!adminId) return;
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/admin/cheat-users?userid=${adminId}`);
+            if (!res.ok) throw new Error('获取作弊用户失败');
+            const data = await res.json();
+            setCheatUsers(data);
+        } catch (err) {
+            console.error(err);
+        }
+    };
+
+    // fetch suspicious users list from backend
+    const fetchSuspiciousUsers = async () => {
+        const adminId = getUserIdFromCookie();
+        if (!adminId) return;
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/admin/suspicious-users?userid=${adminId}`);
+            if (!res.ok) throw new Error('获取可疑用户失败');
+            const data = await res.json();
+            setSuspiciousUsers(data);
+        } catch (err) {
+            console.error(err);
+        }
+    };
 
     const handleConfigChange = (e) => {
         const { name, value } = e.target;
@@ -29,31 +57,129 @@
     };
 
     const handleBan = (user) => {
-        alert(`已封禁用户:${user.username}`);
+        const adminId = getUserIdFromCookie();
+        if (!adminId) {
+            alert('无法获取用户ID');
+            return;
+        }
+        fetch(`${API_BASE_URL}/api/admin/ban-user`, {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ userid: user.userid }),
+        })
+            .then((res) => { if (!res.ok) throw new Error('Network response was not ok'); return res.json(); })
+            .then(() => {
+                // 重新获取用户列表,触发页面重新渲染
+                fetchSuspiciousUsers();
+                fetchCheatUsers();
+            })
+            .catch((err) => console.error('封禁用户失败:', err));
+    }
+
+    // 解封作弊用户
+    const handleUnban = (user) => {
+        const adminId = getUserIdFromCookie();
+        if (!adminId) {
+            alert('无法获取用户ID');
+            return;
+        }
+        fetch(`${API_BASE_URL}/api/admin/unban-user`, {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ userid: user.userid }),
+        })
+            .then((res) => { if (!res.ok) throw new Error('Network response was not ok'); return res.json(); })
+            .then(() => {
+                // 重新获取用户列表,触发页面重新渲染
+                fetchCheatUsers();
+                fetchSuspiciousUsers();
+            })
+            .catch((err) => console.error('解封用户失败:', err));
     };
 
+    // 保存系统参数到后端
+    const handleSave = () => {
+        const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+        const userId = match ? match[2] : null;
+        if (!userId) {
+            alert('无法获取用户ID');
+            return;
+        }
+        fetch(`${API_BASE_URL}/api/save-config`, {
+            method: 'POST',
+            // credentials: 'include',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ userid: userId, ...config }),
+        })
+            .then((res) => {
+                if (!res.ok) throw new Error('Network response was not ok');
+                return res.json();
+            })
+            .then(() => alert('系统参数已保存'))
+            .catch((err) => console.error('保存系统参数失败:', err));
+    };
+
+    // 初始化时向后端请求系统参数及用户列表
+    useEffect(() => {
+        const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+        const userId = match ? match[2] : null;
+        // console.log("User ID from cookie:", userId);
+        if (userId) {
+            // fetch config
+            fetch(`${API_BASE_URL}/api/admin/config?userid=${userId}`)
+                .then((res) => {
+                    if (!res.ok) throw new Error('Network response was not ok');
+                    return res.json();
+                })
+                .then((data) => {
+                    // console.log("Fetched system config:", data);
+                    setConfig(data);
+                })
+                .catch((err) => console.error('获取系统参数失败:', err));
+
+            // 初始获取用户列表
+            fetchCheatUsers();
+            fetchSuspiciousUsers();
+        }
+    }, []);
+
     return (
         <div style={{ padding: 40, maxWidth: 900, margin: "0 auto" }}>
             <h1 style={{ textAlign: "center", marginBottom: 32 }}>管理员页面</h1>
             {/* 参数设置 */}
-            <div style={{ marginBottom: 32, padding: 18, background: "#f7faff", borderRadius: 12, display: "flex", gap: 24, alignItems: "center" }}>
+            <div style={{ marginBottom: 32, padding: 18, background: "#f7faff", borderRadius: 12, display: "flex", gap: 24, alignItems: "center", justifyContent: "space-between" }}>
                 <b>系统参数:</b>
                 <label>
                     FarmNumber:
-                    <input type="number" name="FarmNumber" value={config.FarmNumber} onChange={handleConfigChange} style={{ width: 60, margin: "0 12px" }} />
+                    <input type="number" name="FarmNumber" value={config.FarmNumber} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
                 </label>
                 <label>
                     FakeTime:
-                    <input type="number" name="FakeTime" value={config.FakeTime} onChange={handleConfigChange} style={{ width: 60, margin: "0 12px" }} />
+                    <input type="number" name="FakeTime" value={config.FakeTime} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
                 </label>
                 <label>
                     BegVote:
-                    <input type="number" name="BegVote" value={config.BegVote} onChange={handleConfigChange} style={{ width: 60, margin: "0 12px" }} />
+                    <input type="number" name="BegVote" value={config.BegVote} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
                 </label>
                 <label>
                     CheatTime:
-                    <input type="number" name="CheatTime" value={config.CheatTime} onChange={handleConfigChange} style={{ width: 60, margin: "0 12px" }} />
+                    <input type="number" name="CheatTime" value={config.CheatTime} onChange={handleConfigChange} disabled style={{ width: 60, margin: "0 12px" }} />
                 </label>
+                {/* <button
+                    onClick={handleSave}
+                    style={{
+                        background: '#1976d2',
+                        color: '#fff',
+                        border: 'none',
+                        borderRadius: 6,
+                        padding: '6px 18px',
+                        cursor: 'pointer',
+                        writingMode: 'horizontal-tb',
+                        whiteSpace: 'nowrap'
+                    }}
+                >
+                    保存
+                </button> */}
             </div>
             {/* 作弊用户 */}
             <div style={{ marginBottom: 32 }}>
@@ -61,7 +187,7 @@
                 <table style={{ width: "100%", background: "#fff", borderRadius: 10, boxShadow: "0 2px 8px #e0e7ff", marginBottom: 18 }}>
                     <thead>
                         <tr style={{ background: "#f5f5f5" }}>
-                            <th>user_id</th>
+                            {/* <th>user_id</th> */}
                             <th>email</th>
                             <th>username</th>
                             <th>account_status</th>
@@ -70,19 +196,19 @@
                     </thead>
                     <tbody>
                         {cheatUsers.map((u) => (
-                            <tr key={u.user_id}>
-                                <td>{u.user_id}</td>
+                            <tr key={u.userid}>
+                                {/* <td>{u.userid}</td> */}
                                 <td>{u.email}</td>
                                 <td>{u.username}</td>
-                                <td style={{ color: u.account_status === 1 ? "#e53935" : "#43a047" }}>
-                                    {u.account_status === 1 ? "封禁" : "正常"}
+                                <td style={{ color: "#e53935" }}>
+                                    {"封禁" }
                                 </td>
                                 <td>
                                     <button
-                                        style={{ background: "#e53935", color: "#fff", border: "none", borderRadius: 6, padding: "4px 14px", cursor: "pointer" }}
-                                        onClick={() => handleBan(u)}
+                                        style={{ background: "#13F31E", color: "#fff", border: "none", borderRadius: 6, padding: "4px 14px", cursor: "pointer" }}
+                                        onClick={() => handleUnban(u)}
                                     >
-                                        封禁
+                                        解封
                                     </button>
                                 </td>
                             </tr>
@@ -96,7 +222,7 @@
                 <table style={{ width: "100%", background: "#fff", borderRadius: 10, boxShadow: "0 2px 8px #e0e7ff" }}>
                     <thead>
                         <tr style={{ background: "#f5f5f5" }}>
-                            <th>user_id</th>
+                            {/* <th>user_id</th> */}
                             <th>email</th>
                             <th>username</th>
                             <th>account_status</th>
@@ -106,7 +232,7 @@
                     <tbody>
                         {suspiciousUsers.map((u) => (
                             <tr key={u.user_id}>
-                                <td>{u.user_id}</td>
+                                {/* <td>{u.user_id}</td> */}
                                 <td>{u.email}</td>
                                 <td>{u.username}</td>
                                 <td style={{ color: u.account_status === 1 ? "#e53935" : "#43a047" }}>
diff --git a/front/src/AnimePage.js b/front/src/AnimePage.js
index 3cf5ac9..118d8fb 100644
--- a/front/src/AnimePage.js
+++ b/front/src/AnimePage.js
@@ -10,7 +10,6 @@
 import "./App.css";

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

 import { API_BASE_URL } from "./config";

-import ForumIcon from "@mui/icons-material/Forum";

 

 const navItems = [

   { label: "电影", icon: <MovieIcon />, path: "/movie" },

@@ -20,7 +19,6 @@
   { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },

   { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },

   { label: "资料", icon: <PersonIcon />, path: "/info" },

-  { label: "论坛", icon: <ForumIcon />, path: "/forum" },

   { label: "发布", icon: <AccountCircleIcon />, path: "/publish" }, // Added Publish option

 ];

 

diff --git a/front/src/AppealPage.js b/front/src/AppealPage.js
index a0314c4..dfbe307 100644
--- a/front/src/AppealPage.js
+++ b/front/src/AppealPage.js
@@ -1,50 +1,68 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
+import { API_BASE_URL } from "./config";
 
-// 示例申诉数据
-const appeals = [
-    {
-        appeal_id: "a001",
-        user_id: "u001",
-        content: "我没有作弊,请审核我的账号。",
-        file_url: "http://sse.bjtu.edu.cn/media/attachments/2024/10/20241012160658.pdf",
-        status: 0,
-    },
-    {
-        appeal_id: "a002",
-        user_id: "u002",
-        content: "误封申诉,详见附件。",
-        file_url: "http://sse.bjtu.edu.cn/media/attachments/2024/10/20241012160658.pdf",
-        status: 1,
-    },
-];
-
-// 简单PDF预览组件
-function FileViewer({ url }) {
-    if (!url) return <div>无附件</div>;
-    if (url.endsWith(".pdf")) {
-        return (
-            <iframe
-                src={url}
-                title="PDF预览"
-                width="100%"
-                height="400px"
-                style={{ border: "1px solid #ccc", borderRadius: 8 }}
-            />
-        );
-    }
-    // 这里只做PDF示例,实际可扩展为DOC等
-    return <a href={url} target="_blank" rel="noopener noreferrer">下载附件</a>;
-}
-
+// State for appeals fetched from backend
 export default function AppealPage() {
-    const [selectedId, setSelectedId] = useState(appeals[0].appeal_id);
-    const selectedAppeal = appeals.find(a => a.appeal_id === selectedId);
+    const [appeals, setAppeals] = useState([]);
+    const [selectedId, setSelectedId] = useState(null);
+    const [loading, setLoading] = useState(true);
+    const [error, setError] = useState(null);
 
-    const handleApprove = () => {
-        alert("已通过申诉(示例,无实际状态变更)");
+    // Helper to load appeals
+    const fetchAppeals = async () => {
+        setLoading(true);
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/appeals`);
+            if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
+            const data = await res.json();
+            // console.log("Fetched appeals:", data);
+            setAppeals(data);
+            if (data.length > 0) setSelectedId(data[0].appealid);
+            setError(null);
+        } catch (err) {
+            setError(err.message);
+        } finally {
+            setLoading(false);
+        }
     };
-    const handleReject = () => {
-        alert("已拒绝申诉(示例,无实际状态变更)");
+
+    useEffect(() => {
+        fetchAppeals();
+    }, []);
+
+    if (loading) return <div>加载中...</div>;
+    if (error) return <div>加载失败:{error}</div>;
+    const selectedAppeal = appeals.find(a => a.appealid === selectedId) || {};
+
+    // Approve selected appeal and refresh
+    const handleApprove = async () => {
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/appeals-approve`, {
+                method: "POST",
+                headers: { "Content-Type": "application/json" },
+                body: JSON.stringify({ appealid: selectedAppeal.appealid })
+            });
+            if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
+            alert("已通过申诉");
+            await fetchAppeals();
+        } catch (err) {
+            alert(`操作失败:${err.message}`);
+        }
+    };
+    // Reject selected appeal and refresh
+    const handleReject = async () => {
+      try {
+        const res = await fetch(`${API_BASE_URL}/api/appeals-reject`, {
+          method: "POST",
+          headers: { "Content-Type": "application/json" },
+          body: JSON.stringify({ appealid: selectedAppeal.appealid })
+        });
+        if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
+        alert("已拒绝申诉");
+        await fetchAppeals();
+      } catch (err) {
+        alert(`操作失败:${err.message}`);
+      }
     };
 
     return (
@@ -55,28 +73,28 @@
                 <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
                     {appeals.map(a => (
                         <div
-                            key={a.appeal_id}
-                            onClick={() => setSelectedId(a.appeal_id)}
+                            key={a.appealid}
+                            onClick={() => setSelectedId(a.appealid)}
                             style={{
                                 margin: "0 12px",
                                 padding: "16px 10px",
                                 borderRadius: 8,
-                                background: selectedId === a.appeal_id ? "#e3f2fd" : "#fff",
-                                border: `2px solid ${a.status === 1 ? "#43a047" : "#e53935"}`,
-                                color: a.status === 1 ? "#43a047" : "#e53935",
+                                background: selectedId === a.appealid ? "#e3f2fd" : "#fff",
+                                border: `2px solid ${a.status === 1 || a.status === 2 ? "#43a047" : "#e53935"}`,
+                                color: a.status === 1 || a.status === 2 ? "#43a047" : "#e53935",
                                 fontWeight: 600,
                                 cursor: "pointer",
-                                boxShadow: selectedId === a.appeal_id ? "0 2px 8px #b2d8ea" : "none",
+                                boxShadow: selectedId === a.appealid ? "0 2px 8px #b2d8ea" : "none",
                                 transition: "all 0.2s"
                             }}
                         >
-                            {a.appeal_id}
+                            {a.user.username}
                             <span style={{
                                 float: "right",
                                 fontSize: 12,
-                                color: a.status === 1 ? "#43a047" : "#e53935"
+                                color: a.status === 1 || a.status === 2 ? "#43a047" : "#e53935"
                             }}>
-                                {a.status === 1 ? "已审核" : "未审核"}
+                                {a.status === 1 || a.status === 2 ? "已审核" : "未审核"}
                             </span>
                         </div>
                     ))}
@@ -87,17 +105,17 @@
                 <h2 style={{ marginBottom: 24, color: "#1976d2" }}>申诉详情</h2>
                 <div style={{ background: "#fff", borderRadius: 12, padding: 32, boxShadow: "0 2px 8px #e0e7ff", marginBottom: 32 }}>
                     <div style={{ marginBottom: 18 }}>
-                        <b>申诉ID:</b>{selectedAppeal.appeal_id}
+                        <b>申诉ID:</b>{selectedAppeal.appealid}
                     </div>
                     <div style={{ marginBottom: 18 }}>
-                        <b>用户ID:</b>{selectedAppeal.user_id}
+                        <b>用户ID:</b>{selectedAppeal.user.userid}
                     </div>
                     <div style={{ marginBottom: 18 }}>
                         <b>申诉内容:</b>{selectedAppeal.content}
                     </div>
                     <div style={{ marginBottom: 18 }}>
                         <b>申诉文件:</b>
-                        <FileViewer url={selectedAppeal.file_url} />
+                        <FileViewer url={selectedAppeal.fileURL} />
                     </div>
                 </div>
                 {/* 审核按钮 */}
@@ -138,4 +156,22 @@
             </div>
         </div>
     );
+}
+
+// 简单PDF预览组件
+function FileViewer({ url }) {
+    if (!url) return <div>无附件</div>;
+    if (url.endsWith(".pdf")) {
+        return (
+            <iframe
+                src={url}
+                title="PDF预览"
+                width="100%"
+                height="400px"
+                style={{ border: "1px solid #ccc", borderRadius: 8 }}
+            />
+        );
+    }
+    // 这里只做PDF示例,实际可扩展为DOC等
+    return <a href={url} target="_blank" rel="noopener noreferrer">下载附件</a>;
 }
\ No newline at end of file
diff --git a/front/src/ForumPage.js b/front/src/ForumPage.js
index abcba7d..7939da1 100644
--- a/front/src/ForumPage.js
+++ b/front/src/ForumPage.js
@@ -29,18 +29,56 @@
 export default function ForumPage() {
     const navigate = useNavigate();
     const [posts, setPosts] = useState([]);
+    const [searchQuery, setSearchQuery] = useState('');
 
     useEffect(() => {
         // get userId from cookie
         const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
         const userId = match ? match[2] : null;
         console.log("User ID from cookie:", userId);
-        if (!userId) return;
+        // if (!userId) return;
+        console.log("Fetching forum posts...");
         fetch(`${API_BASE_URL}/api/forum`)
-            .then(res => res.json())
-            .then(data => setPosts(data))
+            .then(res => {
+                // console.log("fetch raw response:", res);
+                return res.json();
+            })
+            .then(data => {
+                // console.log("fetch forum data:", data);
+                const formattedPosts = data.map(post => ({
+                    post_id: post.postid,
+                    title: post.posttitle,
+                    content: post.postcontent,
+                    author_id: post.postuserid,
+                    author_name: post.author?.username || '',
+                    created_at: new Date(post.posttime).toLocaleString(),
+                    reply_count: post.replytime,
+                    view_count: post.readtime,
+                }));
+                setPosts(formattedPosts);
+            })
             .catch(() => setPosts([]));
     }, []);
+    
+    // Handler to perform search based on input value
+    const handleSearch = () => {
+      fetch(`${API_BASE_URL}/api/search-posts?keyword=${encodeURIComponent(searchQuery)}`)
+        .then(res => res.json())
+        .then(data => {
+          const formattedPosts = data.map(post => ({
+            post_id: post.postid,
+            title: post.posttitle,
+            content: post.postcontent,
+            author_id: post.postuserid,
+            author_name: post.author?.username || '',
+            created_at: new Date(post.posttime).toLocaleString(),
+            reply_count: post.replytime,
+            view_count: post.readtime,
+          }));
+          setPosts(formattedPosts);
+        })
+        .catch(() => setPosts([]));
+    };
 
     return (
         <div style={{ maxWidth: 700, margin: "40px auto" }}>
@@ -49,6 +87,8 @@
                 <input
                     type="text"
                     placeholder="搜索帖子内容"
+                    value={searchQuery}
+                    onChange={e => setSearchQuery(e.target.value)}
                     style={{
                         flex: 1,
                         fontSize: 16,
@@ -69,6 +109,7 @@
                         borderRadius: 8,
                         cursor: "pointer",
                     }}
+                    onClick={handleSearch}
                 >
                     🔍
                 </button>
diff --git a/front/src/GamePage.js b/front/src/GamePage.js
index c1ce231..1de4d30 100644
--- a/front/src/GamePage.js
+++ b/front/src/GamePage.js
@@ -10,7 +10,6 @@
 import "./App.css";

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

 import { API_BASE_URL } from "./config";

-import ForumIcon from "@mui/icons-material/Forum";

 

 const navItems = [

   { label: "电影", icon: <MovieIcon />, path: "/movie" },

@@ -20,7 +19,6 @@
   { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },

   { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },

   { label: "资料", icon: <PersonIcon />, path: "/info" },

-  { label: "论坛", icon: <ForumIcon />, path: "/forum" },

   { label: "发布", icon: <AccountCircleIcon />, path: "/publish" }, // Added Publish option

 ];

 

diff --git a/front/src/InfoPage.js b/front/src/InfoPage.js
index 90dc449..0ed6ade 100644
--- a/front/src/InfoPage.js
+++ b/front/src/InfoPage.js
@@ -10,7 +10,6 @@
 import "./App.css";

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

 import { API_BASE_URL } from "./config";

-import ForumIcon from "@mui/icons-material/Forum";

 

 const navItems = [

   { label: "电影", icon: <MovieIcon />, path: "/movie" },

@@ -20,7 +19,6 @@
   { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },

   { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },

   { label: "资料", icon: <PersonIcon />, path: "/info" },

-  { label: "论坛", icon: <ForumIcon />, path: "/forum" },

   { label: "发布", icon: <AccountCircleIcon />, path: "/publish" }, // Added Publish option

 ];

 

diff --git a/front/src/LoginPage.js b/front/src/LoginPage.js
index 890b690..492c018 100644
--- a/front/src/LoginPage.js
+++ b/front/src/LoginPage.js
@@ -16,10 +16,10 @@
 

   const handleLogin = async () => {

     // 进入管理员页面

-    if (formData.username === "admin" && formData.password === "admin123") {

-      navigate('/admin');

-      return;

-    }

+    // if (formData.username === "admin" && formData.password === "admin123") {

+    //   navigate('/admin');

+    //   return;

+    // }

 

     if (formData.password.length < 8) {

       setErrorMessage('密码必须至少包含八位字符!');

@@ -47,6 +47,12 @@
         return;

       }

       document.cookie = `userId=${userId}; path=/`;

+      // 如果是管理员ID,则跳转到管理员页面

+      if (userId === 'admin111') {

+        setErrorMessage('');

+        navigate('/admin');

+        return;

+      }

       setErrorMessage('');

       navigate('/movie');

     } catch (error) {

diff --git a/front/src/MigrationPage.js b/front/src/MigrationPage.js
index ed88a55..f29a534 100644
--- a/front/src/MigrationPage.js
+++ b/front/src/MigrationPage.js
@@ -1,28 +1,5 @@
-import React, { useState } from "react";
-
-// 示例迁移数据
-const migrations = [
-    {
-        migration_id: "m001",
-        user_id: "u001",
-        application_url: "http://sse.bjtu.edu.cn/media/attachments/2024/10/20241012160658.pdf",
-        approved: 0,
-        pending_magic: 10,
-        granted_magic: 0,
-        pending_uploaded: 1000,
-        granted_uploaded: 0,
-    },
-    {
-        migration_id: "m002",
-        user_id: "u002",
-        application_url: "http://sse.bjtu.edu.cn/media/attachments/2024/10/20241012160658.pdf",
-        approved: 1,
-        pending_magic: 20,
-        granted_magic: 20,
-        pending_uploaded: 2000,
-        granted_uploaded: 2000,
-    },
-];
+import React, { useState, useEffect } from "react";
+import { API_BASE_URL } from "./config";
 
 // 简单PDF预览组件
 function FileViewer({ url }) {
@@ -43,14 +20,76 @@
 }
 
 export default function MigrationPage() {
-    const [selectedId, setSelectedId] = useState(migrations[0].migration_id);
-    const selectedMigration = migrations.find(m => m.migration_id === selectedId);
+    const [migrations, setMigrations] = useState([]);
+    const [selectedId, setSelectedId] = useState(null);
+    const [loading, setLoading] = useState(true);
+    const [error, setError] = useState(null);
 
-    const handleApprove = () => {
-        alert("已通过迁移(示例,无实际状态变更)");
+    // Helper to load migrations list
+    const fetchMigrations = async () => {
+        setLoading(true);
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/migrations`);
+            if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
+            const data = await res.json();
+            const formatted = data.map(item => ({
+                migration_id: item.profileurl,
+                user_id: item.user.userid,
+                application_url: item.applicationurl,
+                pending_magic: Number(item.magictogive),
+                granted_magic: Number(item.magicgived),
+                pending_uploaded: Number(item.uploadtogive),
+                granted_uploaded: Number(item.uploadgived),
+                approved: item.exampass ? 1 : 0
+            }));
+            setMigrations(formatted);
+            if (formatted.length > 0) setSelectedId(formatted[0].migration_id);
+            setError(null);
+        } catch (err) {
+            setError(err.message);
+        } finally {
+            setLoading(false);
+        }
     };
-    const handleReject = () => {
-        alert("已拒绝迁移(示例,无实际状态变更)");
+
+    useEffect(() => {
+        fetchMigrations();
+    }, []);
+
+    if (loading) return <div>加载中...</div>;
+    if (error) return <div>加载失败:{error}</div>;
+
+    const selectedMigration = migrations.find(m => m.migration_id === selectedId) || {};
+
+    // Approve selected migration and refresh
+    const handleApprove = async () => {
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/migrations-approve`, {
+                method: "POST",
+                headers: { "Content-Type": "application/json" },
+                body: JSON.stringify({ migration_id: selectedMigration.migration_id })
+            });
+            if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
+            alert("已通过迁移");
+            await fetchMigrations();
+        } catch (err) {
+            alert(`操作失败:${err.message}`);
+        }
+    };
+    // Reject selected migration and refresh
+    const handleReject = async () => {
+        try {
+            const res = await fetch(`${API_BASE_URL}/api/migrations-reject`, {
+                method: "POST",
+                headers: { "Content-Type": "application/json" },
+                body: JSON.stringify({ migration_id: selectedMigration.migration_id })
+            });
+            if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
+            alert("已拒绝迁移");
+            await fetchMigrations();
+        } catch (err) {
+            alert(`操作失败:${err.message}`);
+        }
     };
 
     return (
diff --git a/front/src/MoviePage.js b/front/src/MoviePage.js
index faef3bd..b01d324 100644
--- a/front/src/MoviePage.js
+++ b/front/src/MoviePage.js
@@ -1,4 +1,4 @@
-import React from "react";

+import React, { useState, useEffect } from "react";

 import MovieIcon from "@mui/icons-material/Movie";

 import EmailIcon from "@mui/icons-material/Email";

 import MusicNoteIcon from "@mui/icons-material/MusicNote";

@@ -40,8 +40,32 @@
 

 export default function MoviePage() {

   const navigate = useNavigate();

+  const [searchText, setSearchText] = React.useState('');

+  const [userInfo, setUserInfo] = useState({ avatar_url: '', username: '' });

+  const [userPT, setUserPT] = useState({ magic: 0, ratio: 0, upload: 0, download: 0 });

   const [activeTab, setActiveTab] = React.useState(0);

   const [movieList, setMovieList] = React.useState([]);

+  

+

+  useEffect(() => {

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userId = match ? match[2] : null;

+    console.log("User ID from cookie:", userId);

+    if (!userId) return;

+    fetch(`${API_BASE_URL}/api/get-userpt?userid=${encodeURIComponent(userId)}`)

+      .then(res => res.json())

+      .then(data => {

+        console.log('用户PT信息:', data); 

+        setUserInfo({ avatar_url: data.user.avatar_url, username: data.user.username });

+        setUserPT({

+          magic: data.magic_value || data.magic || 0,

+          ratio: data.share_ratio || data.share || 0,

+          upload: data.upload_amount || data.upload || 0,

+          download: data.download_amount || data.download || 0,

+        });

+      })

+      .catch(err => console.error('Fetching user profile failed', err));

+  }, []);

 

   // 每个tab对应的电影类型

   const movieTypesList = [

@@ -65,20 +89,36 @@
       .catch(() => setMovieList([]));

   }, [activeTab]);

 

+  // 搜索按钮处理

+  const handleSearch = () => {

+    const area = areaTabs[activeTab].label;

+    fetch(`${API_BASE_URL}/api/search-seeds?tag=${encodeURIComponent(area)}&keyword=${encodeURIComponent(searchText)}`)

+      .then(res => res.json())

+      .then(data => {

+        console.log('搜索返回数据:', data);

+        setMovieList(data);

+      })

+      .catch(() => setMovieList([]));

+  };

+

   return (

     <div className="container">

       {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}

       <div style={{ height: 80 }} />

       <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>

         <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>

-          <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />

+          {userInfo.avatar_url ? (

+            <img src={userInfo.avatar_url} alt="用户头像" style={{ width: 38, height: 38, borderRadius: '50%', objectFit: 'cover' }} />

+          ) : (

+            <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />

+          )}

         </div>

-        <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>

+        <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>{userInfo.username || '用户栏'}</div>

         <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>

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

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>{userPT.ratio}</b></span>

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>{userPT.upload}</b></span>

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>{userPT.download}</b></span>

         </div>

       </div>

       {/* 下方内容整体下移,留出与音乐界面一致的间距 */}

@@ -96,8 +136,13 @@
         ))}

       </nav>

       <div className="search-section card">

-        <input className="search-input" placeholder="输入搜索关键词" />

-        <button className="search-btn">

+        <input

+          className="search-input"

+          placeholder="输入搜索关键词"

+          value={searchText}

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

+        />

+        <button className="search-btn" onClick={handleSearch}>

           <span role="img" aria-label="search">🔍</span>

         </button>

       </div>

diff --git a/front/src/MusicPage.js b/front/src/MusicPage.js
index d244629..fb03fe1 100644
--- a/front/src/MusicPage.js
+++ b/front/src/MusicPage.js
@@ -10,7 +10,6 @@
 import "./App.css";

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

 import { API_BASE_URL } from "./config";

-import ForumIcon from "@mui/icons-material/Forum";

 

 const navItems = [

   { label: "电影", icon: <MovieIcon />, path: "/movie" },

@@ -20,7 +19,6 @@
   { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },

   { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },

   { label: "资料", icon: <PersonIcon />, path: "/info" },

-  { label: "论坛", icon: <ForumIcon />, path: "/forum" },

   { label: "发布", icon: <AccountCircleIcon />, path: "/publish" }, // Added Publish option

 ];

 

diff --git a/front/src/PostDetailPage.js b/front/src/PostDetailPage.js
index 85b7707..fceaef8 100644
--- a/front/src/PostDetailPage.js
+++ b/front/src/PostDetailPage.js
@@ -1,40 +1,70 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
 import { useParams } from "react-router-dom";
-
-// 示例数据
-const post = {
-    post_id: "1",
-    title: "欢迎来到G10论坛",
-    content: "这里是G10 PT站的官方论坛,欢迎大家发帖交流!",
-    author_id: "u1",
-    author_name: "Alice",
-    created_at: "2024-06-01 12:00",
-    reply_count: 3,
-    view_count: 120,
-};
-
-const replies = [
-    {
-        reply_id: "r1",
-        post_id: "1",
-        content: "支持一下!",
-        author_id: "u2",
-        author_name: "Bob",
-        created_at: "2024-06-01 13:00",
-    },
-    {
-        reply_id: "r2",
-        post_id: "1",
-        content: "论坛终于上线了!",
-        author_id: "u3",
-        author_name: "Carol",
-        created_at: "2024-06-01 14:20",
-    },
-];
+import { API_BASE_URL } from "./config";
 
 export default function PostDetailPage() {
     const { postId } = useParams();
-    // 这里只展示post和replies的静态内容
+    const [post, setPost] = useState(null);
+    const [replies, setReplies] = useState([]);
+    const [newReply, setNewReply] = useState('');
+
+    // function to load post details and its replies
+    const fetchDetail = () => {
+        fetch(`${API_BASE_URL}/api/forum-detail?postid=${postId}`)
+            .then(res => res.json())
+            .then(data => {
+                console.log("Fetched post detail:", data);
+                const p = data.post || data;
+                const formattedPost = {
+                    post_id: p.postid,
+                    title: p.posttitle,
+                    content: p.postcontent,
+                    author_id: p.postuserid,
+                    author_name: p.author?.username || '',
+                    created_at: new Date(p.posttime).toLocaleString(),
+                    reply_count: p.replytime,
+                    view_count: p.readtime,
+                };
+                const formattedReplies = (data.replies || []).map(r => ({
+                    reply_id: r.replyid,
+                    post_id: r.postid || postId,
+                    content: r.content,
+                    author_id: r.authorid,
+                    author_name: r.author?.username || '',
+                    created_at: new Date(r.createdAt).toLocaleString(),
+                }));
+                setPost(formattedPost);
+                setReplies(formattedReplies);
+            })
+            .catch(err => console.error(err));
+    };
+
+    useEffect(() => {
+        fetchDetail();
+    }, [postId]);
+
+    // post a new reply to backend
+    const handleReply = () => {
+        const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');
+        const userId = match ? match[2] : null;
+        if (!userId) {
+            alert('请先登录后再回复');
+            return;
+        }
+        fetch(`${API_BASE_URL}/api/forum-reply`, {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ postid: postId, replycontent: newReply, replyuserid: userId }),
+        })
+            .then(res => res.json())
+            .then(() => {
+                setNewReply('');
+                fetchDetail();
+            })
+            .catch(err => console.error(err));
+    };
+
+    if (!post) return <div>加载中...</div>;
     return (
         <div style={{ maxWidth: 700, margin: "40px auto" }}>
             {/* 原帖 */}
@@ -91,6 +121,8 @@
                 <input
                     type="text"
                     placeholder="写下你的回复..."
+                    value={newReply}
+                    onChange={e => setNewReply(e.target.value)}
                     style={{
                         flex: 1,
                         border: "1px solid #bfcfff",
@@ -99,7 +131,7 @@
                         fontSize: 15,
                     }}
                 />
-                <button style={{
+                <button onClick={handleReply} style={{
                     background: "#1976d2",
                     color: "#fff",
                     border: "none",
diff --git a/front/src/PublishPage.js b/front/src/PublishPage.js
index 26fa842..52eb956 100644
--- a/front/src/PublishPage.js
+++ b/front/src/PublishPage.js
@@ -39,9 +39,6 @@
 

   const handleSubmit = async (e) => {

     e.preventDefault();

-

-    console.log('Form Data Submitted:', formData);

-

     // 假设userid和tag可以从表单或用户信息中获取,这里用示例数据

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

     const tag = formData.type ? formData.type : '高清';

@@ -59,13 +56,9 @@
     // console.log(data.get('tag'));

 

     try {

-

-      

-

       // console.log(data.get('userid'));

       // console.log(data.get('title'));

       const response = await fetch(`${API_BASE_URL}/api/save-torrent`, {

-

         method: 'POST',

         body: data,

       });

@@ -77,7 +70,6 @@
     } catch (err) {

       alert('网络错误');

     }

-

   };

 

   return (

diff --git a/front/src/SportPage.js b/front/src/SportPage.js
index a221c09..7dc452d 100644
--- a/front/src/SportPage.js
+++ b/front/src/SportPage.js
@@ -1,4 +1,4 @@
-import React from "react";

+import React, { useState, useEffect } from "react";

 import MovieIcon from "@mui/icons-material/Movie";

 import EmailIcon from "@mui/icons-material/Email";

 import MusicNoteIcon from "@mui/icons-material/MusicNote";

@@ -42,9 +42,32 @@
 

 export default function SportPage() {

   const navigate = useNavigate();

+    const [searchText, setSearchText] = React.useState('');

+    const [userInfo, setUserInfo] = useState({ avatar_url: '', username: '' });

+    const [userPT, setUserPT] = useState({ magic: 0, ratio: 0, upload: 0, download: 0 });

   const [activeTab, setActiveTab] = React.useState(0);

   const [sportList, setSportList] = React.useState([]);

 

+    useEffect(() => {

+      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+      const userId = match ? match[2] : null;

+      console.log("User ID from cookie:", userId);

+      if (!userId) return;

+      fetch(`${API_BASE_URL}/api/get-userpt?userid=${encodeURIComponent(userId)}`)

+        .then(res => res.json())

+        .then(data => {

+          console.log('用户PT信息:', data); 

+          setUserInfo({ avatar_url: data.user.avatar_url, username: data.user.username });

+          setUserPT({

+            magic: data.magic_value || data.magic || 0,

+            ratio: data.share_ratio || data.share || 0,

+            upload: data.upload_amount || data.upload || 0,

+            download: data.download_amount || data.download || 0,

+          });

+        })

+        .catch(err => console.error('Fetching user profile failed', err));

+    }, []);

+

   // 每个tab对应的运动类型

   const sportTypesList = [

     ["篮球赛事", "篮球技巧", "篮球明星"], // 篮球

@@ -64,65 +87,36 @@
       .catch(() => setSportList([]));

   }, [activeTab]);

 

+    // 搜索按钮处理

+    const handleSearch = () => {

+      const area = areaTabs[activeTab].label;

+      fetch(`${API_BASE_URL}/api/search-seeds?tag=${encodeURIComponent(area)}&keyword=${encodeURIComponent(searchText)}`)

+        .then(res => res.json())

+        .then(data => {

+          console.log('搜索返回数据:', data);

+          setSportList(data);

+        })

+        .catch(() => setSportList([]));

+    };

+

   return (

     <div className="container">

       {/* 顶部空白与音乐界面一致,用户栏绝对定位在页面右上角 */}

       <div style={{ height: 80 }} />

-      <div

-        className="user-bar"

-        style={{

-          position: "fixed",

-          top: 18,

-          right: 42,

-          zIndex: 100,

-          display: "flex",

-          alignItems: "center",

-          background: "#e0f3ff",

-          borderRadius: 12,

-          padding: "6px 18px",

-          boxShadow: "0 2px 8px #b2d8ea",

-          minWidth: 320,

-          minHeight: 48,

-          width: 420,

-        }}

-      >

-        <div

-          style={{ cursor: "pointer", marginRight: 16 }}

-          onClick={() => navigate("/user")}

-        >

-          <AccountCircleIcon

-            style={{

-              fontSize: 38,

-              color: "#1a237e",

-              background: "#e0f3ff",

-              borderRadius: "50%",

-            }}

-          />

+      <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>

+        <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>

+          {userInfo.avatar_url ? (

+            <img src={userInfo.avatar_url} alt="用户头像" style={{ width: 38, height: 38, borderRadius: '50%', objectFit: 'cover' }} />

+          ) : (

+            <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />

+          )}

         </div>

-        <div style={{ color: "#222", fontWeight: 500, marginRight: 24 }}>

-          用户栏

-        </div>

-        <div

-          style={{

-            display: "flex",

-            gap: 28,

-            flex: 1,

-            justifyContent: "flex-end",

-            alignItems: "center",

-          }}

-        >

-          <span style={{ color: "#1976d2", fontWeight: 500 }}>

-            魔力值: <b>12345</b>

-          </span>

-          <span style={{ color: "#1976d2", fontWeight: 500 }}>

-            分享率: <b>2.56</b>

-          </span>

-          <span style={{ color: "#1976d2", fontWeight: 500 }}>

-            上传量: <b>100GB</b>

-          </span>

-          <span style={{ color: "#1976d2", fontWeight: 500 }}>

-            下载量: <b>50GB</b>

-          </span>

+        <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>{userInfo.username || '用户栏'}</div>

+        <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>

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

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>{userPT.ratio}</b></span>

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>{userPT.upload}</b></span>

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>{userPT.download}</b></span>

         </div>

       </div>

       {/* 下方内容整体下移,留出与音乐界面一致的间距 */}

@@ -140,11 +134,14 @@
         ))}

       </nav>

       <div className="search-section card">

-        <input className="search-input" placeholder="输入搜索关键词" />

-        <button className="search-btn">

-          <span role="img" aria-label="search">

-            🔍

-          </span>

+        <input

+          className="search-input"

+          placeholder="输入搜索关键词"

+          value={searchText}

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

+        />

+        <button className="search-btn" onClick={handleSearch}>

+          <span role="img" aria-label="search">🔍</span>

         </button>

       </div>

       <div

diff --git a/front/src/TVPage.js b/front/src/TVPage.js
index 1bc9a07..7e33a87 100644
--- a/front/src/TVPage.js
+++ b/front/src/TVPage.js
@@ -1,4 +1,4 @@
-import React from "react";

+import React, { useState, useEffect } from "react";

 import MovieIcon from "@mui/icons-material/Movie";

 import EmailIcon from "@mui/icons-material/Email";

 import MusicNoteIcon from "@mui/icons-material/MusicNote";

@@ -10,7 +10,6 @@
 import { useNavigate } from "react-router-dom";

 import "./App.css";

 import { API_BASE_URL } from "./config";

-import ForumIcon from "@mui/icons-material/Forum";

 

 const navItems = [

   { label: "电影", icon: <MovieIcon />, path: "/movie" },

@@ -20,7 +19,6 @@
   { label: "游戏", icon: <SportsEsportsIcon />, path: "/game" },

   { label: "体育", icon: <SportsMartialArtsIcon />, path: "/sport" },

   { label: "资料", icon: <PersonIcon />, path: "/info" },

-  { label: "论坛", icon: <ForumIcon />, path: "/forum" },

   { label: "发布", icon: <AccountCircleIcon />, path: "/publish" }, // Added Publish option

 ];

 

@@ -42,9 +40,32 @@
 

 export default function TVPage() {

   const navigate = useNavigate();

+  const [searchText, setSearchText] = React.useState('');

+    const [userInfo, setUserInfo] = useState({ avatar_url: '', username: '' });

+    const [userPT, setUserPT] = useState({ magic: 0, ratio: 0, upload: 0, download: 0 });

   const [activeTab, setActiveTab] = React.useState(0);

   const [tvList, setTvList] = React.useState([]);

 

+    useEffect(() => {

+      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+      const userId = match ? match[2] : null;

+      console.log("User ID from cookie:", userId);

+      if (!userId) return;

+      fetch(`${API_BASE_URL}/api/get-userpt?userid=${encodeURIComponent(userId)}`)

+        .then(res => res.json())

+        .then(data => {

+          console.log('用户PT信息:', data); 

+          setUserInfo({ avatar_url: data.user.avatar_url, username: data.user.username });

+          setUserPT({

+            magic: data.magic_value || data.magic || 0,

+            ratio: data.share_ratio || data.share || 0,

+            upload: data.upload_amount || data.upload || 0,

+            download: data.download_amount || data.download || 0,

+          });

+        })

+        .catch(err => console.error('Fetching user profile failed', err));

+    }, []);

+

   React.useEffect(() => {

     // 假设后端接口为 /api/tvs?area=大陆

     const area = areaTabs[activeTab].label;

@@ -54,6 +75,18 @@
       .catch(() => setTvList([]));

   }, [activeTab]);

 

+    // 搜索按钮处理

+    const handleSearch = () => {

+      const area = areaTabs[activeTab].label;

+      fetch(`${API_BASE_URL}/api/search-seeds?tag=${encodeURIComponent(area)}&keyword=${encodeURIComponent(searchText)}`)

+        .then(res => res.json())

+        .then(data => {

+          console.log('搜索返回数据:', data);

+          setTvList(data);

+        })

+        .catch(() => setTvList([]));

+    };

+

   // 每个tab对应的剧集类型

   const tvTypes = tvTypesList[activeTab] || [];

 

@@ -63,14 +96,18 @@
       <div style={{ height: 80 }} />

       <div className="user-bar" style={{ position: 'fixed', top: 18, right: 42, zIndex: 100, display: 'flex', alignItems: 'center', background: '#e0f3ff', borderRadius: 12, padding: '6px 18px', boxShadow: '0 2px 8px #b2d8ea', minWidth: 320, minHeight: 48, width: 420 }}>

         <div style={{ cursor: 'pointer', marginRight: 16 }} onClick={() => navigate('/user')}>

-          <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />

+          {userInfo.avatar_url ? (

+            <img src={userInfo.avatar_url} alt="用户头像" style={{ width: 38, height: 38, borderRadius: '50%', objectFit: 'cover' }} />

+          ) : (

+            <AccountCircleIcon style={{ fontSize: 38, color: '#1a237e', background: '#e0f3ff', borderRadius: '50%' }} />

+          )}

         </div>

-        <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>用户栏</div>

+        <div style={{ color: '#222', fontWeight: 500, marginRight: 24 }}>{userInfo.username || '用户栏'}</div>

         <div style={{ display: 'flex', gap: 28, flex: 1, justifyContent: 'flex-end', alignItems: 'center' }}>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>魔力值: <b>12345</b></span>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>2.56</b></span>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>100GB</b></span>

-          <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>50GB</b></span>

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

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>分享率: <b>{userPT.ratio}</b></span>

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>上传量: <b>{userPT.upload}</b></span>

+          <span style={{ color: '#1976d2', fontWeight: 500 }}>下载量: <b>{userPT.download}</b></span>

         </div>

       </div>

       {/* 下方内容整体下移,留出与音乐界面一致的间距 */}

@@ -88,8 +125,13 @@
         ))}

       </nav>

       <div className="search-section card">

-        <input className="search-input" placeholder="输入搜索关键词" />

-        <button className="search-btn">

+        <input

+          className="search-input"

+          placeholder="输入搜索关键词"

+          value={searchText}

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

+        />

+        <button className="search-btn" onClick={handleSearch}>

           <span role="img" aria-label="search">🔍</span>

         </button>

       </div>

diff --git a/front/src/UserProfile.js b/front/src/UserProfile.js
index cf38e7f..f6ef9f4 100644
--- a/front/src/UserProfile.js
+++ b/front/src/UserProfile.js
@@ -17,7 +17,7 @@
     avatar_url: "",

     username: "示例用户",

     email: "user@example.com",

-    invite_left: "",

+    invitetimes: "",

     school: "",

     account_status: "",

     gender: "",

@@ -61,12 +61,17 @@
   // 获取用户信息

   useEffect(() => {

     const fetchUserInfo = async () => {

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

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

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

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

+      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

       if (!userid) return;

       try {

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

         if (res.ok) {

           const data = await res.json();

+          console.log("获取用户信息:", data);

           setUserInfo(data);

           setTempUserInfo(data);

         }

@@ -80,7 +85,10 @@
   // 获取上传种子

   useEffect(() => {

     const fetchUserSeeds = async () => {

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

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

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

+      const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

       if (!userid) return;

       try {

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

@@ -125,13 +133,17 @@
   };

 

   const handleSave = async () => {

-    if (tempUserInfo.gender === "男性") {

+    if (tempUserInfo.gender === "男"){

       tempUserInfo.gender = "m";

-    } else if (tempUserInfo.gender === "女性") {

+    }else if (tempUserInfo.gender === "女"){

       tempUserInfo.gender = "f";

     }

     setUserInfo({ ...tempUserInfo });

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

+    // 获取userid

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

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

+    const match = document.cookie.match('(^|;)\\s*userId=([^;]+)');

+    const userid = match ? match[2] : null;

     try {

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

         method: 'POST',

@@ -152,9 +164,9 @@
   };

 

   const handleAvatarClick = () => {

-    const pictureUrl = prompt("请输入头像的URL:");

-    if (pictureUrl) {

-      setTempUserInfo({ ...tempUserInfo, avatar_url: pictureUrl });

+    const avatarUrl = prompt("请输入头像的URL:");

+    if (avatarUrl) {

+      setTempUserInfo({ ...tempUserInfo, avatar_url: avatarUrl });

     }

   };

 

@@ -484,7 +496,7 @@
                   }}

                   onClick={e => {

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

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

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

                   }}

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

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

@@ -498,9 +510,24 @@
                     color="error"

                     size="small"

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

-                    onClick={e => {

+                    onClick={async e => {

                       e.stopPropagation();

-                      handleDeleteSeed(seed.seedid);

+                      // 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({ seed_id: seed.seed_id, userid }),

+                        });

+                        if (res.ok) {

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

+                        } else {

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

+                        }

+                      } catch (err) {

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

+                      }

                     }}

                   >删除</Button>

                 </li>

diff --git a/front/src/config.js b/front/src/config.js
index d651f0d..e28dd5f 100644
--- a/front/src/config.js
+++ b/front/src/config.js
@@ -1,2 +1 @@
-// src/config.js
-export const API_BASE_URL = "http://10.126.59.25:8081";
+export const API_BASE_URL = "http://10.126.59.25:8081";
\ No newline at end of file
diff --git a/front/src/index.js b/front/src/index.js
index d563c0f..c6392bb 100644
--- a/front/src/index.js
+++ b/front/src/index.js
@@ -4,7 +4,10 @@
 import App from './App';
 import reportWebVitals from './reportWebVitals';
 
-const root = ReactDOM.createRoot(document.getElementById('root'));
+const container = document.getElementById('root');
+if (!container) throw new Error('Failed to find root container element');
+const root = ReactDOM.createRoot(container);
+
 root.render(
   <React.StrictMode>
     <App />