| 1326 | } |
| 1327 | |
| 1328 | function AdminView() { |
| 1329 | const [summary, setSummary] = useState<any>(null); |
| 1330 | const [message, setMessage] = useState(""); |
| 1331 | |
| 1332 | useEffect(() => { |
| 1333 | api.adminSummary().then(setSummary).catch((err) => setMessage(String(err))); |
| 1334 | }, []); |
| 1335 | |
| 1336 | if (!summary) return <section className="panel heroPanel">正在加载后台...</section>; |
| 1337 | |
| 1338 | return ( |
| 1339 | <section className="adminLayout"> |
| 1340 | <div className="panel adminHero"> |
| 1341 | <span className="eyebrow">运营后台</span> |
| 1342 | <h1>基础数据</h1> |
| 1343 | {message ? <div className="inlineError">{message}</div> : null} |
| 1344 | <div className="adminStats"> |
| 1345 | {Object.entries(summary.totals).map(([key, value]) => ( |
| 1346 | <div key={key} className="statBlock"> |
| 1347 | <strong>{String(value)}</strong> |
| 1348 | <span>{key}</span> |
| 1349 | </div> |
| 1350 | ))} |
| 1351 | </div> |
| 1352 | </div> |
| 1353 | <div className="panel"> |
| 1354 | <h2> |
| 1355 | <BarChart3 size={18} /> |
| 1356 | 挑战列表 |
| 1357 | </h2> |
| 1358 | {summary.challenges.map((challenge: Challenge) => ( |
| 1359 | <div key={challenge.id} className="adminChallenge"> |
| 1360 | <strong>{challenge.name}</strong> |
| 1361 | <span> |
| 1362 | {challenge.place_name} · {pct(challenge.stats.progress)} ·{" "} |
| 1363 | {challenge.stats.participant_count} 人 |
| 1364 | </span> |
| 1365 | </div> |
| 1366 | ))} |
| 1367 | </div> |
| 1368 | <div className="panel"> |
| 1369 | <h2> |
| 1370 | <Activity size={18} /> |
| 1371 | 点亮来源 |
| 1372 | </h2> |
| 1373 | {summary.unlock_source_breakdown.length === 0 ? ( |
| 1374 | <p>暂无点亮。</p> |
| 1375 | ) : ( |
| 1376 | summary.unlock_source_breakdown.map((item: any) => ( |
| 1377 | <div key={item.source} className="adminChallenge"> |
| 1378 | <strong>{item.source === "gps" ? "GPS 真打卡" : item.source}</strong> |
| 1379 | <span>{item.count} 格</span> |
| 1380 | </div> |
| 1381 | )) |
| 1382 | )} |
| 1383 | </div> |
| 1384 | <div className="panel"> |
| 1385 | <h2> |