blob: 0f915e37364ff927b9640197a0d58e7270eb2546 [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();
251 // 确保总是设置为数组
252 setAnnouncements(Array.isArray(announcements) ? announcements : []);
253 } catch (err) {
254 setError('获取公告列表失败: ' + err.message);
255 console.error(err);
256 } finally {
257 setLoading(false);
258 }
259};
260
261 // 发布新公告
262 const handlePostAnnouncement = async () => {
263 if (!newAnnouncement.title || !newAnnouncement.content) {
264 setError('请填写公告标题和内容');
265 return;
266 }
267
268 try {
269 await postAnnouncement(newAnnouncement);
270 setNewAnnouncement({ title: '', content: '' });
271 fetchAllAnnouncements();
272 setError(null);
273 } catch (err) {
274 setError('发布公告失败: ' + err.message);
275 console.error(err);
276 }
277 };
278
279 // 初始化加载数据
280 useEffect(() => {
281 if (activeTab === 'users') {
282 fetchAllUsers();
283 } else if (activeTab === 'discounts') {
284 fetchAllDiscounts();
285 fetchCurrentDiscount();
286 } else if (activeTab === 'announcements') {
287 fetchAllAnnouncements();
288 }
289 }, [activeTab]);
290
291
292
22301080a93bebb2025-05-27 19:48:11 +0800293
294 return (
295 <div className="administer-container">
296 <h1>系统管理</h1>
297
298 {/* 选项卡切换 */}
299 <div className="tab-container">
300 <button
301 className={`tab-button ${activeTab === 'users' ? 'active' : ''}`}
302 onClick={() => setActiveTab('users')}
303 >
304 用户管理
305 </button>
306 <button
307 className={`tab-button ${activeTab === 'discounts' ? 'active' : ''}`}
308 onClick={() => setActiveTab('discounts')}
309 >
310 折扣管理
311 </button>
DREWae420b22025-06-02 14:07:20 +0800312 <button
313 className={`tab-button ${activeTab === 'announcements' ? 'active' : ''}`}
314 onClick={() => setActiveTab('announcements')}
315 >
316 公告管理
317 </button>
22301080a93bebb2025-05-27 19:48:11 +0800318 </div>
319
320 {activeTab === 'users' ? (
321 <>
322 {/* 搜索框 */}
323 <div className="search-container">
324 <input
325 type="text"
326 value={searchKey}
327 onChange={(e) => setSearchKey(e.target.value)}
328 placeholder="输入用户名搜索"
329 className="search-input"
330 />
331 <button onClick={handleSearch} className="search-button">
332 搜索
333 </button>
334 <button onClick={handleReset} className="reset-button">
335 重置
336 </button>
337 </div>
338
339 {/* 错误提示 */}
340 {error && <div className="error-message">{error}</div>}
341
342 {/* 加载状态 */}
343 {loading && <div className="loading-message">加载中...</div>}
344
345 {/* 用户列表 */}
346 <div className="user-list-container">
347 <table className="user-table">
348 <thead>
349 <tr>
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 <th>操作</th>
359 </tr>
360 </thead>
361 <tbody>
362 {Array.isArray(users) && users.map((user) => (
363 <tr key={user.username}>
364 <td>{user.username}</td>
365 <td>{formatDate(user.registTime)}</td>
366 <td>{formatDate(user.lastLogin)}</td>
367 <td>{user.upload}</td>
368 <td>{user.download}</td>
369 <td>{formatShareRate(user.shareRate)}</td>
370 <td>{user.magicPoints}</td>
371 <td>{user.authority}</td>
372 <td>
373 <select
374 value={user.authority}
375 onChange={(e) => handleChangeAuthority(user.username, e.target.value)}
376 className="authority-select"
377 >
378 <option value="USER">普通用户</option>
379 <option value="ADMIN">管理员</option>
380 <option value="LIMIT">受限用户</option>
381 <option value="BAN">封禁用户</option>
382 </select>
383 </td>
384 </tr>
385 ))}
386 </tbody>
387 </table>
388 </div>
389 </>
DREWae420b22025-06-02 14:07:20 +0800390 ) : activeTab === 'discounts' ? (
391 /* 新增的折扣管理部分 */
392 <>
393 {/* 当前活动折扣 */}
394 <div className="current-discount-section">
395 <h3>当前活动折扣</h3>
396 {currentDiscount ? (
397 <div className="current-discount-card">
398 <p><strong>名称:</strong> {currentDiscount.name}</p>
399 <p><strong>类型:</strong> {translateDiscountType(currentDiscount.discountType)}</p>
400 <p><strong>时间:</strong> {formatDateTime(currentDiscount.startTime)} 至 {formatDateTime(currentDiscount.endTime)}</p>
401 <p><strong>状态:</strong> {currentDiscount.status}</p>
402 </div>
403 ) : (
404 <p>当前没有进行中的折扣</p>
405 )}
406 </div>
22301080a93bebb2025-05-27 19:48:11 +0800407
DREWae420b22025-06-02 14:07:20 +0800408 {/* 添加新折扣表单 */}
409 <div className="add-discount-form">
410 <h3>添加新折扣</h3>
411 <div className="form-group">
412 <label>折扣名称:</label>
413 <input
414 type="text"
415 value={newDiscount.name}
416 onChange={(e) => setNewDiscount({...newDiscount, name: e.target.value})}
22301080a93bebb2025-05-27 19:48:11 +0800417 />
DREWae420b22025-06-02 14:07:20 +0800418 </div>
419 <div className="form-group">
420 <label>开始时间:</label>
22301080a93bebb2025-05-27 19:48:11 +0800421 <DatePicker
DREWae420b22025-06-02 14:07:20 +0800422 selected={startDate}
423 onChange={(date) => setStartDate(date)}
22301080a93bebb2025-05-27 19:48:11 +0800424 showTimeSelect
425 timeFormat="HH:mm"
426 timeIntervals={1} // 1分钟间隔
427 dateFormat="yyyy-MM-dd HH:mm"
DREWae420b22025-06-02 14:07:20 +0800428 minDate={new Date()}
429 placeholderText="选择开始日期和时间"
22301080a93bebb2025-05-27 19:48:11 +0800430 />
DREWae420b22025-06-02 14:07:20 +0800431 </div>
432 <div className="form-group">
433 <label>结束时间:</label>
434 <DatePicker
435 selected={endDate}
436 onChange={(date) => setEndDate(date)}
437 showTimeSelect
438 timeFormat="HH:mm"
439 timeIntervals={1} // 1分钟间隔
440 dateFormat="yyyy-MM-dd HH:mm"
441 minDate={startDate}
442 placeholderText="选择结束日期和时间"
443 />
444 </div>
445 <div className="form-group">
446 <label>折扣类型:</label>
447 <select
448 value={newDiscount.discountType}
449 onChange={(e) => setNewDiscount({...newDiscount, discountType: e.target.value})}
450 >
451 <option value="FREE">全部免费</option>
452 <option value="HALF">半价下载</option>
453 <option value="DOUBLE">双倍上传</option>
454 </select>
455 </div>
456 <button
457 onClick={(e) => {
458 e.preventDefault(); // 确保没有阻止默认行为
459 handleAddDiscount();
460 }}
461 >
462 添加折扣
463 </button>
464 </div>
465
466 {/* 所有折扣列表 */}
467 <div className="discount-list-container">
468 <h3>所有折扣计划</h3>
469 <table className="discount-table">
470 <thead>
471 <tr>
472 <th>ID</th>
473 <th>名称</th>
474 <th>开始时间</th>
475 <th>结束时间</th>
476 <th>类型</th>
477 <th>创建时间</th>
478 <th>状态</th>
479 <th>操作</th>
480 </tr>
481 </thead>
482 <tbody>
483 {discounts.map(discount => (
484 <tr key={discount.id}>
485 <td>{discount.id}</td>
486 <td>{discount.name}</td>
487 <td>{formatDateTime(discount.startTime)}</td>
488 <td>{formatDateTime(discount.endTime)}</td>
489 <td>{translateDiscountType(discount.discountType)}</td>
490 <td>{formatDateTime(discount.createTime)}</td>
491 <td>{discount.status || '未知'}</td>
492 <td>
493 <button
494 onClick={() => handleDeleteDiscount(discount.id)}
495 className="delete-button"
496 >
497 删除
498 </button>
499 </td>
500 </tr>
501 ))}
502 </tbody>
503 </table>
504 </div>
505 </>
506 ) : (
507 /* 新增的公告管理部分 */
508 <>
509 {/* 发布新公告表单 */}
510 <div className="announcement-form">
511 <h3>发布新公告</h3>
512 <div className="form-group">
513 <label>公告标题:</label>
514 <input
515 type="text"
516 value={newAnnouncement.title}
517 onChange={(e) => setNewAnnouncement({
518 ...newAnnouncement,
519 title: e.target.value
520 })}
521 placeholder="输入公告标题"
522 />
22301080a93bebb2025-05-27 19:48:11 +0800523 </div>
524 <div className="form-group">
DREWae420b22025-06-02 14:07:20 +0800525 <label>公告内容:</label>
526 <textarea
527 value={newAnnouncement.content}
528 onChange={(e) => setNewAnnouncement({
529 ...newAnnouncement,
530 content: e.target.value
531 })}
532 rows="5"
533 placeholder="输入公告内容"
534 />
22301080a93bebb2025-05-27 19:48:11 +0800535 </div>
DREWae420b22025-06-02 14:07:20 +0800536 <button onClick={handlePostAnnouncement}>
537 发布公告
22301080a93bebb2025-05-27 19:48:11 +0800538 </button>
539 </div>
540
DREWae420b22025-06-02 14:07:20 +0800541 {/* 所有公告列表 */}
542 <div className="announcement-list-container">
543 <h3>所有公告</h3>
544 <table className="announcement-table">
22301080a93bebb2025-05-27 19:48:11 +0800545 <thead>
546 <tr>
DREWae420b22025-06-02 14:07:20 +0800547 <th>标题</th>
548 <th>内容</th>
549 <th>发布时间</th>
22301080a93bebb2025-05-27 19:48:11 +0800550 </tr>
551 </thead>
552 <tbody>
DREWae420b22025-06-02 14:07:20 +0800553 {announcements.map(announcement => (
554 <tr key={announcement.id}>
555 <td>{announcement.title}</td>
556 <td>{announcement.content}</td>
557 <td>{formatDateTime(announcement.createTime)}</td>
22301080a93bebb2025-05-27 19:48:11 +0800558 </tr>
559 ))}
560 </tbody>
561 </table>
562 </div>
563 </>
564 )}
565 </div>
566 );
567};
568
569export default Administer;