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

Change-Id: Ief5646321feb98fadb17da4b4e91caeaacdbacc5
diff --git a/front/src/MigrationPage.js b/front/src/MigrationPage.js
new file mode 100644
index 0000000..ed88a55
--- /dev/null
+++ b/front/src/MigrationPage.js
@@ -0,0 +1,152 @@
+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,
+    },
+];
+
+// 简单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 MigrationPage() {
+    const [selectedId, setSelectedId] = useState(migrations[0].migration_id);
+    const selectedMigration = migrations.find(m => m.migration_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 }}>
+                    {migrations.map(m => (
+                        <div
+                            key={m.migration_id}
+                            onClick={() => setSelectedId(m.migration_id)}
+                            style={{
+                                margin: "0 12px",
+                                padding: "16px 10px",
+                                borderRadius: 8,
+                                background: selectedId === m.migration_id ? "#e3f2fd" : "#fff",
+                                border: `2px solid ${m.approved === 1 ? "#43a047" : "#e53935"}`,
+                                color: m.approved === 1 ? "#43a047" : "#e53935",
+                                fontWeight: 600,
+                                cursor: "pointer",
+                                boxShadow: selectedId === m.migration_id ? "0 2px 8px #b2d8ea" : "none",
+                                transition: "all 0.2s"
+                            }}
+                        >
+                            {m.migration_id}
+                            <span style={{
+                                float: "right",
+                                fontSize: 12,
+                                color: m.approved === 1 ? "#43a047" : "#e53935"
+                            }}>
+                                {m.approved === 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>{selectedMigration.migration_id}
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>用户ID:</b>{selectedMigration.user_id}
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>申请文件:</b>
+                        <FileViewer url={selectedMigration.application_url} />
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>待迁移魔法值:</b>{selectedMigration.pending_magic},
+                        <b>已迁移魔法值:</b>{selectedMigration.granted_magic}
+                    </div>
+                    <div style={{ marginBottom: 18 }}>
+                        <b>待迁移上传量:</b>{selectedMigration.pending_uploaded},
+                        <b>已迁移上传量:</b>{selectedMigration.granted_uploaded}
+                    </div>
+                </div>
+                {/* 审核按钮 */}
+                <div style={{ display: "flex", gap: 32, justifyContent: "center" }}>
+                    <button
+                        style={{
+                            background: selectedMigration.approved === 1 ? "#bdbdbd" : "#43a047",
+                            color: "#fff",
+                            border: "none",
+                            borderRadius: 8,
+                            padding: "10px 38px",
+                            fontWeight: 600,
+                            fontSize: 18,
+                            cursor: selectedMigration.approved === 1 ? "not-allowed" : "pointer"
+                        }}
+                        disabled={selectedMigration.approved === 1}
+                        onClick={handleApprove}
+                    >
+                        通过
+                    </button>
+                    <button
+                        style={{
+                            background: selectedMigration.approved === 1 ? "#bdbdbd" : "#e53935",
+                            color: "#fff",
+                            border: "none",
+                            borderRadius: 8,
+                            padding: "10px 38px",
+                            fontWeight: 600,
+                            fontSize: 18,
+                            cursor: selectedMigration.approved === 1 ? "not-allowed" : "pointer"
+                        }}
+                        disabled={selectedMigration.approved === 1}
+                        onClick={handleReject}
+                    >
+                        不通过
+                    </button>
+                </div>
+            </div>
+        </div>
+    );
+}
\ No newline at end of file