KnowledgeBase_frontend/src/pages/Permissions/components/UserPermissions.jsx

472 lines
17 KiB
React
Raw Normal View History

2025-03-13 10:24:05 +08:00
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { get, put } from '../../../services/api';
import { showNotification } from '../../../store/notification.slice';
import UserPermissionDetails from './UserPermissionDetails';
import './UserPermissions.css';
import SvgIcon from '../../../components/SvgIcon';
// 模拟数据
const mockUsers = [
{
id: '1',
username: 'zhangsan',
name: '张三',
department: '达人组',
position: '达人对接',
permissions_count: {
read: 1,
edit: 0,
admin: 0,
},
},
{
id: '2',
username: 'lisi',
name: '李四',
department: '人力资源组',
position: 'HR',
permissions_count: {
read: 1,
edit: 0,
admin: 2,
},
},
{
id: '3',
username: 'wangwu',
name: '王五',
department: '市场部',
position: '市场专员',
permissions_count: {
read: 2,
edit: 1,
admin: 0,
},
},
{
id: '4',
username: 'zhaoliu',
name: '赵六',
department: '技术部',
position: '前端开发',
permissions_count: {
read: 3,
edit: 2,
admin: 1,
},
},
{
id: '5',
username: 'sunqi',
name: '孙七',
department: '产品部',
position: '产品经理',
permissions_count: {
read: 4,
edit: 2,
admin: 0,
},
},
{
id: '6',
username: 'zhouba',
name: '周八',
department: '设计部',
position: 'UI设计师',
permissions_count: {
read: 1,
edit: 1,
admin: 0,
},
},
{
id: '7',
username: 'wujiu',
name: '吴九',
department: '财务部',
position: '财务主管',
permissions_count: {
read: 2,
edit: 0,
admin: 3,
},
},
{
id: '8',
username: 'zhengshi',
name: '郑十',
department: '行政部',
position: '行政专员',
permissions_count: {
read: 1,
edit: 0,
admin: 0,
},
},
];
// 每页显示选项
const PAGE_SIZE_OPTIONS = [5, 10, 15, 20];
2025-03-13 10:24:05 +08:00
export default function UserPermissions() {
const dispatch = useDispatch();
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [selectedUser, setSelectedUser] = useState(null);
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
// 分页状态
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(5);
const [totalPages, setTotalPages] = useState(1);
2025-03-13 10:24:05 +08:00
// 获取用户列表
useEffect(() => {
const fetchUsers = async () => {
try {
setLoading(true);
const response = await get('/users/permissions/');
if (response && response.code === 200) {
// 只有当API返回的用户列表为空时才使用模拟数据
const apiUsers = response.data.users || [];
if (apiUsers.length > 0) {
setUsers(apiUsers);
} else {
console.log('API返回的用户列表为空使用模拟数据');
setUsers(mockUsers);
}
2025-03-13 10:24:05 +08:00
} else {
// API请求失败使用模拟数据
console.log('API请求失败使用模拟数据');
setUsers(mockUsers);
2025-03-13 10:24:05 +08:00
}
} catch (error) {
console.error('获取用户列表失败:', error);
setError('获取用户列表失败');
// API请求出错使用模拟数据作为后备
console.log('API请求出错使用模拟数据作为后备');
setUsers(mockUsers);
2025-03-13 10:24:05 +08:00
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
// 当用户列表或搜索词变化时,更新总页数
useEffect(() => {
const filteredUsers = getFilteredUsers();
setTotalPages(Math.ceil(filteredUsers.length / pageSize));
// 如果当前页超出了新的总页数,则重置为第一页
if (currentPage > Math.ceil(filteredUsers.length / pageSize)) {
setCurrentPage(1);
}
}, [users, searchTerm, pageSize]);
2025-03-13 10:24:05 +08:00
// 打开用户权限详情弹窗
const handleOpenDetailsModal = (user) => {
setSelectedUser(user);
setShowDetailsModal(true);
};
// 关闭用户权限详情弹窗
const handleCloseDetailsModal = () => {
setShowDetailsModal(false);
setSelectedUser(null);
};
// 保存用户权限更改
const handleSavePermissions = async (userId, updatedPermissions) => {
try {
const response = await put(`/users/${userId}/permissions/`, {
permissions: updatedPermissions,
});
if (response && response.code === 200) {
// 更新用户列表中的权限信息
setUsers(
users.map((user) => {
if (user.id === userId) {
return {
...user,
permissions: {
...user.permissions,
...response.data.permissions,
},
};
}
return user;
})
);
dispatch(
showNotification({
message: '权限更新成功',
type: 'success',
})
);
// 关闭弹窗
handleCloseDetailsModal();
} else {
dispatch(
showNotification({
message: '权限更新失败',
type: 'danger',
})
);
}
} catch (error) {
console.error('权限更新失败:', error);
dispatch(
showNotification({
message: '权限更新失败',
type: 'danger',
})
);
}
};
// 处理搜索输入变化
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
setCurrentPage(1); // 重置为第一页
};
// 处理页码变化
const handlePageChange = (page) => {
setCurrentPage(page);
};
// 处理每页显示数量变化
const handlePageSizeChange = (e) => {
const newPageSize = parseInt(e.target.value);
setPageSize(newPageSize);
setCurrentPage(1); // 重置为第一页
};
// 获取过滤后的用户列表
const getFilteredUsers = () => {
if (!searchTerm.trim()) return users;
return users.filter(
(user) =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.department.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.position.toLowerCase().includes(searchTerm.toLowerCase())
);
};
// 获取当前页的数据
const getCurrentPageData = () => {
const filteredUsers = getFilteredUsers();
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return filteredUsers.slice(startIndex, endIndex);
};
// 渲染分页控件
const renderPagination = () => {
if (totalPages <= 1) return null;
return (
<div className='d-flex justify-content-center align-items-center mt-4'>
<nav aria-label='用户权限分页'>
<ul className='pagination'>
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}>
<button
className='page-link'
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
上一页
</button>
</li>
<li className='page-item active'>
<span className='page-link'>
{currentPage} / {totalPages}
</span>
</li>
<li className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`}>
<button
className='page-link'
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
下一页
</button>
</li>
</ul>
</nav>
<div className='ms-3'>
<select
className='form-select form-select-sm'
value={pageSize}
onChange={handlePageSizeChange}
aria-label='每页显示数量'
>
{PAGE_SIZE_OPTIONS.map((size) => (
<option key={size} value={size}>
{size}/
</option>
))}
</select>
</div>
</div>
);
};
2025-03-13 10:24:05 +08:00
// 渲染加载状态
if (loading && users.length === 0) {
2025-03-13 10:24:05 +08:00
return (
<div className='text-center py-5'>
<div className='spinner-border' role='status'>
<span className='visually-hidden'>加载中...</span>
</div>
<p className='mt-3'>加载用户列表...</p>
</div>
);
}
// 渲染错误状态
if (error && users.length === 0) {
2025-03-13 10:24:05 +08:00
return (
<div className='alert alert-danger' role='alert'>
{error}
</div>
);
}
// 渲染空状态
if (users.length === 0) {
return (
<div className='alert alert-info' role='alert'>
暂无用户数据
</div>
);
}
// 获取当前页的数据
const currentPageData = getCurrentPageData();
// 获取过滤后的总用户数
const filteredUsersCount = getFilteredUsers().length;
2025-03-13 10:24:05 +08:00
// 渲染用户列表
return (
<>
<div className='d-flex justify-content-between align-items-center mb-3'>
<h5 className='mb-0'>用户权限管理</h5>
<div className='input-group' style={{ maxWidth: '250px' }}>
<input
type='text'
className='form-control'
placeholder='搜索用户...'
value={searchTerm}
onChange={handleSearchChange}
/>
<span className='input-group-text'>
<i className='bi bi-search'></i>
</span>
2025-03-13 10:24:05 +08:00
</div>
</div>
{filteredUsersCount > 0 ? (
<>
<div className='card'>
<div className='table-responsive'>
<table className='table table-hover mb-0'>
<thead className='table-light'>
<tr>
<th scope='col'>用户</th>
<th scope='col'>部门</th>
<th scope='col'>职位</th>
<th scope='col'>数据集权限</th>
<th scope='col' className='text-end'>
操作
</th>
2025-03-13 10:24:05 +08:00
</tr>
</thead>
<tbody>
{currentPageData.map((user) => (
<tr key={user.id}>
<td>
<div className='d-flex align-items-center'>
<div
className='bg-dark rounded-circle text-white d-flex align-items-center justify-content-center me-2'
style={{ width: '36px', height: '36px' }}
>
{user.name.charAt(0)}
</div>
<div>
<div className='fw-medium'>{user.name}</div>
<div className='text-muted small'>{user.username}</div>
</div>
</div>
</td>
<td className='align-middle'>{user.department}</td>
<td className='align-middle'>{user.position}</td>
<td className='align-middle'>
{user.permissions_count && (
<div className='d-flex flex-wrap gap-1'>
{user.permissions_count.read > 0 && (
<span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'>
完全访问: {user.permissions_count.read}
</span>
)}
{user.permissions_count.edit > 0 && (
<span className='badge bg-warning-subtle text-warning d-flex align-items-center gap-1'>
只读访问: {user.permissions_count.edit}
</span>
)}
{user.permissions_count.admin > 0 && (
<span className='badge bg-danger d-flex align-items-center gap-1'>
无访问权限: {user.permissions_count.admin}
</span>
)}
</div>
)}
</td>
<td className='text-end align-middle'>
<button
className='btn btn-outline-dark btn-sm'
onClick={() => handleOpenDetailsModal(user)}
>
修改权限
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
2025-03-13 10:24:05 +08:00
</div>
{/* 分页控件 */}
{renderPagination()}
</>
) : (
<div className='alert alert-info' role='alert'>
没有找到匹配的用户
2025-03-13 10:24:05 +08:00
</div>
)}
2025-03-13 10:24:05 +08:00
{/* 用户权限详情弹窗 */}
{showDetailsModal && selectedUser && (
<UserPermissionDetails
user={selectedUser}
onClose={handleCloseDetailsModal}
onSave={handleSavePermissions}
/>
)}
</>
);
}