blob: b8235e43077927be0735a6ffe970ac44895ac2a3 [file] [log] [blame]
223011339e292152025-06-08 00:34:37 +08001import React, { useState, useEffect } from "react";
2import { API_BASE_URL } from "./config";
whtb1e79592025-06-07 16:03:09 +08003
4// 简单PDF预览组件
5function FileViewer({ url }) {
6 if (!url) return <div>无附件</div>;
7 if (url.endsWith(".pdf")) {
8 return (
9 <iframe
10 src={url}
11 title="PDF预览"
12 width="100%"
13 height="400px"
14 style={{ border: "1px solid #ccc", borderRadius: 8 }}
15 />
16 );
17 }
18 // 这里只做PDF示例,实际可扩展为DOC等
19 return <a href={url} target="_blank" rel="noopener noreferrer">下载附件</a>;
20}
21
22export default function MigrationPage() {
223011339e292152025-06-08 00:34:37 +080023 const [migrations, setMigrations] = useState([]);
24 const [selectedId, setSelectedId] = useState(null);
25 const [loading, setLoading] = useState(true);
26 const [error, setError] = useState(null);
wht338fc032025-06-09 17:16:22 +080027 const [grantedUploadInput, setGrantedUploadInput] = useState(0);
whtb1e79592025-06-07 16:03:09 +080028
223011339e292152025-06-08 00:34:37 +080029 // Helper to load migrations list
30 const fetchMigrations = async () => {
31 setLoading(true);
32 try {
33 const res = await fetch(`${API_BASE_URL}/api/migrations`);
34 if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
35 const data = await res.json();
36 const formatted = data.map(item => ({
37 migration_id: item.profileurl,
38 user_id: item.user.userid,
39 application_url: item.applicationurl,
40 pending_magic: Number(item.magictogive),
41 granted_magic: Number(item.magicgived),
42 pending_uploaded: Number(item.uploadtogive),
43 granted_uploaded: Number(item.uploadgived),
44 approved: item.exampass ? 1 : 0
45 }));
46 setMigrations(formatted);
47 if (formatted.length > 0) setSelectedId(formatted[0].migration_id);
48 setError(null);
49 } catch (err) {
50 setError(err.message);
51 } finally {
52 setLoading(false);
53 }
whtb1e79592025-06-07 16:03:09 +080054 };
223011339e292152025-06-08 00:34:37 +080055
56 useEffect(() => {
57 fetchMigrations();
58 }, []);
59
wht338fc032025-06-09 17:16:22 +080060 // 获取当前选中的迁移申请
223011339e292152025-06-08 00:34:37 +080061 const selectedMigration = migrations.find(m => m.migration_id === selectedId) || {};
62
wht338fc032025-06-09 17:16:22 +080063 // 每次切换迁移申请时,重置输入框为0或当前已迁移量
64 useEffect(() => {
65 // 审核通过后,输入框应为已迁移量,否则为0
66 if (selectedMigration.approved === 1) {
67 setGrantedUploadInput(selectedMigration.granted_uploaded || 0);
68 } else {
69 setGrantedUploadInput(0);
70 }
71 }, [selectedId, migrations, selectedMigration.approved, selectedMigration.granted_uploaded]);
72
73 // 审核通过
223011339e292152025-06-08 00:34:37 +080074 const handleApprove = async () => {
wht338fc032025-06-09 17:16:22 +080075 const max = selectedMigration.pending_uploaded || 0;
76 let value = Number(grantedUploadInput);
77 if (isNaN(value) || value < 0) value = 0;
78 if (value > max) value = max;
79
223011339e292152025-06-08 00:34:37 +080080 try {
81 const res = await fetch(`${API_BASE_URL}/api/migrations-approve`, {
82 method: "POST",
83 headers: { "Content-Type": "application/json" },
wht338fc032025-06-09 17:16:22 +080084 body: JSON.stringify({
85 migration_id: selectedMigration.migration_id,
86 granted_uploaded: value
87 })
223011339e292152025-06-08 00:34:37 +080088 });
89 if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
90 alert("已通过迁移");
91 await fetchMigrations();
92 } catch (err) {
93 alert(`操作失败:${err.message}`);
94 }
95 };
wht338fc032025-06-09 17:16:22 +080096
97 // 审核不通过
223011339e292152025-06-08 00:34:37 +080098 const handleReject = async () => {
99 try {
100 const res = await fetch(`${API_BASE_URL}/api/migrations-reject`, {
101 method: "POST",
102 headers: { "Content-Type": "application/json" },
wht338fc032025-06-09 17:16:22 +0800103 body: JSON.stringify({
104 migration_id: selectedMigration.migration_id,
105 granted_uploaded: 0
106 })
223011339e292152025-06-08 00:34:37 +0800107 });
108 if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
109 alert("已拒绝迁移");
110 await fetchMigrations();
111 } catch (err) {
112 alert(`操作失败:${err.message}`);
113 }
whtb1e79592025-06-07 16:03:09 +0800114 };
115
wht338fc032025-06-09 17:16:22 +0800116 if (loading) return <div>加载中...</div>;
117 if (error) return <div>加载失败:{error}</div>;
118
whtb1e79592025-06-07 16:03:09 +0800119 return (
120 <div style={{ display: "flex", minHeight: "100vh", background: "#f7faff" }}>
121 {/* 侧栏 */}
122 <div style={{ width: 180, background: "#fff", borderRight: "1px solid #e0e7ff", padding: 0 }}>
123 <h3 style={{ textAlign: "center", padding: "18px 0 0 0", color: "#1976d2" }}>迁移列表</h3>
124 <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
125 {migrations.map(m => (
126 <div
127 key={m.migration_id}
128 onClick={() => setSelectedId(m.migration_id)}
129 style={{
130 margin: "0 12px",
131 padding: "16px 10px",
132 borderRadius: 8,
133 background: selectedId === m.migration_id ? "#e3f2fd" : "#fff",
134 border: `2px solid ${m.approved === 1 ? "#43a047" : "#e53935"}`,
135 color: m.approved === 1 ? "#43a047" : "#e53935",
136 fontWeight: 600,
137 cursor: "pointer",
138 boxShadow: selectedId === m.migration_id ? "0 2px 8px #b2d8ea" : "none",
139 transition: "all 0.2s"
140 }}
141 >
142 {m.migration_id}
143 <span style={{
144 float: "right",
145 fontSize: 12,
146 color: m.approved === 1 ? "#43a047" : "#e53935"
147 }}>
148 {m.approved === 1 ? "已审核" : "未审核"}
149 </span>
150 </div>
151 ))}
152 </div>
153 </div>
154 {/* 迁移详情 */}
155 <div style={{ flex: 1, padding: "40px 48px" }}>
156 <h2 style={{ marginBottom: 24, color: "#1976d2" }}>迁移详情</h2>
157 <div style={{ background: "#fff", borderRadius: 12, padding: 32, boxShadow: "0 2px 8px #e0e7ff", marginBottom: 32 }}>
158 <div style={{ marginBottom: 18 }}>
159 <b>迁移ID:</b>{selectedMigration.migration_id}
160 </div>
161 <div style={{ marginBottom: 18 }}>
162 <b>用户ID:</b>{selectedMigration.user_id}
163 </div>
164 <div style={{ marginBottom: 18 }}>
165 <b>申请文件:</b>
166 <FileViewer url={selectedMigration.application_url} />
167 </div>
168 <div style={{ marginBottom: 18 }}>
whtb1e79592025-06-07 16:03:09 +0800169 <b>待迁移上传量:</b>{selectedMigration.pending_uploaded},
wht338fc032025-06-09 17:16:22 +0800170 <b>已迁移上传量:</b>
171 {selectedMigration.approved === 1 ? (
172 <span>{selectedMigration.granted_uploaded}</span>
173 ) : (
174 <input
175 type="number"
176 min={0}
177 max={selectedMigration.pending_uploaded}
178 value={grantedUploadInput}
179 onChange={e => {
180 let val = Number(e.target.value);
181 if (isNaN(val) || val < 0) val = 0;
182 if (val > selectedMigration.pending_uploaded) val = selectedMigration.pending_uploaded;
183 setGrantedUploadInput(val);
184 }}
185 style={{
186 width: 100,
187 marginLeft: 8,
188 padding: "4px 8px",
189 borderRadius: 4,
190 border: "1px solid #bdbdbd"
191 }}
192 disabled={selectedMigration.approved === 1}
193 />
194 )}
whtb1e79592025-06-07 16:03:09 +0800195 </div>
196 </div>
197 {/* 审核按钮 */}
198 <div style={{ display: "flex", gap: 32, justifyContent: "center" }}>
199 <button
200 style={{
201 background: selectedMigration.approved === 1 ? "#bdbdbd" : "#43a047",
202 color: "#fff",
203 border: "none",
204 borderRadius: 8,
205 padding: "10px 38px",
206 fontWeight: 600,
207 fontSize: 18,
208 cursor: selectedMigration.approved === 1 ? "not-allowed" : "pointer"
209 }}
210 disabled={selectedMigration.approved === 1}
211 onClick={handleApprove}
212 >
213 通过
214 </button>
215 <button
216 style={{
217 background: selectedMigration.approved === 1 ? "#bdbdbd" : "#e53935",
218 color: "#fff",
219 border: "none",
220 borderRadius: 8,
221 padding: "10px 38px",
222 fontWeight: 600,
223 fontSize: 18,
224 cursor: selectedMigration.approved === 1 ? "not-allowed" : "pointer"
225 }}
226 disabled={selectedMigration.approved === 1}
227 onClick={handleReject}
228 >
229 不通过
230 </button>
231 </div>
232 </div>
233 </div>
234 );
235}