新增后台接口

Change-Id: Ibd8183079a77aad05b2c81658c2d6fe9b648e042
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