| import React, { useState, useEffect } from 'react'; |
| import { useNavigate } from 'react-router-dom'; |
| import './Administer.css'; |
| import { |
| getAllUsers, |
| searchUsers, |
| updateUserAuthority, |
| getAllDiscounts, |
| getCurrentDiscount, |
| addDiscount, |
| deleteDiscount |
| } from '../api/administer'; |
| import {postAnnouncement, getAnnouncements} from '../api/announcement'; |
| import DatePicker from 'react-datepicker'; |
| import 'react-datepicker/dist/react-datepicker.css'; |
| |
| |
| const Administer = () => { |
| const navigate = useNavigate(); |
| const [users, setUsers] = useState([]); |
| const [discounts, setDiscounts] = useState([]); |
| const [currentDiscount, setCurrentDiscount] = useState(null); |
| const [searchKey, setSearchKey] = useState(''); |
| const [loading, setLoading] = useState(false); |
| const [error, setError] = useState(null); |
| const [announcements, setAnnouncements] = useState([]); // 存储公告列表 |
| const [newAnnouncement, setNewAnnouncement] = useState({ |
| title: '', |
| content: '' |
| }); |
| const [newDiscount, setNewDiscount] = useState({ |
| name: '', |
| discountType: 'FREE' |
| }); |
| const [startDate, setStartDate] = useState(new Date()); |
| const [endDate, setEndDate] = useState(new Date()); |
| const [activeTab, setActiveTab] = useState('users'); // 'users' 或 'discounts','announcements' |
| |
| const fetchAllUsers = async () => { |
| setLoading(true); |
| setError(null); |
| try { |
| const users = await getAllUsers(); |
| console.log("API Data:", users); // 现在应该直接是用户数组 |
| |
| const formattedUsers = users.map(user => ({ |
| username: user.username || '未知用户', |
| authority: user.authority || 'USER', |
| registTime: user.registTime || null, |
| lastLogin: user.lastLogin || null, |
| upload: Number(user.upload) || 0, |
| download: Number(user.download) || 0, |
| magicPoints: Number(user.magicPoints) || 0, |
| shareRate: Number(user.shareRate) || 0 |
| })); |
| |
| console.log("Formatted Users:", formattedUsers); |
| setUsers(formattedUsers); |
| } catch (err) { |
| console.error("Error details:", err); |
| setError(`获取用户列表失败: ${err.message}`); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| |
| const handleSearch = async () => { |
| if (!searchKey.trim()) { |
| fetchAllUsers(); |
| return; |
| } |
| |
| setLoading(true); |
| setError(null); |
| try { |
| const users = await searchUsers(searchKey); |
| console.log("Search Results:", users); // 打印搜索结果 |
| |
| // 格式化数据(确保数值字段正确解析) |
| const formattedUsers = users.map(user => ({ |
| username: user.username || '未知用户', |
| authority: user.authority || 'USER', |
| registTime: user.registTime || null, |
| lastLogin: user.lastLogin || null, |
| upload: Number(user.upload) || 0, // 确保解析为数字 |
| download: Number(user.download) || 0, |
| magicPoints: Number(user.magicPoints) || 0, |
| shareRate: Number(user.shareRate) || 0 |
| })); |
| |
| setUsers(formattedUsers); |
| } catch (err) { |
| setError('搜索用户失败,请重试'); |
| console.error(err); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| // 重置搜索 |
| const handleReset = () => { |
| setSearchKey(''); |
| fetchAllUsers(); |
| }; |
| |
| // 修改用户权限 |
| const handleChangeAuthority = async (username, newAuthority) => { |
| try { |
| await updateUserAuthority(username, newAuthority); |
| // 更新本地状态 |
| setUsers(users.map(user => |
| user.username === username ? { ...user, authority: newAuthority } : user |
| )); |
| } catch (err) { |
| setError('修改权限失败,请重试'); |
| console.error(err); |
| } |
| }; |
| |
| // 获取所有折扣 |
| const fetchAllDiscounts = async () => { |
| setLoading(true); |
| setError(null); |
| try { |
| const data = await getAllDiscounts(); |
| setDiscounts(data); |
| } catch (err) { |
| setError('获取折扣列表失败: ' + err.message); |
| console.error(err); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| // 获取当前折扣 |
| const fetchCurrentDiscount = async () => { |
| try { |
| const data = await getCurrentDiscount(); |
| setCurrentDiscount(data); |
| } catch (err) { |
| console.error('获取当前折扣失败:', err); |
| } |
| }; |
| |
| const handleAddDiscount = async () => { |
| if (!newDiscount.name || !startDate || !endDate) { |
| setError('请填写所有必填字段'); |
| return; |
| } |
| |
| try { |
| // 验证时间 |
| if (startDate >= endDate) { |
| setError('结束时间必须晚于开始时间'); |
| return; |
| } |
| |
| const payload = { |
| name: newDiscount.name, |
| startTime: formatDateToISO(startDate), // 例如: "2025-06-01T14:30:00" |
| endTime: formatDateToISO(endDate, true), // 例如: "2025-06-01T18:45:59" |
| discountType: newDiscount.discountType |
| }; |
| |
| console.log('提交数据:', payload); // 调试用 |
| |
| await addDiscount(payload); |
| |
| // 重置表单 |
| setNewDiscount({ |
| name: '', |
| discountType: 'FREE' |
| }); |
| setStartDate(new Date()); |
| setEndDate(new Date()); |
| |
| fetchAllDiscounts(); |
| setError(null); |
| } catch (err) { |
| setError('添加折扣失败: ' + err.message); |
| console.error(err); |
| } |
| }; |
| |
| const formatDateToISO = (date, isEndTime = false) => { |
| if (!date) return ''; |
| |
| const pad = (num) => num.toString().padStart(2, '0'); |
| |
| const year = date.getFullYear(); |
| const month = pad(date.getMonth() + 1); |
| const day = pad(date.getDate()); |
| const hours = pad(date.getHours()); |
| const minutes = pad(date.getMinutes()); |
| |
| if (isEndTime) { |
| // 结束时间精确到用户选择的时间+59秒 |
| return `${year}-${month}-${day}T${hours}:${minutes}:59`; |
| } else { |
| // 开始时间精确到用户选择的时间+00秒 |
| return `${year}-${month}-${day}T${hours}:${minutes}:00`; |
| } |
| }; |
| |
| // 删除折扣 |
| const handleDeleteDiscount = async (id) => { |
| try { |
| await deleteDiscount(id); |
| fetchAllDiscounts(); |
| } catch (err) { |
| setError('删除折扣失败: ' + err.message); |
| console.error(err); |
| } |
| }; |
| |
| |
| |
| // 格式化分享率为百分比 |
| const formatShareRate = (rate) => { |
| return (rate * 100).toFixed(2) + '%'; |
| }; |
| |
| // 格式化日期 |
| const formatDate = (date) => { |
| if (!date) return '-'; |
| return new Date(date).toLocaleDateString(); |
| }; |
| // 格式化日期 |
| const formatDateTime = (dateTime) => { |
| if (!dateTime) return '-'; |
| return new Date(dateTime).toLocaleString(); |
| }; |
| |
| // 折扣类型翻译 |
| const translateDiscountType = (type) => { |
| switch (type) { |
| case 'FREE': return '全部免费'; |
| case 'HALF': return '半价下载'; |
| case 'DOUBLE': return '双倍上传'; |
| default: return type; |
| } |
| }; |
| |
| // 获取所有公告 |
| const fetchAllAnnouncements = async () => { |
| setLoading(true); |
| setError(null); |
| try { |
| const announcements = await getAnnouncements(); |
| setAnnouncements(announcements); |
| } catch (err) { |
| setError('获取公告列表失败: ' + err.message); |
| console.error(err); |
| } finally { |
| setLoading(false); |
| } |
| }; |
| |
| // 发布新公告 |
| const handlePostAnnouncement = async () => { |
| if (!newAnnouncement.title || !newAnnouncement.content) { |
| setError('请填写公告标题和内容'); |
| return; |
| } |
| |
| try { |
| await postAnnouncement(newAnnouncement); |
| setNewAnnouncement({ title: '', content: '' }); |
| fetchAllAnnouncements(); |
| setError(null); |
| } catch (err) { |
| setError('发布公告失败: ' + err.message); |
| console.error(err); |
| } |
| }; |
| |
| // 初始化加载数据 |
| useEffect(() => { |
| if (activeTab === 'users') { |
| fetchAllUsers(); |
| } else if (activeTab === 'discounts') { |
| fetchAllDiscounts(); |
| fetchCurrentDiscount(); |
| } else if (activeTab === 'announcements') { |
| fetchAllAnnouncements(); |
| } |
| }, [activeTab]); |
| |
| |
| |
| |
| return ( |
| <div className="administer-container"> |
| <h1>系统管理</h1> |
| |
| {/* 选项卡切换 */} |
| <div className="tab-container"> |
| <button |
| className={`tab-button ${activeTab === 'users' ? 'active' : ''}`} |
| onClick={() => setActiveTab('users')} |
| > |
| 用户管理 |
| </button> |
| <button |
| className={`tab-button ${activeTab === 'discounts' ? 'active' : ''}`} |
| onClick={() => setActiveTab('discounts')} |
| > |
| 折扣管理 |
| </button> |
| <button |
| className={`tab-button ${activeTab === 'announcements' ? 'active' : ''}`} |
| onClick={() => setActiveTab('announcements')} |
| > |
| 公告管理 |
| </button> |
| </div> |
| |
| {activeTab === 'users' ? ( |
| <> |
| {/* 搜索框 */} |
| <div className="search-container"> |
| <input |
| type="text" |
| value={searchKey} |
| onChange={(e) => setSearchKey(e.target.value)} |
| placeholder="输入用户名搜索" |
| className="search-input" |
| /> |
| <button onClick={handleSearch} className="search-button"> |
| 搜索 |
| </button> |
| <button onClick={handleReset} className="reset-button"> |
| 重置 |
| </button> |
| </div> |
| |
| {/* 错误提示 */} |
| {error && <div className="error-message">{error}</div>} |
| |
| {/* 加载状态 */} |
| {loading && <div className="loading-message">加载中...</div>} |
| |
| {/* 用户列表 */} |
| <div className="user-list-container"> |
| <table className="user-table"> |
| <thead> |
| <tr> |
| <th>用户名</th> |
| <th>注册时间</th> |
| <th>最后登录</th> |
| <th>上传量</th> |
| <th>下载量</th> |
| <th>分享率</th> |
| <th>魔力值</th> |
| <th>权限</th> |
| <th>操作</th> |
| </tr> |
| </thead> |
| <tbody> |
| {Array.isArray(users) && users.map((user) => ( |
| <tr key={user.username}> |
| <td>{user.username}</td> |
| <td>{formatDate(user.registTime)}</td> |
| <td>{formatDate(user.lastLogin)}</td> |
| <td>{user.upload}</td> |
| <td>{user.download}</td> |
| <td>{formatShareRate(user.shareRate)}</td> |
| <td>{user.magicPoints}</td> |
| <td>{user.authority}</td> |
| <td> |
| <select |
| value={user.authority} |
| onChange={(e) => handleChangeAuthority(user.username, e.target.value)} |
| className="authority-select" |
| > |
| <option value="USER">普通用户</option> |
| <option value="ADMIN">管理员</option> |
| <option value="LIMIT">受限用户</option> |
| <option value="BAN">封禁用户</option> |
| </select> |
| </td> |
| </tr> |
| ))} |
| </tbody> |
| </table> |
| </div> |
| </> |
| ) : activeTab === 'discounts' ? ( |
| /* 新增的折扣管理部分 */ |
| <> |
| {/* 当前活动折扣 */} |
| <div className="current-discount-section"> |
| <h3>当前活动折扣</h3> |
| {currentDiscount ? ( |
| <div className="current-discount-card"> |
| <p><strong>名称:</strong> {currentDiscount.name}</p> |
| <p><strong>类型:</strong> {translateDiscountType(currentDiscount.discountType)}</p> |
| <p><strong>时间:</strong> {formatDateTime(currentDiscount.startTime)} 至 {formatDateTime(currentDiscount.endTime)}</p> |
| <p><strong>状态:</strong> {currentDiscount.status}</p> |
| </div> |
| ) : ( |
| <p>当前没有进行中的折扣</p> |
| )} |
| </div> |
| |
| {/* 添加新折扣表单 */} |
| <div className="add-discount-form"> |
| <h3>添加新折扣</h3> |
| <div className="form-group"> |
| <label>折扣名称:</label> |
| <input |
| type="text" |
| value={newDiscount.name} |
| onChange={(e) => setNewDiscount({...newDiscount, name: e.target.value})} |
| /> |
| </div> |
| <div className="form-group"> |
| <label>开始时间:</label> |
| <DatePicker |
| selected={startDate} |
| onChange={(date) => setStartDate(date)} |
| showTimeSelect |
| timeFormat="HH:mm" |
| timeIntervals={1} // 1分钟间隔 |
| dateFormat="yyyy-MM-dd HH:mm" |
| minDate={new Date()} |
| placeholderText="选择开始日期和时间" |
| /> |
| </div> |
| <div className="form-group"> |
| <label>结束时间:</label> |
| <DatePicker |
| selected={endDate} |
| onChange={(date) => setEndDate(date)} |
| showTimeSelect |
| timeFormat="HH:mm" |
| timeIntervals={1} // 1分钟间隔 |
| dateFormat="yyyy-MM-dd HH:mm" |
| minDate={startDate} |
| placeholderText="选择结束日期和时间" |
| /> |
| </div> |
| <div className="form-group"> |
| <label>折扣类型:</label> |
| <select |
| value={newDiscount.discountType} |
| onChange={(e) => setNewDiscount({...newDiscount, discountType: e.target.value})} |
| > |
| <option value="FREE">全部免费</option> |
| <option value="HALF">半价下载</option> |
| <option value="DOUBLE">双倍上传</option> |
| </select> |
| </div> |
| <button |
| onClick={(e) => { |
| e.preventDefault(); // 确保没有阻止默认行为 |
| handleAddDiscount(); |
| }} |
| > |
| 添加折扣 |
| </button> |
| </div> |
| |
| {/* 所有折扣列表 */} |
| <div className="discount-list-container"> |
| <h3>所有折扣计划</h3> |
| <table className="discount-table"> |
| <thead> |
| <tr> |
| <th>ID</th> |
| <th>名称</th> |
| <th>开始时间</th> |
| <th>结束时间</th> |
| <th>类型</th> |
| <th>创建时间</th> |
| <th>状态</th> |
| <th>操作</th> |
| </tr> |
| </thead> |
| <tbody> |
| {discounts.map(discount => ( |
| <tr key={discount.id}> |
| <td>{discount.id}</td> |
| <td>{discount.name}</td> |
| <td>{formatDateTime(discount.startTime)}</td> |
| <td>{formatDateTime(discount.endTime)}</td> |
| <td>{translateDiscountType(discount.discountType)}</td> |
| <td>{formatDateTime(discount.createTime)}</td> |
| <td>{discount.status || '未知'}</td> |
| <td> |
| <button |
| onClick={() => handleDeleteDiscount(discount.id)} |
| className="delete-button" |
| > |
| 删除 |
| </button> |
| </td> |
| </tr> |
| ))} |
| </tbody> |
| </table> |
| </div> |
| </> |
| ) : ( |
| /* 新增的公告管理部分 */ |
| <> |
| {/* 发布新公告表单 */} |
| <div className="announcement-form"> |
| <h3>发布新公告</h3> |
| <div className="form-group"> |
| <label>公告标题:</label> |
| <input |
| type="text" |
| value={newAnnouncement.title} |
| onChange={(e) => setNewAnnouncement({ |
| ...newAnnouncement, |
| title: e.target.value |
| })} |
| placeholder="输入公告标题" |
| /> |
| </div> |
| <div className="form-group"> |
| <label>公告内容:</label> |
| <textarea |
| value={newAnnouncement.content} |
| onChange={(e) => setNewAnnouncement({ |
| ...newAnnouncement, |
| content: e.target.value |
| })} |
| rows="5" |
| placeholder="输入公告内容" |
| /> |
| </div> |
| <button onClick={handlePostAnnouncement}> |
| 发布公告 |
| </button> |
| </div> |
| |
| {/* 所有公告列表 */} |
| <div className="announcement-list-container"> |
| <h3>所有公告</h3> |
| <table className="announcement-table"> |
| <thead> |
| <tr> |
| <th>标题</th> |
| <th>内容</th> |
| <th>发布时间</th> |
| </tr> |
| </thead> |
| <tbody> |
| {announcements.map(announcement => ( |
| <tr key={announcement.id}> |
| <td>{announcement.title}</td> |
| <td>{announcement.content}</td> |
| <td>{formatDateTime(announcement.createTime)}</td> |
| </tr> |
| ))} |
| </tbody> |
| </table> |
| </div> |
| </> |
| )} |
| </div> |
| ); |
| }; |
| |
| export default Administer; |