blob: 9cf89861dbdeb09680eafa7c6ff0b4c32779460a [file] [log] [blame]
22301080a93bebb2025-05-27 19:48:11 +08001import React, { useState, useEffect } from 'react';
2import { useNavigate } from 'react-router-dom';
3import './Administer.css';
4import {
5 getAllUsers,
6 searchUsers,
7 updateUserAuthority,
8 getAllDiscounts,
9 getCurrentDiscount,
10 addDiscount,
11 deleteDiscount
12} from '../api/administer';
DREWae420b22025-06-02 14:07:20 +080013import {postAnnouncement, getAnnouncements} from '../api/announcement';
22301080a93bebb2025-05-27 19:48:11 +080014import DatePicker from 'react-datepicker';
15import 'react-datepicker/dist/react-datepicker.css';
16
17
18const Administer = () => {
19 const navigate = useNavigate();
20 const [users, setUsers] = useState([]);
21 const [discounts, setDiscounts] = useState([]);
22 const [currentDiscount, setCurrentDiscount] = useState(null);
23 const [searchKey, setSearchKey] = useState('');
24 const [loading, setLoading] = useState(false);
25 const [error, setError] = useState(null);
DREWae420b22025-06-02 14:07:20 +080026 const [announcements, setAnnouncements] = useState([]); // 存储公告列表
27 const [newAnnouncement, setNewAnnouncement] = useState({
28 title: '',
29 content: ''
30 });
22301080a93bebb2025-05-27 19:48:11 +080031 const [newDiscount, setNewDiscount] = useState({
32 name: '',
33 discountType: 'FREE'
34 });
35 const [startDate, setStartDate] = useState(new Date());
36 const [endDate, setEndDate] = useState(new Date());
DREWae420b22025-06-02 14:07:20 +080037 const [activeTab, setActiveTab] = useState('users'); // 'users' 或 'discounts','announcements'
22301080a93bebb2025-05-27 19:48:11 +080038
39const fetchAllUsers = async () => {
40 setLoading(true);
41 setError(null);
42 try {
43 const users = await getAllUsers();
44 console.log("API Data:", users); // 现在应该直接是用户数组
45
46 const formattedUsers = users.map(user => ({
47 username: user.username || '未知用户',
48 authority: user.authority || 'USER',
49 registTime: user.registTime || null,
50 lastLogin: user.lastLogin || null,
51 upload: Number(user.upload) || 0,
52 download: Number(user.download) || 0,
53 magicPoints: Number(user.magicPoints) || 0,
54 shareRate: Number(user.shareRate) || 0
55 }));
56
57 console.log("Formatted Users:", formattedUsers);
58 setUsers(formattedUsers);
59 } catch (err) {
60 console.error("Error details:", err);
61 setError(`获取用户列表失败: ${err.message}`);
62 } finally {
63 setLoading(false);
64 }
65};
66
67
68 const handleSearch = async () => {
69 if (!searchKey.trim()) {
70 fetchAllUsers();
71 return;
72 }
73
74 setLoading(true);
75 setError(null);
76 try {
77 const users = await searchUsers(searchKey);
78 console.log("Search Results:", users); // 打印搜索结果
79
80 // 格式化数据(确保数值字段正确解析)
81 const formattedUsers = users.map(user => ({
82 username: user.username || '未知用户',
83 authority: user.authority || 'USER',
84 registTime: user.registTime || null,
85 lastLogin: user.lastLogin || null,
86 upload: Number(user.upload) || 0, // 确保解析为数字
87 download: Number(user.download) || 0,
88 magicPoints: Number(user.magicPoints) || 0,
89 shareRate: Number(user.shareRate) || 0
90 }));
91
92 setUsers(formattedUsers);
93 } catch (err) {
94 setError('搜索用户失败,请重试');
95 console.error(err);
96 } finally {
97 setLoading(false);
98 }
99};
100
101 // 重置搜索
102 const handleReset = () => {
103 setSearchKey('');
104 fetchAllUsers();
105 };
106
107 // 修改用户权限
108 const handleChangeAuthority = async (username, newAuthority) => {
109 try {
110 await updateUserAuthority(username, newAuthority);
111 // 更新本地状态
112 setUsers(users.map(user =>
113 user.username === username ? { ...user, authority: newAuthority } : user
114 ));
115 } catch (err) {
116 setError('修改权限失败,请重试');
117 console.error(err);
118 }
119 };
120
121 // 获取所有折扣
122 const fetchAllDiscounts = async () => {
123 setLoading(true);
124 setError(null);
125 try {
126 const data = await getAllDiscounts();
127 setDiscounts(data);
128 } catch (err) {
129 setError('获取折扣列表失败: ' + err.message);
130 console.error(err);
131 } finally {
132 setLoading(false);
133 }
134 };
135
136 // 获取当前折扣
137 const fetchCurrentDiscount = async () => {
138 try {
139 const data = await getCurrentDiscount();
140 setCurrentDiscount(data);
141 } catch (err) {
142 console.error('获取当前折扣失败:', err);
143 }
144 };
145
146 const handleAddDiscount = async () => {
147 if (!newDiscount.name || !startDate || !endDate) {
148 setError('请填写所有必填字段');
149 return;
150 }
151
152 try {
153 // 验证时间
154 if (startDate >= endDate) {
155 setError('结束时间必须晚于开始时间');
156 return;
157 }
158
159 const payload = {
160 name: newDiscount.name,
161 startTime: formatDateToISO(startDate), // 例如: "2025-06-01T14:30:00"
162 endTime: formatDateToISO(endDate, true), // 例如: "2025-06-01T18:45:59"
163 discountType: newDiscount.discountType
164 };
165
166 console.log('提交数据:', payload); // 调试用
167
168 await addDiscount(payload);
169
170 // 重置表单
171 setNewDiscount({
172 name: '',
173 discountType: 'FREE'
174 });
175 setStartDate(new Date());
176 setEndDate(new Date());
177
178 fetchAllDiscounts();
179 setError(null);
180 } catch (err) {
181 setError('添加折扣失败: ' + err.message);
182 console.error(err);
183 }
184};
185
186const formatDateToISO = (date, isEndTime = false) => {
187 if (!date) return '';
188
189 const pad = (num) => num.toString().padStart(2, '0');
190
191 const year = date.getFullYear();
192 const month = pad(date.getMonth() + 1);
193 const day = pad(date.getDate());
194 const hours = pad(date.getHours());
195 const minutes = pad(date.getMinutes());
196
197 if (isEndTime) {
198 // 结束时间精确到用户选择的时间+59秒
199 return `${year}-${month}-${day}T${hours}:${minutes}:59`;
200 } else {
201 // 开始时间精确到用户选择的时间+00秒
202 return `${year}-${month}-${day}T${hours}:${minutes}:00`;
203 }
204};
205
206 // 删除折扣
207 const handleDeleteDiscount = async (id) => {
208 try {
209 await deleteDiscount(id);
210 fetchAllDiscounts();
211 } catch (err) {
212 setError('删除折扣失败: ' + err.message);
213 console.error(err);
214 }
215 };
216
DREWae420b22025-06-02 14:07:20 +0800217
22301080a93bebb2025-05-27 19:48:11 +0800218
219 // 格式化分享率为百分比
220 const formatShareRate = (rate) => {
221 return (rate * 100).toFixed(2) + '%';
222 };
223
224 // 格式化日期
225 const formatDate = (date) => {
226 if (!date) return '-';
227 return new Date(date).toLocaleDateString();
228 };
229 // 格式化日期
230 const formatDateTime = (dateTime) => {
231 if (!dateTime) return '-';
232 return new Date(dateTime).toLocaleString();
233 };
234
235 // 折扣类型翻译
236 const translateDiscountType = (type) => {
237 switch (type) {
238 case 'FREE': return '全部免费';
239 case 'HALF': return '半价下载';
240 case 'DOUBLE': return '双倍上传';
241 default: return type;
242 }
243 };
244
DREWae420b22025-06-02 14:07:20 +0800245 // 获取所有公告
246 const fetchAllAnnouncements = async () => {
247 setLoading(true);
248 setError(null);
249 try {
250 const announcements = await getAnnouncements();
DREW5b1883e2025-06-07 10:41:32 +0800251 setAnnouncements(announcements);
DREWae420b22025-06-02 14:07:20 +0800252 } catch (err) {
253 setError('获取公告列表失败: ' + err.message);
254 console.error(err);
255 } finally {
256 setLoading(false);
257 }
258};
259
260 // 发布新公告
261 const handlePostAnnouncement = async () => {
262 if (!newAnnouncement.title || !newAnnouncement.content) {
263 setError('请填写公告标题和内容');
264 return;
265 }
266
267 try {
268 await postAnnouncement(newAnnouncement);
269 setNewAnnouncement({ title: '', content: '' });
270 fetchAllAnnouncements();
271 setError(null);
272 } catch (err) {
273 setError('发布公告失败: ' + err.message);
274 console.error(err);
275 }
276 };
277
278 // 初始化加载数据
279 useEffect(() => {
280 if (activeTab === 'users') {
281 fetchAllUsers();
282 } else if (activeTab === 'discounts') {
283 fetchAllDiscounts();
284 fetchCurrentDiscount();
285 } else if (activeTab === 'announcements') {
286 fetchAllAnnouncements();
287 }
288 }, [activeTab]);
289
290
291
22301080a93bebb2025-05-27 19:48:11 +0800292
293 return (
294 <div className="administer-container">
295 <h1>系统管理</h1>
296
297 {/* 选项卡切换 */}
298 <div className="tab-container">
299 <button
300 className={`tab-button ${activeTab === 'users' ? 'active' : ''}`}
301 onClick={() => setActiveTab('users')}
302 >
303 用户管理
304 </button>
305 <button
306 className={`tab-button ${activeTab === 'discounts' ? 'active' : ''}`}
307 onClick={() => setActiveTab('discounts')}
308 >
309 折扣管理
310 </button>
DREWae420b22025-06-02 14:07:20 +0800311 <button
312 className={`tab-button ${activeTab === 'announcements' ? 'active' : ''}`}
313 onClick={() => setActiveTab('announcements')}
314 >
315 公告管理
316 </button>
22301080a93bebb2025-05-27 19:48:11 +0800317 </div>
318
319 {activeTab === 'users' ? (
320 <>
321 {/* 搜索框 */}
322 <div className="search-container">
323 <input
324 type="text"
325 value={searchKey}
326 onChange={(e) => setSearchKey(e.target.value)}
327 placeholder="输入用户名搜索"
328 className="search-input"
329 />
330 <button onClick={handleSearch} className="search-button">
331 搜索
332 </button>
333 <button onClick={handleReset} className="reset-button">
334 重置
335 </button>
336 </div>
337
338 {/* 错误提示 */}
339 {error && <div className="error-message">{error}</div>}
340
341 {/* 加载状态 */}
342 {loading && <div className="loading-message">加载中...</div>}
343
344 {/* 用户列表 */}
345 <div className="user-list-container">
346 <table className="user-table">
347 <thead>
348 <tr>
349 <th>用户名</th>
350 <th>注册时间</th>
351 <th>最后登录</th>
352 <th>上传量</th>
353 <th>下载量</th>
354 <th>分享率</th>
355 <th>魔力值</th>
356 <th>权限</th>
357 <th>操作</th>
358 </tr>
359 </thead>
360 <tbody>
361 {Array.isArray(users) && users.map((user) => (
362 <tr key={user.username}>
363 <td>{user.username}</td>
364 <td>{formatDate(user.registTime)}</td>
365 <td>{formatDate(user.lastLogin)}</td>
366 <td>{user.upload}</td>
367 <td>{user.download}</td>
368 <td>{formatShareRate(user.shareRate)}</td>
369 <td>{user.magicPoints}</td>
370 <td>{user.authority}</td>
371 <td>
372 <select
373 value={user.authority}
374 onChange={(e) => handleChangeAuthority(user.username, e.target.value)}
375 className="authority-select"
376 >
377 <option value="USER">普通用户</option>
378 <option value="ADMIN">管理员</option>
379 <option value="LIMIT">受限用户</option>
380 <option value="BAN">封禁用户</option>
381 </select>
382 </td>
383 </tr>
384 ))}
385 </tbody>
386 </table>
387 </div>
388 </>
DREWae420b22025-06-02 14:07:20 +0800389 ) : activeTab === 'discounts' ? (
390 /* 新增的折扣管理部分 */
391 <>
392 {/* 当前活动折扣 */}
393 <div className="current-discount-section">
394 <h3>当前活动折扣</h3>
395 {currentDiscount ? (
396 <div className="current-discount-card">
397 <p><strong>名称:</strong> {currentDiscount.name}</p>
398 <p><strong>类型:</strong> {translateDiscountType(currentDiscount.discountType)}</p>
399 <p><strong>时间:</strong> {formatDateTime(currentDiscount.startTime)} 至 {formatDateTime(currentDiscount.endTime)}</p>
400 <p><strong>状态:</strong> {currentDiscount.status}</p>
401 </div>
402 ) : (
403 <p>当前没有进行中的折扣</p>
404 )}
405 </div>
22301080a93bebb2025-05-27 19:48:11 +0800406
DREWae420b22025-06-02 14:07:20 +0800407 {/* 添加新折扣表单 */}
408 <div className="add-discount-form">
409 <h3>添加新折扣</h3>
410 <div className="form-group">
411 <label>折扣名称:</label>
412 <input
413 type="text"
414 value={newDiscount.name}
415 onChange={(e) => setNewDiscount({...newDiscount, name: e.target.value})}
22301080a93bebb2025-05-27 19:48:11 +0800416 />
DREWae420b22025-06-02 14:07:20 +0800417 </div>
418 <div className="form-group">
419 <label>开始时间:</label>
22301080a93bebb2025-05-27 19:48:11 +0800420 <DatePicker
DREWae420b22025-06-02 14:07:20 +0800421 selected={startDate}
422 onChange={(date) => setStartDate(date)}
22301080a93bebb2025-05-27 19:48:11 +0800423 showTimeSelect
424 timeFormat="HH:mm"
425 timeIntervals={1} // 1分钟间隔
426 dateFormat="yyyy-MM-dd HH:mm"
DREWae420b22025-06-02 14:07:20 +0800427 minDate={new Date()}
428 placeholderText="选择开始日期和时间"
22301080a93bebb2025-05-27 19:48:11 +0800429 />
DREWae420b22025-06-02 14:07:20 +0800430 </div>
431 <div className="form-group">
432 <label>结束时间:</label>
433 <DatePicker
434 selected={endDate}
435 onChange={(date) => setEndDate(date)}
436 showTimeSelect
437 timeFormat="HH:mm"
438 timeIntervals={1} // 1分钟间隔
439 dateFormat="yyyy-MM-dd HH:mm"
440 minDate={startDate}
441 placeholderText="选择结束日期和时间"
442 />
443 </div>
444 <div className="form-group">
445 <label>折扣类型:</label>
446 <select
447 value={newDiscount.discountType}
448 onChange={(e) => setNewDiscount({...newDiscount, discountType: e.target.value})}
449 >
450 <option value="FREE">全部免费</option>
451 <option value="HALF">半价下载</option>
452 <option value="DOUBLE">双倍上传</option>
453 </select>
454 </div>
455 <button
456 onClick={(e) => {
457 e.preventDefault(); // 确保没有阻止默认行为
458 handleAddDiscount();
459 }}
460 >
461 添加折扣
462 </button>
463 </div>
464
465 {/* 所有折扣列表 */}
466 <div className="discount-list-container">
467 <h3>所有折扣计划</h3>
468 <table className="discount-table">
469 <thead>
470 <tr>
471 <th>ID</th>
472 <th>名称</th>
473 <th>开始时间</th>
474 <th>结束时间</th>
475 <th>类型</th>
476 <th>创建时间</th>
477 <th>状态</th>
478 <th>操作</th>
479 </tr>
480 </thead>
481 <tbody>
482 {discounts.map(discount => (
483 <tr key={discount.id}>
484 <td>{discount.id}</td>
485 <td>{discount.name}</td>
486 <td>{formatDateTime(discount.startTime)}</td>
487 <td>{formatDateTime(discount.endTime)}</td>
488 <td>{translateDiscountType(discount.discountType)}</td>
489 <td>{formatDateTime(discount.createTime)}</td>
490 <td>{discount.status || '未知'}</td>
491 <td>
492 <button
493 onClick={() => handleDeleteDiscount(discount.id)}
494 className="delete-button"
495 >
496 删除
497 </button>
498 </td>
499 </tr>
500 ))}
501 </tbody>
502 </table>
503 </div>
504 </>
505 ) : (
506 /* 新增的公告管理部分 */
507 <>
508 {/* 发布新公告表单 */}
509 <div className="announcement-form">
510 <h3>发布新公告</h3>
511 <div className="form-group">
512 <label>公告标题:</label>
513 <input
514 type="text"
515 value={newAnnouncement.title}
516 onChange={(e) => setNewAnnouncement({
517 ...newAnnouncement,
518 title: e.target.value
519 })}
520 placeholder="输入公告标题"
521 />
22301080a93bebb2025-05-27 19:48:11 +0800522 </div>
523 <div className="form-group">
DREWae420b22025-06-02 14:07:20 +0800524 <label>公告内容:</label>
525 <textarea
526 value={newAnnouncement.content}
527 onChange={(e) => setNewAnnouncement({
528 ...newAnnouncement,
529 content: e.target.value
530 })}
531 rows="5"
532 placeholder="输入公告内容"
533 />
22301080a93bebb2025-05-27 19:48:11 +0800534 </div>
DREWae420b22025-06-02 14:07:20 +0800535 <button onClick={handlePostAnnouncement}>
536 发布公告
22301080a93bebb2025-05-27 19:48:11 +0800537 </button>
538 </div>
539
DREWae420b22025-06-02 14:07:20 +0800540 {/* 所有公告列表 */}
541 <div className="announcement-list-container">
542 <h3>所有公告</h3>
543 <table className="announcement-table">
22301080a93bebb2025-05-27 19:48:11 +0800544 <thead>
545 <tr>
DREWae420b22025-06-02 14:07:20 +0800546 <th>标题</th>
547 <th>内容</th>
548 <th>发布时间</th>
22301080a93bebb2025-05-27 19:48:11 +0800549 </tr>
550 </thead>
551 <tbody>
DREWae420b22025-06-02 14:07:20 +0800552 {announcements.map(announcement => (
553 <tr key={announcement.id}>
554 <td>{announcement.title}</td>
555 <td>{announcement.content}</td>
556 <td>{formatDateTime(announcement.createTime)}</td>
22301080a93bebb2025-05-27 19:48:11 +0800557 </tr>
558 ))}
559 </tbody>
560 </table>
561 </div>
562 </>
563 )}
564 </div>
565 );
566};
567
568export default Administer;