新增管理员页面和用户申诉、迁移审核页面,推荐系统

Change-Id: Ief5646321feb98fadb17da4b4e91caeaacdbacc5
diff --git a/front/src/AppealPage.js b/front/src/AppealPage.js
new file mode 100644
index 0000000..a0314c4
--- /dev/null
+++ b/front/src/AppealPage.js
@@ -0,0 +1,141 @@
+import React, { useState } from "react";
+
+// 示例申诉数据
+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>;
+}
+
+export default function AppealPage() {
+    const [selectedId, setSelectedId] = useState(appeals[0].appeal_id);
+    const selectedAppeal = appeals.find(a => a.appeal_id === selectedId);
+
+    const handleApprove = () => {
+        alert("已通过申诉(示例,无实际状态变更)");
+    };
+    const handleReject = () => {
+        alert("已拒绝申诉(示例,无实际状态变更)");
+    };
+
+    return (
+        <div style={{ display: "flex", minHeight: "100vh", background: "#f7faff" }}>
+            {/* 侧栏 */}
+            <div style={{ width: 180, background: "#fff", borderRight: "1px solid #e0e7ff", padding: 0 }}>
+                <h3 style={{ textAlign: "center", padding: "18px 0 0 0", color: "#1976d2" }}>申诉列表</h3>
+                <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
+                    {appeals.map(a => (
+                        <div
+                            key={a.appeal_id}
+                            onClick={() => setSelectedId(a.appeal_id)}
+                            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",
+                                fontWeight: 600,
+                                cursor: "pointer",
+                                boxShadow: selectedId === a.appeal_id ? "0 2px 8px #b2d8ea" : "none",
+                                transition: "all 0.2s"
+                            }}
+                        >
+                            {a.appeal_id}
+                            <span style={{
+                                float: "right",
+                                fontSize: 12,
+                                color: a.status === 1 ? "#43a047" : "#e53935"
+                            }}>
+                                {a.status === 1 ? "已审核" : "未审核"}
+                            </span>
+                        </div>
+                    ))}
+                </div>
+            </div>
+            {/* 申诉详情 */}
+            <div style={{ flex: 1, padding: "40px 48px" }}>
+                <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}
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>用户ID:</b>{selectedAppeal.user_id}
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>申诉内容:</b>{selectedAppeal.content}
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>申诉文件:</b>
+                        <FileViewer url={selectedAppeal.file_url} />
+                    </div>
+                </div>
+                {/* 审核按钮 */}
+                <div style={{ display: "flex", gap: 32, justifyContent: "center" }}>
+                    <button
+                        style={{
+                            background: selectedAppeal.status === 1 ? "#bdbdbd" : "#43a047",
+                            color: "#fff",
+                            border: "none",
+                            borderRadius: 8,
+                            padding: "10px 38px",
+                            fontWeight: 600,
+                            fontSize: 18,
+                            cursor: selectedAppeal.status === 1 ? "not-allowed" : "pointer"
+                        }}
+                        disabled={selectedAppeal.status === 1}
+                        onClick={handleApprove}
+                    >
+                        通过
+                    </button>
+                    <button
+                        style={{
+                            background: selectedAppeal.status === 1 ? "#bdbdbd" : "#e53935",
+                            color: "#fff",
+                            border: "none",
+                            borderRadius: 8,
+                            padding: "10px 38px",
+                            fontWeight: 600,
+                            fontSize: 18,
+                            cursor: selectedAppeal.status === 1 ? "not-allowed" : "pointer"
+                        }}
+                        disabled={selectedAppeal.status === 1}
+                        onClick={handleReject}
+                    >
+                        不通过
+                    </button>
+                </div>
+            </div>
+        </div>
+    );
+}
\ No newline at end of file