“实现帖子与评论上传图片,删除评论,评论计数,管理员界面”
Change-Id: I33d5331e41de0411f2d6f1913f3a939db61f665d
diff --git a/src/components/Administer.jsx b/src/components/Administer.jsx
new file mode 100644
index 0000000..35ae428
--- /dev/null
+++ b/src/components/Administer.jsx
@@ -0,0 +1,459 @@
+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 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 [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'
+
+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);
+ }
+ };
+
+ // 初始化加载数据
+ useEffect(() => {
+ if (activeTab === 'users') {
+ fetchAllUsers();
+ } else {
+ fetchAllDiscounts();
+ fetchCurrentDiscount();
+ }
+ }, [activeTab]);
+
+ // 格式化分享率为百分比
+ 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;
+ }
+ };
+
+
+ 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>
+ </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>
+ </>
+ ) : (
+ /* 新增的折扣管理部分 */
+ <>
+ {/* 当前活动折扣 */}
+ <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>
+ );
+};
+
+export default Administer;
\ No newline at end of file