imetting/frontend/src/pages/admin/UserManagement.jsx

350 lines
12 KiB
React
Raw Normal View History

import React, { useState, useEffect } from 'react';
import apiClient from '../../utils/apiClient';
import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
2026-01-21 07:21:17 +00:00
import { Plus, Edit, Trash2, KeyRound, User, Mail, Shield, Search, X } from 'lucide-react';
import ConfirmDialog from '../../components/ConfirmDialog';
import FormModal from '../../components/FormModal';
import Toast from '../../components/Toast';
import './UserManagement.css';
const UserManagement = () => {
const [users, setUsers] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [showUserModal, setShowUserModal] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [currentUser, setCurrentUser] = useState(null);
const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null);
const [resetConfirmInfo, setResetConfirmInfo] = useState(null);
const [roles, setRoles] = useState([]);
const [toasts, setToasts] = useState([]);
const [searchText, setSearchText] = useState(''); // 搜索文本
// Toast helper functions
const showToast = (message, type = 'info') => {
const id = Date.now();
setToasts(prev => [...prev, { id, message, type }]);
};
const removeToast = (id) => {
setToasts(prev => prev.filter(toast => toast.id !== id));
};
useEffect(() => {
fetchUsers();
fetchRoles();
}, [page, pageSize, searchText]);
const fetchUsers = async () => {
setLoading(true);
try {
let url = `${API_ENDPOINTS.USERS.LIST}?page=${page}&size=${pageSize}`;
if (searchText) {
url += `&search=${encodeURIComponent(searchText)}`;
}
const response = await apiClient.get(buildApiUrl(url));
setUsers(response.data.users);
setTotal(response.data.total);
} catch (err) {
setError('无法加载用户列表');
} finally {
setLoading(false);
}
};
const fetchRoles = async () => {
try {
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.USERS.ROLES));
setRoles(response.data);
} catch (err) {
console.error('Error fetching roles:', err);
// 如果无法获取角色列表,使用默认值
setRoles([
{ role_id: 1, role_name: '平台管理员' },
{ role_id: 2, role_name: '普通用户' }
]);
}
};
const handleAddUser = async (e) => {
e.preventDefault();
try {
await apiClient.post(buildApiUrl(API_ENDPOINTS.USERS.CREATE), currentUser);
handleCloseModal();
setError('');
fetchUsers();
showToast('用户添加成功', 'success');
} catch (err) {
console.error('Error adding user:', err);
setError(err.response?.data?.message || '新增用户失败');
}
};
const handleUpdateUser = async (e) => {
e.preventDefault();
try {
const updateData = {
caption: currentUser.caption,
email: currentUser.email,
role_id: currentUser.role_id
};
if (currentUser.username && currentUser.username.trim()) {
updateData.username = currentUser.username;
}
await apiClient.put(buildApiUrl(API_ENDPOINTS.USERS.UPDATE(currentUser.user_id)), updateData);
handleCloseModal();
setError('');
fetchUsers();
showToast('用户修改成功', 'success');
} catch (err) {
console.error('Error updating user:', err);
setError(err.response?.data?.message || '修改用户失败');
}
};
const handleDeleteUser = async () => {
try {
await apiClient.delete(buildApiUrl(API_ENDPOINTS.USERS.DELETE(deleteConfirmInfo.user_id)));
setDeleteConfirmInfo(null);
showToast('用户删除成功', 'success');
fetchUsers();
} catch (err) {
console.error('Error deleting user:', err);
showToast('删除用户失败,请重试', 'error');
setDeleteConfirmInfo(null);
}
};
const handleResetPassword = async () => {
try {
await apiClient.post(buildApiUrl(API_ENDPOINTS.USERS.RESET_PASSWORD(resetConfirmInfo.user_id)));
setResetConfirmInfo(null);
showToast('密码重置成功', 'success');
} catch (err) {
console.error('Error resetting password:', err);
showToast('重置密码失败,请重试', 'error');
setResetConfirmInfo(null);
}
};
const handleOpenModal = (user = null) => {
if (user) {
setIsEditing(true);
setCurrentUser({ ...user });
} else {
setIsEditing(false);
setCurrentUser({ username: '', caption: '', email: '', role_id: 2 });
}
setError('');
setShowUserModal(true);
};
const handleCloseModal = () => {
setShowUserModal(false);
setCurrentUser(null);
setError('');
};
const handleSave = async () => {
if (isEditing) {
await handleUpdateUser({ preventDefault: () => {} });
} else {
await handleAddUser({ preventDefault: () => {} });
}
};
const handleInputChange = (field, value) => {
setCurrentUser(prev => ({ ...prev, [field]: value }));
};
const openDeleteConfirm = (user) => {
setDeleteConfirmInfo({ user_id: user.user_id, caption: user.caption });
};
const openResetConfirm = (user) => {
setResetConfirmInfo({ user_id: user.user_id, caption: user.caption });
};
return (
<div className="user-management">
<div className="toolbar">
<h2>用户列表</h2>
<div className="toolbar-actions">
2026-01-21 07:21:17 +00:00
<div className="search-box">
<Search size={18} />
<input
type="text"
placeholder="搜索用户名或姓名..."
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
setPage(1); // 重置到第一页
}}
/>
{searchText && (
<button className="clear-search" onClick={() => setSearchText('')}>
<X size={16} />
</button>
)}
</div>
<button className="btn btn-primary" onClick={() => handleOpenModal()}><Plus size={16} /> 新增用户</button>
</div>
</div>
{loading && <p>加载中...</p>}
{error && <p className="error-message">{error}</p>}
{!loading && !error && (
<>
<table className="users-table">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>姓名</th>
<th>邮箱</th>
<th>角色</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.user_id}>
<td>{user.user_id}</td>
<td>{user.username}</td>
<td>{user.caption}</td>
<td>{user.email}</td>
<td>{user.role_name}</td>
<td>{new Date(user.created_at).toLocaleString()}</td>
<td className="action-cell">
<button className="action-btn" onClick={() => handleOpenModal(user)} title="修改"><Edit size={16} />修改</button>
<button className="action-btn btn-danger" onClick={() => openDeleteConfirm(user)} title="删除"><Trash2 size={16} />删除</button>
<button className="action-btn btn-warning" onClick={() => openResetConfirm(user)} title="重置密码"><KeyRound size={16} />重置</button>
</td>
</tr>
))}
</tbody>
</table>
<div className="pagination">
<span>总计 {total} </span>
<div>
<button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1}>上一页</button>
<span> {page} / {Math.ceil(total / pageSize)}</span>
<button onClick={() => setPage(p => p + 1)} disabled={page * pageSize >= total}>下一页</button>
</div>
</div>
</>
)}
{/* 用户表单模态框 */}
<FormModal
isOpen={showUserModal}
onClose={handleCloseModal}
title={isEditing ? '编辑用户' : '新增用户'}
size="medium"
actions={
<>
<button type="button" className="btn btn-secondary" onClick={handleCloseModal}>
取消
</button>
<button type="button" className="btn btn-primary" onClick={handleSave}>
{isEditing ? '确认修改' : '确认新增'}
</button>
</>
}
>
{error && <div className="error-message">{error}</div>}
{currentUser && (
<>
<div className="form-group">
<label><User size={16} /> 用户名</label>
<input
type="text"
value={currentUser.username}
onChange={(e) => handleInputChange('username', e.target.value)}
placeholder="请输入用户名"
required
/>
</div>
<div className="form-group">
<label><User size={16} /> 姓名</label>
<input
type="text"
value={currentUser.caption}
onChange={(e) => handleInputChange('caption', e.target.value)}
placeholder="请输入姓名"
required
/>
</div>
<div className="form-group">
<label><Mail size={16} /> 邮箱</label>
<input
type="email"
value={currentUser.email}
onChange={(e) => handleInputChange('email', e.target.value)}
placeholder="请输入邮箱"
required
/>
</div>
<div className="form-group">
<label><Shield size={16} /> 角色</label>
<select
value={currentUser.role_id}
onChange={(e) => handleInputChange('role_id', parseInt(e.target.value))}
>
{roles.map(role => (
<option key={role.role_id} value={role.role_id}>{role.role_name}</option>
))}
</select>
</div>
{!isEditing && (
<div className="info-note">
<p>新用户的默认密码为系统配置的默认密码用户可登录后自行修改</p>
</div>
)}
</>
)}
</FormModal>
{/* 删除用户确认对话框 */}
<ConfirmDialog
isOpen={!!deleteConfirmInfo}
onClose={() => setDeleteConfirmInfo(null)}
onConfirm={handleDeleteUser}
title="删除用户"
message={`确定要删除用户"${deleteConfirmInfo?.caption}"吗?此操作无法撤销。`}
confirmText="确定删除"
cancelText="取消"
type="danger"
/>
{/* 重置密码确认对话框 */}
<ConfirmDialog
isOpen={!!resetConfirmInfo}
onClose={() => setResetConfirmInfo(null)}
onConfirm={handleResetPassword}
title="重置密码"
message={`确定要重置用户"${resetConfirmInfo?.caption}"的密码吗?重置后密码将恢复为系统默认密码。`}
confirmText="确定重置"
cancelText="取消"
type="warning"
/>
{/* Toast notifications */}
{toasts.map(toast => (
<Toast
key={toast.id}
message={toast.message}
type={toast.type}
onClose={() => removeToast(toast.id)}
/>
))}
</div>
);
};
export default UserManagement;