blob: 0f168c3859022ff50fdd30a05919b75fb6e6fc7c [file] [log] [blame]
223011339e292152025-06-08 00:34:37 +08001import React, { useState, useEffect } from "react";
2import { API_BASE_URL } from "./config";
wht30587822025-06-09 23:33:09 +08003import "./AppealPage.css";
whtb1e79592025-06-07 16:03:09 +08004
223011339e292152025-06-08 00:34:37 +08005// State for appeals fetched from backend
whtb1e79592025-06-07 16:03:09 +08006export default function AppealPage() {
223011339e292152025-06-08 00:34:37 +08007 const [appeals, setAppeals] = useState([]);
8 const [selectedId, setSelectedId] = useState(null);
9 const [loading, setLoading] = useState(true);
10 const [error, setError] = useState(null);
whtb1e79592025-06-07 16:03:09 +080011
223011339e292152025-06-08 00:34:37 +080012 // Helper to load appeals
13 const fetchAppeals = async () => {
14 setLoading(true);
15 try {
16 const res = await fetch(`${API_BASE_URL}/api/appeals`);
17 if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
18 const data = await res.json();
19 // console.log("Fetched appeals:", data);
20 setAppeals(data);
21 if (data.length > 0) setSelectedId(data[0].appealid);
22 setError(null);
23 } catch (err) {
24 setError(err.message);
25 } finally {
26 setLoading(false);
27 }
whtb1e79592025-06-07 16:03:09 +080028 };
223011339e292152025-06-08 00:34:37 +080029
30 useEffect(() => {
31 fetchAppeals();
32 }, []);
33
wht30587822025-06-09 23:33:09 +080034 if (loading) return <div className="appeal-loading">加载中...</div>;
35 if (error) return <div className="appeal-error">加载失败:{error}</div>;
223011339e292152025-06-08 00:34:37 +080036 const selectedAppeal = appeals.find(a => a.appealid === selectedId) || {};
37
38 // Approve selected appeal and refresh
39 const handleApprove = async () => {
40 try {
41 const res = await fetch(`${API_BASE_URL}/api/appeals-approve`, {
42 method: "POST",
43 headers: { "Content-Type": "application/json" },
44 body: JSON.stringify({ appealid: selectedAppeal.appealid })
45 });
46 if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
47 alert("已通过申诉");
48 await fetchAppeals();
49 } catch (err) {
50 alert(`操作失败:${err.message}`);
51 }
52 };
53 // Reject selected appeal and refresh
54 const handleReject = async () => {
55 try {
56 const res = await fetch(`${API_BASE_URL}/api/appeals-reject`, {
57 method: "POST",
58 headers: { "Content-Type": "application/json" },
59 body: JSON.stringify({ appealid: selectedAppeal.appealid })
60 });
61 if (!res.ok) throw new Error(`请求失败,状态码 ${res.status}`);
62 alert("已拒绝申诉");
63 await fetchAppeals();
64 } catch (err) {
65 alert(`操作失败:${err.message}`);
66 }
whtb1e79592025-06-07 16:03:09 +080067 };
68
69 return (
wht30587822025-06-09 23:33:09 +080070 <div className="appeal-page-container">
whtb1e79592025-06-07 16:03:09 +080071 {/* 侧栏 */}
wht30587822025-06-09 23:33:09 +080072 <div className="appeal-sidebar">
73 <h3 className="appeal-sidebar-title">申诉列表</h3>
74 <div className="appeal-list-container">
whtb1e79592025-06-07 16:03:09 +080075 {appeals.map(a => (
76 <div
223011339e292152025-06-08 00:34:37 +080077 key={a.appealid}
78 onClick={() => setSelectedId(a.appealid)}
wht30587822025-06-09 23:33:09 +080079 className={`appeal-list-item ${
80 selectedId === a.appealid ? 'selected' : ''
81 } ${
82 a.status === 1 || a.status === 2 ? 'approved' : 'pending'
83 }`}
whtb1e79592025-06-07 16:03:09 +080084 >
223011339e292152025-06-08 00:34:37 +080085 {a.user.username}
wht30587822025-06-09 23:33:09 +080086 <span className={`appeal-status-label ${
87 a.status === 1 || a.status === 2 ? 'approved' : 'pending'
88 }`}>
223011339e292152025-06-08 00:34:37 +080089 {a.status === 1 || a.status === 2 ? "已审核" : "未审核"}
whtb1e79592025-06-07 16:03:09 +080090 </span>
91 </div>
92 ))}
93 </div>
94 </div>
95 {/* 申诉详情 */}
wht30587822025-06-09 23:33:09 +080096 <div className="appeal-main-content">
97 <h2 className="appeal-detail-title">申诉详情</h2>
98 <div className="appeal-detail-card">
99 <div className="appeal-detail-item">
223011339e292152025-06-08 00:34:37 +0800100 <b>申诉ID:</b>{selectedAppeal.appealid}
whtb1e79592025-06-07 16:03:09 +0800101 </div>
wht30587822025-06-09 23:33:09 +0800102 <div className="appeal-detail-item">
223011339e292152025-06-08 00:34:37 +0800103 <b>用户ID:</b>{selectedAppeal.user.userid}
whtb1e79592025-06-07 16:03:09 +0800104 </div>
wht30587822025-06-09 23:33:09 +0800105 <div className="appeal-detail-item">
whtb1e79592025-06-07 16:03:09 +0800106 <b>申诉内容:</b>{selectedAppeal.content}
107 </div>
wht30587822025-06-09 23:33:09 +0800108 <div className="appeal-detail-item">
whtb1e79592025-06-07 16:03:09 +0800109 <b>申诉文件:</b>
223011339e292152025-06-08 00:34:37 +0800110 <FileViewer url={selectedAppeal.fileURL} />
whtb1e79592025-06-07 16:03:09 +0800111 </div>
112 </div>
113 {/* 审核按钮 */}
wht30587822025-06-09 23:33:09 +0800114 <div className="appeal-buttons-container">
whtb1e79592025-06-07 16:03:09 +0800115 <button
wht30587822025-06-09 23:33:09 +0800116 className={`appeal-btn appeal-btn-approve`}
whtb1e79592025-06-07 16:03:09 +0800117 disabled={selectedAppeal.status === 1}
118 onClick={handleApprove}
119 >
120 通过
121 </button>
122 <button
wht30587822025-06-09 23:33:09 +0800123 className={`appeal-btn appeal-btn-reject`}
whtb1e79592025-06-07 16:03:09 +0800124 disabled={selectedAppeal.status === 1}
125 onClick={handleReject}
126 >
127 不通过
128 </button>
129 </div>
130 </div>
131 </div>
132 );
223011339e292152025-06-08 00:34:37 +0800133}
134
135// 简单PDF预览组件
136function FileViewer({ url }) {
137 if (!url) return <div>无附件</div>;
138 if (url.endsWith(".pdf")) {
139 return (
wht30587822025-06-09 23:33:09 +0800140 <div className="file-viewer-container">
141 <iframe
142 src={url}
143 title="PDF预览"
144 className="file-viewer-iframe"
145 />
146 </div>
223011339e292152025-06-08 00:34:37 +0800147 );
148 }
149 // 这里只做PDF示例,实际可扩展为DOC等
wht30587822025-06-09 23:33:09 +0800150 return <a href={url} target="_blank" rel="noopener noreferrer" className="file-download-link">下载附件</a>;
whtb1e79592025-06-07 16:03:09 +0800151}