import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { Card, Table, Button, Space, Modal, Form, Input, Select, message, Tag, Popconfirm, Avatar, Tooltip, Badge, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined, ReloadOutlined, LockOutlined, CameraOutlined, CheckOutlined, CloseOutlined, } from '@ant-design/icons'; import { userAPI, roleAPI } from '../api'; import CloseButton from '../components/CloseButton'; const { Option } = Select; const UserManagement = () => { const [users, setUsers] = useState([]); const [roles, setRoles] = useState([]); const [loading, setLoading] = useState(false); const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 }); const [modalVisible, setModalVisible] = useState(false); const [passwordModalVisible, setPasswordModalVisible] = useState(false); const [avatarModalVisible, setAvatarModalVisible] = useState(false); const [editingUser, setEditingUser] = useState(null); const [passwordUser, setPasswordUser] = useState(null); const [avatarUser, setAvatarUser] = useState(null); const [uploadLoading, setUploadLoading] = useState(false); const [activeTab, setActiveTab] = useState('all'); const [form] = Form.useForm(); const [passwordForm] = Form.useForm(); const fileInputRef = useRef(null); useEffect(() => { fetchUsers(); fetchRoles(); }, [pagination.current]); const fetchUsers = useCallback(async () => { setLoading(true); try { const params = { page: pagination.current, pageSize: pagination.pageSize, }; if (activeTab !== 'all') { params.status = activeTab; } const response = await userAPI.list(params); if (response.success) { setUsers(response.data.users); setPagination(prev => ({ ...prev, total: response.data.total })); } } catch (error) { message.error('获取用户列表失败'); } finally { setLoading(false); } }, [pagination.current, pagination.pageSize, activeTab]); const fetchRoles = useCallback(async () => { try { const response = await roleAPI.all(); if (response.success) { setRoles(response.data); } else { message.error('获取角色列表失败: ' + (response.message || '未知错误')); } } catch (error) { console.error('获取角色列表失败:', error); message.error('获取角色列表失败,请检查网络连接'); } }, []); const handleAdd = useCallback(() => { setEditingUser(null); form.resetFields(); setModalVisible(true); }, []); const handleEdit = useCallback(user => { setEditingUser(user); form.setFieldsValue({ username: user.username, email: user.email, phone: user.phone, realName: user.realName, status: user.status, roleIds: user.roles?.map(r => r.roleId) || [], }); setModalVisible(true); }, []); const handleResetPassword = useCallback(user => { setPasswordUser(user); passwordForm.resetFields(); setPasswordModalVisible(true); }, []); const handleAvatarClick = useCallback(user => { setAvatarUser(user); setAvatarModalVisible(true); }, []); const handleAvatarUpload = useCallback( async e => { const file = e.target.files[0]; if (!file) return; if (!file.type.match(/image\/(jpeg|png|gif|webp)/)) { message.error('只支持 JPG、PNG、GIF 和 WebP 格式的图片'); return; } if (file.size > 5 * 1024 * 1024) { message.error('图片大小不能超过 5MB'); return; } setUploadLoading(true); try { const response = await userAPI.uploadAvatar(avatarUser.userId, file); if (response.success) { message.success('头像上传成功'); fetchUsers(); setAvatarModalVisible(false); } else { message.error(response.message || '上传失败'); } } catch (error) { message.error('上传失败'); } finally { setUploadLoading(false); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }, [avatarUser, fetchUsers] ); const handleAvatarDelete = useCallback(async () => { try { const response = await userAPI.deleteAvatar(avatarUser.userId); if (response.success) { message.success('头像已删除'); fetchUsers(); setAvatarModalVisible(false); } else { message.error(response.message || '删除失败'); } } catch (error) { message.error('删除失败'); } }, [avatarUser, fetchUsers]); const handleDelete = useCallback( async userId => { Modal.confirm({ title: '确认删除', content: '确定要删除这个用户吗?此操作不可恢复!', okText: '删除', okType: 'danger', cancelText: '取消', onOk: async () => { try { const response = await userAPI.delete(userId); if (response.success) { message.success('删除成功'); fetchUsers(); } else { message.error(response.message || '删除失败'); } } catch (error) { message.error('删除失败'); } }, }); }, [fetchUsers] ); const handleLockUnlock = useCallback( async record => { try { const newStatus = record.status === 'locked' ? 'active' : 'locked'; const response = await userAPI.update(record.userId, { status: newStatus, }); if (response.success) { message.success(record.status === 'locked' ? '解锁成功' : '锁定成功'); fetchUsers(); } else { message.error(response.message || '操作失败'); } } catch (error) { message.error('操作失败'); } }, [fetchUsers] ); const handleSubmit = useCallback( async values => { try { let response; if (editingUser) { response = await userAPI.update(editingUser.userId, values); } else { response = await userAPI.create(values); } if (response.success) { message.success(editingUser ? '更新成功' : '创建成功'); setModalVisible(false); fetchUsers(); } else { message.error(response.message || '操作失败'); } } catch (error) { message.error('操作失败'); } }, [editingUser, fetchUsers] ); const handleResetPasswordSubmit = useCallback( async values => { try { const response = await userAPI.resetPassword(passwordUser.userId, values); if (response.success) { message.success('密码重置成功'); setPasswordModalVisible(false); } else { message.error(response.message || '重置失败'); } } catch (error) { message.error('重置失败'); } }, [passwordUser] ); const getStatusColor = status => { const colors = { active: 'green', inactive: 'red', locked: 'orange', pending: 'blue', }; return colors[status] || 'default'; }; const getStatusText = status => { const texts = { active: '正常', inactive: '禁用', locked: '锁定', pending: '待审核', }; return texts[status] || status; }; const handleApprove = useCallback( async userId => { try { const response = await userAPI.approve(userId); if (response.success) { message.success('审核通过'); fetchUsers(); } else { message.error(response.message || '审核失败'); } } catch (error) { message.error('审核失败'); } }, [fetchUsers] ); const handleReject = useCallback( async userId => { try { const response = await userAPI.reject(userId); if (response.success) { message.success('已拒绝该用户的注册申请'); fetchUsers(); } else { message.error(response.message || '操作失败'); } } catch (error) { message.error('操作失败'); } }, [fetchUsers] ); const getAvatarUrl = user => { if (!user?.avatar) return null; return user.avatar; }; const tableColumns = useMemo( () => [ { title: '头像', key: 'avatar', width: 80, render: (_, record) => ( } src={getAvatarUrl(record)} style={{ backgroundColor: record.avatar ? 'transparent' : '#1890ff', cursor: 'pointer', }} onClick={() => handleAvatarClick(record)} /> ), }, { title: '用户名', key: 'username', width: 150, render: (_, record) => (
{record.realName || record.username}
@{record.username}
), }, { title: '邮箱', dataIndex: 'email', key: 'email', width: 200, render: email => email || '-', }, { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130, render: phone => phone || '-', }, { title: '角色', key: 'roles', render: (_, record) => ( {record.roles?.map(role => ( {role.roleName} )) || '-'} ), }, { title: '状态', dataIndex: 'status', key: 'status', render: status => {getStatusText(status)}, }, { title: '最后登录', key: 'lastLogin', render: (_, record) => (
{record.lastLoginTime ? new Date(record.lastLoginTime).toLocaleString() : '从未登录'}
{record.lastLoginIp || '-'}
), }, { title: '操作', key: 'action', render: (_, record) => ( {record.status === 'pending' ? ( <> handleApprove(record.userId)} okText="通过" cancelText="取消" > { setActiveTab(key); setPagination(prev => ({ ...prev, current: 1 })); }} > `共 ${total} 条记录`, }} onChange={newPagination => { setPagination(prev => ({ ...prev, ...newPagination })); }} rowClassName={() => 'table-row'} /> {editingUser ? '编辑用户' : '添加用户'} } open={modalVisible} closeIcon={} onCancel={() => setModalVisible(false)} footer={null} width={500} destroyOnHidden styles={{ body: { padding: '24px' }, header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' }, }} style={{ borderRadius: '16px', overflow: 'hidden' }} >
{!editingUser && ( )} {editingUser && ( )}
{`重置密码 - ${passwordUser?.username}`} } open={passwordModalVisible} closeIcon={} onCancel={() => setPasswordModalVisible(false)} footer={null} width={400} destroyOnHidden styles={{ body: { padding: '24px' }, header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' }, }} style={{ borderRadius: '16px', overflow: 'hidden' }} >
{`设置头像 - ${avatarUser?.username}`} } open={avatarModalVisible} closeIcon={} onCancel={() => setAvatarModalVisible(false)} footer={null} width={400} destroyOnHidden styles={{ body: { padding: '24px' }, header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' }, }} style={{ borderRadius: '16px', overflow: 'hidden' }} >
} src={getAvatarUrl(avatarUser)} style={{ backgroundColor: avatarUser?.avatar ? 'transparent' : '#1890ff', border: '1px solid #f0f0f0', cursor: 'pointer', }} />
{avatarUser?.avatar && ( )}
支持 JPG、PNG、GIF、WebP 格式,大小不超过 5MB
); }; export default UserManagement;