新增后台接口
Change-Id: Ibd8183079a77aad05b2c81658c2d6fe9b648e042
diff --git a/front/package.json b/front/package.json
index c25589f..cb291a8 100644
--- a/front/package.json
+++ b/front/package.json
@@ -18,6 +18,8 @@
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
+
+
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
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 />