641 lines
22 KiB
JavaScript
641 lines
22 KiB
JavaScript
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|||
|
|
import axios from '../../services/axios';
|
|||
|
|
import { adminAPI } from '../../services/api';
|
|||
|
|
import { toast } from 'react-toastify';
|
|||
|
|
import Pagination from '../../components/Pagination';
|
|||
|
|
import '../../styles/Pagination.css';
|
|||
|
|
import { debounce } from '../../utils/debounce';
|
|||
|
|
|
|||
|
|
const UserManagement = () => {
|
|||
|
|
const [users, setUsers] = useState([]);
|
|||
|
|
const [page, setPage] = useState(1);
|
|||
|
|
const [itemsPerPage, setItemsPerPage] = useState(10);
|
|||
|
|
const [totalPages, setTotalPages] = useState(1);
|
|||
|
|
const [totalItems, setTotalItems] = useState(0);
|
|||
|
|
const [loading, setLoading] = useState(false);
|
|||
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|||
|
|
const [sortBy, setSortBy] = useState('created_at');
|
|||
|
|
const [sortOrder, setSortOrder] = useState('desc');
|
|||
|
|
const [showAddModal, setShowAddModal] = useState(false);
|
|||
|
|
const [showEditModal, setShowEditModal] = useState(false);
|
|||
|
|
const [currentUser, setCurrentUser] = useState(null);
|
|||
|
|
const [currentLoginUser, setCurrentLoginUser] = useState(null);
|
|||
|
|
const [formData, setFormData] = useState({
|
|||
|
|
username: '',
|
|||
|
|
email: '',
|
|||
|
|
password: '',
|
|||
|
|
is_active: true,
|
|||
|
|
daily_limit: 100
|
|||
|
|
});
|
|||
|
|
const [selectedUsers, setSelectedUsers] = useState([]);
|
|||
|
|
const [showBatchModal, setShowBatchModal] = useState(false);
|
|||
|
|
const [batchAction, setBatchAction] = useState('');
|
|||
|
|
|
|||
|
|
// 获取用户列表
|
|||
|
|
const fetchUsers = async (reset = false) => {
|
|||
|
|
try {
|
|||
|
|
setLoading(true);
|
|||
|
|
const currentPage = reset ? 1 : page;
|
|||
|
|
if (reset) setPage(1);
|
|||
|
|
|
|||
|
|
const response = await axios.get('/api/admin/users', {
|
|||
|
|
params: {
|
|||
|
|
page: currentPage,
|
|||
|
|
per_page: itemsPerPage,
|
|||
|
|
search: searchQuery,
|
|||
|
|
sort_by: sortBy,
|
|||
|
|
sort_order: sortOrder
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const { users: newUsers, total_pages, total_users } = response.data;
|
|||
|
|
|
|||
|
|
setUsers(newUsers);
|
|||
|
|
setTotalPages(total_pages);
|
|||
|
|
setTotalItems(total_users);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取用户列表失败:', error);
|
|||
|
|
toast.error('获取用户列表失败');
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handlePageChange = (newPage) => {
|
|||
|
|
setPage(newPage);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleItemsPerPageChange = (newItemsPerPage) => {
|
|||
|
|
setItemsPerPage(newItemsPerPage);
|
|||
|
|
setPage(1);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 防抖搜索函数
|
|||
|
|
const debouncedSearch = useCallback(
|
|||
|
|
debounce(() => {
|
|||
|
|
fetchUsers(true);
|
|||
|
|
}, 500),
|
|||
|
|
[]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
fetchCurrentUser();
|
|||
|
|
}, []);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
debouncedSearch();
|
|||
|
|
}, [searchQuery, debouncedSearch]);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
fetchUsers();
|
|||
|
|
}, [sortBy, sortOrder, itemsPerPage, page]);
|
|||
|
|
|
|||
|
|
// 获取当前登录用户信息
|
|||
|
|
const fetchCurrentUser = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await axios.get('/api/auth/me');
|
|||
|
|
setCurrentLoginUser(res.data);
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('获取当前用户信息失败', err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理表单变化
|
|||
|
|
const handleChange = (e) => {
|
|||
|
|
const { name, value, type, checked } = e.target;
|
|||
|
|
setFormData({
|
|||
|
|
...formData,
|
|||
|
|
[name]: type === 'checkbox' ? checked : value
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 打开添加用户模态框
|
|||
|
|
const handleAddUser = () => {
|
|||
|
|
setFormData({
|
|||
|
|
username: '',
|
|||
|
|
email: '',
|
|||
|
|
password: '',
|
|||
|
|
is_active: false,
|
|||
|
|
daily_quota: 5
|
|||
|
|
});
|
|||
|
|
setShowAddModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 打开编辑用户模态框
|
|||
|
|
const handleEditUser = (user) => {
|
|||
|
|
setCurrentUser(user);
|
|||
|
|
setFormData({
|
|||
|
|
username: user.username,
|
|||
|
|
email: user.email,
|
|||
|
|
password: '', // 不显示现有密码
|
|||
|
|
is_active: user.is_active,
|
|||
|
|
daily_quota: user.daily_quota
|
|||
|
|
});
|
|||
|
|
setShowEditModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 提交添加用户
|
|||
|
|
const submitAddUser = async () => {
|
|||
|
|
try {
|
|||
|
|
if (!formData.username || !formData.password) {
|
|||
|
|
toast.warning('用户名和密码为必填项');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await axios.post('/api/admin/users', formData);
|
|||
|
|
toast.success('用户添加成功');
|
|||
|
|
setShowAddModal(false);
|
|||
|
|
fetchUsers();
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error('添加用户失败,可能用户名已存在');
|
|||
|
|
console.error(err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 提交编辑用户
|
|||
|
|
const submitEditUser = async () => {
|
|||
|
|
try {
|
|||
|
|
if (!currentUser) return;
|
|||
|
|
|
|||
|
|
// 准备要发送的数据,不包含空密码
|
|||
|
|
const updateData = { ...formData };
|
|||
|
|
if (!updateData.password) {
|
|||
|
|
delete updateData.password;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await axios.put(`/api/admin/users/${currentUser.id}`, updateData);
|
|||
|
|
toast.success('用户更新成功');
|
|||
|
|
setShowEditModal(false);
|
|||
|
|
fetchUsers();
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error('更新用户失败');
|
|||
|
|
console.error(err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 删除用户
|
|||
|
|
const handleDeleteUser = async (userId) => {
|
|||
|
|
if (window.confirm('确定要删除这个用户吗?此操作不可恢复。')) {
|
|||
|
|
try {
|
|||
|
|
await adminAPI.deleteUser(userId);
|
|||
|
|
toast.success('用户已删除');
|
|||
|
|
fetchUsers();
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error('删除用户失败');
|
|||
|
|
console.error(err);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理用户选择
|
|||
|
|
const handleSelectUser = (userId) => {
|
|||
|
|
setSelectedUsers(prev =>
|
|||
|
|
prev.includes(userId)
|
|||
|
|
? prev.filter(id => id !== userId)
|
|||
|
|
: [...prev, userId]
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 全选/取消全选
|
|||
|
|
const handleSelectAll = () => {
|
|||
|
|
if (selectedUsers.length === users.length) {
|
|||
|
|
setSelectedUsers([]);
|
|||
|
|
} else {
|
|||
|
|
setSelectedUsers(users.map(user => user.id));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 批量操作
|
|||
|
|
const handleBatchAction = (action) => {
|
|||
|
|
if (selectedUsers.length === 0) {
|
|||
|
|
toast.warning('请先选择用户');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
setBatchAction(action);
|
|||
|
|
setShowBatchModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 执行批量操作
|
|||
|
|
const executeBatchAction = async () => {
|
|||
|
|
try {
|
|||
|
|
let successCount = 0;
|
|||
|
|
|
|||
|
|
if (batchAction === 'delete') {
|
|||
|
|
if (!window.confirm(`确定要删除选中的 ${selectedUsers.length} 个用户吗?此操作不可恢复。`)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const userId of selectedUsers) {
|
|||
|
|
try {
|
|||
|
|
await adminAPI.deleteUser(userId);
|
|||
|
|
successCount++;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(`删除用户 ${userId} 失败:`, err);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
toast.success(`成功删除 ${successCount}/${selectedUsers.length} 个用户`);
|
|||
|
|
} else if (batchAction === 'activate') {
|
|||
|
|
for (const userId of selectedUsers) {
|
|||
|
|
try {
|
|||
|
|
await adminAPI.updateUser(userId, { is_active: true });
|
|||
|
|
successCount++;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(`激活用户 ${userId} 失败:`, err);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
toast.success(`成功激活 ${successCount}/${selectedUsers.length} 个用户`);
|
|||
|
|
} else if (batchAction === 'deactivate') {
|
|||
|
|
for (const userId of selectedUsers) {
|
|||
|
|
try {
|
|||
|
|
await adminAPI.updateUser(userId, { is_active: false });
|
|||
|
|
successCount++;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(`禁用用户 ${userId} 失败:`, err);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
toast.success(`成功禁用 ${successCount}/${selectedUsers.length} 个用户`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setShowBatchModal(false);
|
|||
|
|
setSelectedUsers([]);
|
|||
|
|
fetchUsers();
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error('批量操作失败');
|
|||
|
|
console.error(err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 切换用户激活状态
|
|||
|
|
const toggleUserStatus = async (userId, currentStatus) => {
|
|||
|
|
try {
|
|||
|
|
await axios.put(`/api/admin/users/${userId}`, {
|
|||
|
|
is_active: !currentStatus
|
|||
|
|
});
|
|||
|
|
toast.success(`用户已${!currentStatus ? '激活' : '禁用'}`);
|
|||
|
|
fetchUsers();
|
|||
|
|
} catch (err) {
|
|||
|
|
toast.error('更新用户状态失败');
|
|||
|
|
console.error(err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (loading && page === 1) {
|
|||
|
|
return (
|
|||
|
|
<div className="d-flex justify-content-center align-items-center vh-100">
|
|||
|
|
<div className="spinner-border text-primary" role="status">
|
|||
|
|
<span className="visually-hidden">Loading...</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<div className="d-flex justify-content-between align-items-center mb-4">
|
|||
|
|
<h1>用户管理</h1>
|
|||
|
|
<div className="flex items-center space-x-4">
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
placeholder="搜索用户名或邮箱..."
|
|||
|
|
className="p-2 border border-gray-300 rounded-md w-64 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|||
|
|
value={searchQuery}
|
|||
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|||
|
|
/>
|
|||
|
|
<select
|
|||
|
|
className="p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|||
|
|
|
|||
|
|
value={sortBy}
|
|||
|
|
onChange={(e) => setSortBy(e.target.value)}
|
|||
|
|
>
|
|||
|
|
<option value="created_at">按创建时间排序</option>
|
|||
|
|
<option value="username">按用户名排序</option>
|
|||
|
|
</select>
|
|||
|
|
<select
|
|||
|
|
className="p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|||
|
|
value={sortOrder}
|
|||
|
|
onChange={(e) => setSortOrder(e.target.value)}
|
|||
|
|
>
|
|||
|
|
<option value="desc">降序</option>
|
|||
|
|
<option value="asc">升序</option>
|
|||
|
|
</select>
|
|||
|
|
{selectedUsers.length > 0 && (
|
|||
|
|
<>
|
|||
|
|
<button
|
|||
|
|
className="btn-admin-outline-success btn-sm me-2"
|
|||
|
|
onClick={() => handleBatchAction('activate')}
|
|||
|
|
>
|
|||
|
|
批量激活
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="btn-admin-outline-warning btn-sm me-2"
|
|||
|
|
onClick={() => handleBatchAction('deactivate')}
|
|||
|
|
>
|
|||
|
|
批量禁用
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="btn-admin-outline-danger btn-sm me-2"
|
|||
|
|
onClick={() => handleBatchAction('delete')}
|
|||
|
|
>
|
|||
|
|
批量删除
|
|||
|
|
</button>
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
<button className="btn-admin-primary" onClick={handleAddUser}>
|
|||
|
|
<i className="bi bi-plus-circle me-2"></i>添加用户
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{selectedUsers.length > 0 && (
|
|||
|
|
<div className="alert alert-info d-flex justify-content-between align-items-center mb-3">
|
|||
|
|
<span>已选择 {selectedUsers.length} 个用户</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="card">
|
|||
|
|
<div className="card-body">
|
|||
|
|
<div className="table-responsive">
|
|||
|
|
<table className="table table-striped table-hover">
|
|||
|
|
<thead>
|
|||
|
|
<tr>
|
|||
|
|
<th>
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
checked={selectedUsers.length === users.length && users.length > 0}
|
|||
|
|
ref={(el) => {
|
|||
|
|
if (el) {
|
|||
|
|
el.indeterminate = selectedUsers.length > 0 && selectedUsers.length < users.length;
|
|||
|
|
}
|
|||
|
|
}}
|
|||
|
|
onChange={handleSelectAll}
|
|||
|
|
/>
|
|||
|
|
</th>
|
|||
|
|
<th>ID</th>
|
|||
|
|
<th>用户名</th>
|
|||
|
|
<th>邮箱</th>
|
|||
|
|
<th>状态</th>
|
|||
|
|
<th>每日限额</th>
|
|||
|
|
<th>注册时间</th>
|
|||
|
|
<th>操作</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody>
|
|||
|
|
{users.length === 0 ? (
|
|||
|
|
<tr>
|
|||
|
|
<td colSpan="8" className="text-center">暂无用户数据</td>
|
|||
|
|
</tr>
|
|||
|
|
) : (
|
|||
|
|
users.map(user => (
|
|||
|
|
<tr key={user.id}>
|
|||
|
|
<td>
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
checked={selectedUsers.includes(user.id)}
|
|||
|
|
onChange={() => handleSelectUser(user.id)}
|
|||
|
|
/>
|
|||
|
|
</td>
|
|||
|
|
<td>{user.id}</td>
|
|||
|
|
<td>{user.username}</td>
|
|||
|
|
<td>{user.email}</td>
|
|||
|
|
<td>
|
|||
|
|
<span className={`badge ${user.is_active ? 'bg-success' : 'bg-danger'}`}>
|
|||
|
|
{user.is_active ? '已激活' : '未激活'}
|
|||
|
|
</span>
|
|||
|
|
</td>
|
|||
|
|
<td>{user.daily_quota}</td>
|
|||
|
|
<td>{new Date(user.created_at).toLocaleString()}</td>
|
|||
|
|
<td>
|
|||
|
|
<div className="admin-btn-group">
|
|||
|
|
<button
|
|||
|
|
className="btn-admin-outline-primary btn-sm"
|
|||
|
|
onClick={() => handleEditUser(user)}
|
|||
|
|
>
|
|||
|
|
<i className="bi bi-pencil me-1"></i>编辑
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className={user.is_active ? "btn-admin-outline-danger btn-sm" : "btn-admin-outline-success btn-sm"}
|
|||
|
|
onClick={() => toggleUserStatus(user.id, user.is_active)}
|
|||
|
|
>
|
|||
|
|
<i className={`bi bi-${user.is_active ? 'x-circle' : 'check-circle'} me-1`}></i>
|
|||
|
|
{user.is_active ? '禁用' : '激活'}
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="btn-admin-outline-danger btn-sm"
|
|||
|
|
onClick={() => handleDeleteUser(user.id)}
|
|||
|
|
disabled={currentLoginUser && user.id === currentLoginUser.id} // 不能删除自己
|
|||
|
|
>
|
|||
|
|
<i className="bi bi-trash me-1"></i>删除
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
))
|
|||
|
|
)}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
<Pagination
|
|||
|
|
currentPage={page}
|
|||
|
|
totalPages={totalPages}
|
|||
|
|
itemsPerPage={itemsPerPage}
|
|||
|
|
onItemsPerPageChange={handleItemsPerPageChange}
|
|||
|
|
onPageChange={handlePageChange}
|
|||
|
|
totalItems={totalItems}
|
|||
|
|
loading={loading}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* 添加用户模态框 */}
|
|||
|
|
{showAddModal && (
|
|||
|
|
<div className="modal-overlay" onClick={() => setShowAddModal(false)}>
|
|||
|
|
<div className="admin-modal" onClick={(e) => e.stopPropagation()}>
|
|||
|
|
<div className="modal-header">
|
|||
|
|
<h5 className="modal-title">添加新用户</h5>
|
|||
|
|
<button className="modal-close" onClick={() => setShowAddModal(false)}>×</button>
|
|||
|
|
</div>
|
|||
|
|
<div className="modal-body">
|
|||
|
|
<form>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">用户名</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
className="form-control"
|
|||
|
|
name="username"
|
|||
|
|
value={formData.username}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">邮箱</label>
|
|||
|
|
<input
|
|||
|
|
type="email"
|
|||
|
|
className="form-control"
|
|||
|
|
name="email"
|
|||
|
|
value={formData.email}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">密码</label>
|
|||
|
|
<input
|
|||
|
|
type="password"
|
|||
|
|
className="form-control"
|
|||
|
|
name="password"
|
|||
|
|
value={formData.password}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">每日领取限额</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
className="form-control"
|
|||
|
|
name="daily_quota"
|
|||
|
|
value={formData.daily_quota}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
min="1"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3 form-check">
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
className="form-check-input"
|
|||
|
|
name="is_active"
|
|||
|
|
checked={formData.is_active}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
/>
|
|||
|
|
<label className="form-check-label">是否激活</label>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
<div className="modal-footer">
|
|||
|
|
<button className="btn-admin-outline-secondary" onClick={() => setShowAddModal(false)}>
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button className="btn-admin-outline-primary" onClick={submitAddUser}>
|
|||
|
|
添加
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 编辑用户模态框 */}
|
|||
|
|
{showEditModal && (
|
|||
|
|
<div className="modal-overlay" onClick={() => setShowEditModal(false)}>
|
|||
|
|
<div className="admin-modal" onClick={(e) => e.stopPropagation()}>
|
|||
|
|
<div className="modal-header">
|
|||
|
|
<h5 className="modal-title">编辑用户</h5>
|
|||
|
|
<button className="modal-close" onClick={() => setShowEditModal(false)}>×</button>
|
|||
|
|
</div>
|
|||
|
|
<div className="modal-body">
|
|||
|
|
<form>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">用户名</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
className="form-control"
|
|||
|
|
name="username"
|
|||
|
|
value={formData.username}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
required
|
|||
|
|
disabled={currentUser?.is_admin} // 管理员用户名不可修改
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">邮箱</label>
|
|||
|
|
<input
|
|||
|
|
type="email"
|
|||
|
|
className="form-control"
|
|||
|
|
name="email"
|
|||
|
|
value={formData.email}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">密码(不修改请留空)</label>
|
|||
|
|
<input
|
|||
|
|
type="password"
|
|||
|
|
className="form-control"
|
|||
|
|
name="password"
|
|||
|
|
value={formData.password}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
placeholder="不修改请留空"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3">
|
|||
|
|
<label className="form-label">每日领取限额</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
className="form-control"
|
|||
|
|
name="daily_quota"
|
|||
|
|
value={formData.daily_quota}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
min="1"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="mb-3 form-check">
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
className="form-check-input"
|
|||
|
|
name="is_active"
|
|||
|
|
checked={formData.is_active}
|
|||
|
|
onChange={handleChange}
|
|||
|
|
/>
|
|||
|
|
<label className="form-check-label">是否激活</label>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
<div className="modal-footer">
|
|||
|
|
<button className="btn-admin-outline-secondary" onClick={() => setShowEditModal(false)}>
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button className="btn-admin-outline-primary" onClick={submitEditUser}>
|
|||
|
|
保存
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 批量操作确认模态框 */}
|
|||
|
|
{showBatchModal && (
|
|||
|
|
<div className="modal-overlay" onClick={() => setShowBatchModal(false)}>
|
|||
|
|
<div className="admin-modal" onClick={(e) => e.stopPropagation()}>
|
|||
|
|
<div className="modal-header">
|
|||
|
|
<h5 className="modal-title">确认批量操作</h5>
|
|||
|
|
<button className="modal-close" onClick={() => setShowBatchModal(false)}>×</button>
|
|||
|
|
</div>
|
|||
|
|
<div className="modal-body">
|
|||
|
|
{batchAction === 'delete' && (
|
|||
|
|
<p>确定要删除选中的 {selectedUsers.length} 个用户吗?此操作不可恢复。</p>
|
|||
|
|
)}
|
|||
|
|
{batchAction === 'activate' && (
|
|||
|
|
<p>确定要激活选中的 {selectedUsers.length} 个用户吗?</p>
|
|||
|
|
)}
|
|||
|
|
{batchAction === 'deactivate' && (
|
|||
|
|
<p>确定要禁用选中的 {selectedUsers.length} 个用户吗?</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="modal-footer">
|
|||
|
|
<button className="btn-admin-outline-secondary" onClick={() => setShowBatchModal(false)}>
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button className="btn-admin-outline-primary" onClick={executeBatchAction}>
|
|||
|
|
确认
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default UserManagement;
|