Compare commits

..

2 Commits

Author SHA1 Message Date
56c4878065 [dev]update permissions 2025-03-24 21:50:15 -04:00
cc0f56a5d4 [dev]permissions page 2025-03-24 21:47:30 -04:00
11 changed files with 336 additions and 295 deletions

View File

@ -25,14 +25,14 @@ function App() {
// WebSocket
if (user && !isConnected) {
initWebSocket()
.then(() => {
// initWebSocket()
// .then(() => {
// dispatch(setWebSocketConnected(true));
// console.log('WebSocket connection initialized');
})
.catch((error) => {
console.error('Failed to initialize WebSocket connection:', error);
});
// })
// .catch((error) => {
// console.error('Failed to initialize WebSocket connection:', error);
// });
}
// WebSocket

View File

@ -60,7 +60,7 @@ const CreateKnowledgeBaseModal = ({
const getAvailableTypes = () => {
if (isAdmin) {
return [
{ value: 'admin', label: 'Admin 级知识库' },
{ value: 'admin', label: '公共知识库' },
{ value: 'leader', label: 'Leader 级知识库' },
{ value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' },
@ -68,11 +68,15 @@ const CreateKnowledgeBaseModal = ({
];
} else if (isLeader) {
return [
{ value: 'admin', label: '公共知识库' },
{ value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' },
];
} else {
return [{ value: 'private', label: '私有知识库' }];
return [
{ value: 'admin', label: '公共知识库' },
{ value: 'private', label: '私有知识库' },
];
}
};
@ -169,7 +173,7 @@ const CreateKnowledgeBaseModal = ({
</div>
{!isAdmin && !isLeader && (
<small className='text-muted d-block mt-1'>
注意您当前只能创建私有知识库其他类型需要更高权限
您可以创建公共知识库所有人可访问或私有知识库仅自己可访问
</small>
)}
{formErrors.type && <div className='text-danger small mt-1'>{formErrors.type}</div>}

View File

@ -173,6 +173,17 @@ export default function SettingsTab({ knowledgeBase }) {
return;
}
// adminprivate
if (newType !== 'admin' && newType !== 'private' && currentUser.role === 'member') {
dispatch(
showNotification({
message: '您只能将知识库修改为公共(admin)或私有(private)类型',
type: 'warning',
})
);
return;
}
if (isAdmin && !validateForm()) {
return;
}

View File

@ -26,7 +26,7 @@ const KnowledgeBaseForm = ({
const getAvailableTypes = () => {
if (isAdmin) {
return [
{ value: 'admin', label: 'Admin 级知识库' },
{ value: 'admin', label: '公共知识库' },
{ value: 'leader', label: 'Leader 级知识库' },
{ value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' },
@ -34,11 +34,15 @@ const KnowledgeBaseForm = ({
];
} else if (isLeader) {
return [
{ value: 'admin', label: '公共知识库' },
{ value: 'member', label: 'Member 级知识库' },
{ value: 'private', label: '私有知识库' },
];
} else {
return [{ value: 'private', label: '私有知识库' }];
return [
{ value: 'admin', label: '公共知识库' },
{ value: 'private', label: '私有知识库' },
];
}
};
@ -106,7 +110,7 @@ const KnowledgeBaseForm = ({
value={type.value}
checked={formData.type === type.value}
onChange={onInputChange}
disabled={currentUser?.role === 'member'} //
disabled={false} //
/>
<label className='form-check-label' htmlFor={`type${type.value}`}>
{type.label}
@ -115,7 +119,7 @@ const KnowledgeBaseForm = ({
))}
</div>
{currentUser?.role === 'member' && (
<small className='text-muted d-block mt-1'>当前无权修改知识库类型</small>
<small className='text-muted d-block mt-1'>可以修改知识库类型为公共或私有</small>
)}
{formErrors.type && <div className='text-danger small mt-1'>{formErrors.type}</div>}
</div>

View File

@ -437,8 +437,8 @@ export default function KnowledgeBase() {
const isAdmin = currentUser?.role === 'admin';
const isLeader = currentUser?.role === 'leader';
//
let defaultType = 'private';
//
let defaultType = 'admin';
//
let department = currentUser?.department || '';

View File

@ -12,9 +12,6 @@ import './PendingRequests.css'; // 引入外部CSS文件
import SvgIcon from '../../../components/SvgIcon';
import RequestDetailSlideOver from './RequestDetailSlideOver';
//
const PAGE_SIZE = 5;
export default function PendingRequests() {
const dispatch = useDispatch();
const location = useLocation();
@ -25,14 +22,18 @@ export default function PendingRequests() {
const [selectedRequest, setSelectedRequest] = useState(null);
const [showSlideOver, setShowSlideOver] = useState(false);
// 使
const [pendingRequests, setPendingRequests] = useState([]);
const [fetchStatus, setFetchStatus] = useState('idle');
const [fetchError, setFetchError] = useState(null);
// Redux store
const {
results: pendingRequests,
status: fetchStatus,
error: fetchError,
page: currentPage,
page_size: pageSize,
total,
} = useSelector((state) => state.permissions.pending);
//
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
//
const totalPages = Math.ceil(total / pageSize) || 1;
// Redux store/
const {
@ -43,24 +44,8 @@ export default function PendingRequests() {
//
useEffect(() => {
const fetchData = async () => {
try {
setFetchStatus('loading');
const result = await dispatch(fetchPermissionsThunk());
if (result.payload) {
setPendingRequests(result.payload);
setTotalPages(Math.ceil(result.payload.length / PAGE_SIZE));
}
setFetchStatus('succeeded');
} catch (error) {
console.error('获取待处理申请列表失败:', error);
setFetchError(error.message || '获取待处理申请列表失败');
setFetchStatus('failed');
}
};
fetchData();
}, [dispatch]);
dispatch(fetchPermissionsThunk({ page: currentPage, page_size: pageSize }));
}, [dispatch, currentPage, pageSize]);
//
useEffect(() => {
@ -88,17 +73,8 @@ export default function PendingRequests() {
setShowSlideOver(false);
setSelectedRequest(null);
//
const updatedRequests = pendingRequests.filter((req) => req.id !== currentRequestId);
setPendingRequests(updatedRequests);
//
setTotalPages(Math.ceil(updatedRequests.length / PAGE_SIZE));
//
if (getCurrentPageData().length === 1 && currentPage > 1) {
setCurrentPage(currentPage - 1);
}
//
dispatch(fetchPermissionsThunk({ page: currentPage, page_size: pageSize }));
//
dispatch(resetOperationStatus());
@ -112,15 +88,7 @@ export default function PendingRequests() {
//
dispatch(resetOperationStatus());
}
}, [
approveRejectStatus,
approveRejectError,
dispatch,
isApproving,
currentRequestId,
pendingRequests,
currentPage,
]);
}, [approveRejectStatus, approveRejectError, dispatch, isApproving, currentPage, pageSize]);
//
const handleOpenResponseInput = (requestId, approving) => {
@ -183,16 +151,9 @@ export default function PendingRequests() {
}
};
//
const getCurrentPageData = () => {
const startIndex = (currentPage - 1) * PAGE_SIZE;
const endIndex = startIndex + PAGE_SIZE;
return Array.isArray(pendingRequests) ? pendingRequests.slice(startIndex, endIndex) : [];
};
//
const handlePageChange = (page) => {
setCurrentPage(page);
dispatch(fetchPermissionsThunk({ page, page_size: pageSize }));
};
//
@ -254,29 +215,27 @@ export default function PendingRequests() {
);
}
//
const currentPageData = getCurrentPageData();
//
return (
<>
<div className='d-flex justify-content-between align-items-center mb-3'>
<h5 className='mb-0'>待处理申请</h5>
<div className='badge bg-danger'>{pendingRequests.length}个待处理</div>
<div className='badge bg-danger'>{total}个待处理</div>
</div>
<div className='pending-requests-list'>
{currentPageData.map((request) => (
{pendingRequests.map((request) => (
<div key={request.id} className='pending-request-item' onClick={() => handleRowClick(request)}>
<div className='request-header'>
<div className='user-info'>
<h6 className='mb-0'>{request.applicant}</h6>
<h6 className='mb-0'>{request.applicant.name || request.applicant.username}</h6>
<small className='text-muted'>{request.applicant.department || '未分配部门'}</small>
</div>
<div className='request-date'>{new Date(request.created_at).toLocaleDateString()}</div>
</div>
<div className='request-content'>
<p className='mb-2'>申请访问{request.knowledge_base}</p>
<p className='mb-2'>申请访问{request.knowledge_base.name}</p>
{request.permissions.can_edit ? (
<span
@ -297,6 +256,14 @@ export default function PendingRequests() {
</span>
)
)}
{request.expires_at && (
<div className='mt-2'>
<small className='text-muted'>
到期时间: {new Date(request.expires_at).toLocaleDateString()}
</small>
</div>
)}
</div>
<div className='request-actions'>
<button
@ -346,7 +313,7 @@ export default function PendingRequests() {
{/* 回复输入弹窗 */}
{showResponseInput && (
<div className='modal fade show' style={{ display: 'block' }} tabIndex='-1'>
<div className='modal-dialog'>
<div className='modal-dialog' style={{ zIndex: 9999 }}>
<div className='modal-content'>
<div className='modal-header'>
<h5 className='modal-title'>{isApproving ? '批准' : '拒绝'}申请</h5>

View File

@ -13,9 +13,13 @@ export default function RequestDetailSlideOver({
}) {
if (!request) return null;
//
const applicant = request.applicant || request.title || '未知用户';
const applicantInitial = applicant.charAt(0);
//
const applicantName = request.applicant?.name || request.applicant?.username || '未知用户';
const applicantDept = request.applicant?.department || '未分配部门';
const applicantInitial = applicantName.charAt(0);
const knowledgeBaseName = request.knowledge_base?.name || '未知知识库';
const knowledgeBaseId = request.knowledge_base?.id || '';
const knowledgeBaseType = request.knowledge_base?.type || '';
return (
<>
@ -32,7 +36,8 @@ export default function RequestDetailSlideOver({
<div className='d-flex align-items-center mb-3'>
<div className='avatar-placeholder me-3 bg-dark'>{applicantInitial}</div>
<div>
<h5 className='mb-1'>{applicant}</h5>
<h5 className='mb-1'>{applicantName}</h5>
<div className='text-muted'>{applicantDept}</div>
</div>
</div>
</div>
@ -40,8 +45,16 @@ export default function RequestDetailSlideOver({
<div className='mb-4'>
<h6 className='text-muted mb-2'>知识库信息</h6>
<p className='mb-1'>
<strong>ID</strong> {request.knowledge_base || request.content || '未知知识库'}
<strong>名称</strong> {knowledgeBaseName}
</p>
<p className='mb-1'>
<strong>ID</strong> {knowledgeBaseId}
</p>
{knowledgeBaseType && (
<p className='mb-1'>
<strong>类型</strong> {knowledgeBaseType}
</p>
)}
</div>
<div className='mb-4'>
@ -49,12 +62,20 @@ export default function RequestDetailSlideOver({
<div className='d-flex flex-wrap gap-2 mb-3'>
{request.permissions?.can_read && !request.permissions?.can_edit && (
<span className='badge bg-warning-subtle text-warning d-flex align-items-center gap-1'>
<SvgIcon className={'eye'} />
只读
</span>
)}
{request.permissions?.can_edit && request.permissions?.can_delete && (
{request.permissions?.can_edit && (
<span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'>
完全访问
<SvgIcon className={'pencil'} />
编辑
</span>
)}
{request.permissions?.can_delete && (
<span className='badge bg-danger-subtle text-danger d-flex align-items-center gap-1'>
<SvgIcon className={'trash'} />
删除
</span>
)}
</div>
@ -72,51 +93,84 @@ export default function RequestDetailSlideOver({
</div>
)}
{request.status && (
<div className='mb-4'>
<h6 className='text-muted mb-2'>申请状态</h6>
<p className='mb-1'>
{request.status === 'pending' ? (
<span className='badge bg-warning'>待处理</span>
) : request.status === 'approved' ? (
<span className='badge bg-success'>已批准</span>
) : (
<span className='badge bg-danger'>已拒绝</span>
)}
</p>
</div>
)}
{request.approver && (
<div className='mb-4'>
<h6 className='text-muted mb-2'>审批人</h6>
<p className='mb-1'>
{request.approver.name || request.approver.username} ({request.approver.department || '未分配部门'})
</p>
</div>
)}
{request.response_message && (
<div className='mb-4'>
<h6 className='text-muted mb-2'>审批意见</h6>
<div className='p-3 bg-light rounded'>{request.response_message}</div>
</div>
)}
<div className='mb-4'>
<h6 className='text-muted mb-2'>申请理由</h6>
<div className='p-3 bg-light rounded'>
{request.reason || request.content || '无申请理由'}
{request.reason || '无申请理由'}
</div>
</div>
</div>
<div className='slide-over-footer'>
<button
className='btn btn-outline-danger me-2'
onClick={() => onReject(request.id)}
disabled={processingId === request.id && approveRejectStatus === 'loading'}
>
{processingId === request.id && approveRejectStatus === 'loading' && !isApproving ? (
<>
<span
className='spinner-border spinner-border-sm me-1'
role='status'
aria-hidden='true'
></span>
处理中...
</>
) : (
<>拒绝</>
)}
</button>
<button
className='btn btn-outline-success'
onClick={() => onApprove(request.id)}
disabled={processingId === request.id && approveRejectStatus === 'loading'}
>
{processingId === request.id && approveRejectStatus === 'loading' && isApproving ? (
<>
<span
className='spinner-border spinner-border-sm me-1'
role='status'
aria-hidden='true'
></span>
处理中...
</>
) : (
<>批准</>
)}
</button>
</div>
{request.status === 'pending' && (
<div className='slide-over-footer'>
<button
className='btn btn-outline-danger me-2'
onClick={() => onReject(request.id)}
disabled={processingId === request.id && approveRejectStatus === 'loading'}
>
{processingId === request.id && approveRejectStatus === 'loading' && !isApproving ? (
<>
<span
className='spinner-border spinner-border-sm me-1'
role='status'
aria-hidden='true'
></span>
处理中...
</>
) : (
<>拒绝</>
)}
</button>
<button
className='btn btn-outline-success'
onClick={() => onApprove(request.id)}
disabled={processingId === request.id && approveRejectStatus === 'loading'}
>
{processingId === request.id && approveRejectStatus === 'loading' && isApproving ? (
<>
<span
className='spinner-border spinner-border-sm me-1'
role='status'
aria-hidden='true'
></span>
处理中...
</>
) : (
<>批准</>
)}
</button>
</div>
)}
</div>
</div>
</>

View File

@ -1,120 +1,75 @@
import React, { useState, useEffect } from 'react';
import { get } from '../../../services/api';
//
const mockUserPermissions = [
{
knowledge_base: {
id: '1',
name: '达人直播数据报告',
department: '达人组',
},
permission: {
can_read: true,
can_edit: true,
can_admin: false,
},
last_access_time: '2024-03-10T14:30:00Z',
},
{
knowledge_base: {
id: '2',
name: '人力资源政策文件',
department: '人力资源组',
},
permission: {
can_read: true,
can_edit: false,
can_admin: false,
},
last_access_time: '2024-03-08T09:15:00Z',
},
{
knowledge_base: {
id: '3',
name: '市场分析报告',
department: '市场部',
},
permission: {
can_read: true,
can_edit: false,
can_admin: false,
},
last_access_time: null,
},
{
knowledge_base: {
id: '4',
name: '产品规划文档',
department: '产品部',
},
permission: {
can_read: true,
can_edit: true,
can_admin: true,
},
last_access_time: '2024-03-15T11:20:00Z',
},
];
import { get, post } from '../../../services/api';
export default function UserPermissionDetails({ user, onClose, onSave }) {
const [updatedPermissions, setUpdatedPermissions] = useState({});
const [userPermissions, setUserPermissions] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [updatedPermissions, setUpdatedPermissions] = useState({});
const [savingPermissions, setSavingPermissions] = useState(false);
const [successMessage, setSuccessMessage] = useState(null);
//
useEffect(() => {
const fetchUserPermissions = async () => {
try {
setLoading(true);
const response = await get(`/users/${user.user.id}/permissions/`);
if (response && response.code === 200) {
// API
const apiPermissions = response.data.permissions || [];
if (apiPermissions.length > 0) {
// 使API
setUserPermissions(apiPermissions);
console.log('使用API返回的用户权限数据');
} else {
// API使
console.log('API返回的用户权限数据为空使用模拟数据');
setUserPermissions(mockUserPermissions);
}
} else {
// API使
console.log('API请求失败使用模拟数据');
setUserPermissions(mockUserPermissions);
setError('获取用户权限详情失败,显示模拟数据');
}
} catch (error) {
console.error('获取用户权限详情失败:', error);
// API使
console.log('API请求出错使用模拟数据作为后备');
setUserPermissions(mockUserPermissions);
setError('获取用户权限详情失败,显示模拟数据');
} finally {
setLoading(false);
}
};
if (user) {
fetchUserPermissions();
// If we already have the permissions data in the user object, use it
if (user.permissions && Array.isArray(user.permissions)) {
setUserPermissions(user.permissions);
setLoading(false);
}
}, [user]);
//
const handlePermissionChange = (knowledgeBaseId, permissionType) => {
setUpdatedPermissions({
...updatedPermissions,
[knowledgeBaseId]: permissionType,
});
const handlePermissionChange = (knowledgeBaseId, newPermissionType) => {
setUpdatedPermissions((prev) => ({
...prev,
[knowledgeBaseId]: newPermissionType,
}));
};
//
const handleSave = () => {
onSave(user.user.id, updatedPermissions);
const handleSave = async () => {
setSavingPermissions(true);
setError(null);
setSuccessMessage(null);
try {
const permissionUpdates = Object.entries(updatedPermissions).map(
async ([knowledgeBaseId, permissionType]) => {
const permissions = {
can_read: permissionType !== 'none',
can_edit: ['edit', 'admin'].includes(permissionType),
can_delete: permissionType === 'admin',
};
const requestBody = {
user_id: user.user_info.id,
knowledge_base_id: knowledgeBaseId,
permissions: permissions,
// Optional expiration date - can be added if needed
// expires_at: "2025-12-31T23:59:59Z"
};
try {
const response = await post('/permissions/update_permission/', requestBody);
return response;
} catch (err) {
throw new Error(`更新知识库 ${knowledgeBaseId} 权限失败: ${err.message || '未知错误'}`);
}
}
);
await Promise.all(permissionUpdates);
setSuccessMessage('权限更新成功');
// Reset updated permissions
setUpdatedPermissions({});
// Notify parent component if needed
if (onSave) {
onSave(user.user_info.id);
}
} catch (err) {
setError(err.message || '更新权限时发生错误');
} finally {
setSavingPermissions(false);
}
};
//
@ -136,7 +91,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
//
const getPermissionType = (permission) => {
if (!permission) return 'none';
if (permission.can_admin) return 'admin';
if (permission.can_delete) return 'admin';
if (permission.can_edit) return 'edit';
if (permission.can_read) return 'read';
return 'none';
@ -147,10 +102,21 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
<div className='modal-dialog modal-lg modal-dialog-scrollable' style={{ zIndex: 9999 }}>
<div className='modal-content'>
<div className='modal-header'>
<h5 className='modal-title'>{user.user.name} 的权限详情</h5>
<h5 className='modal-title'>{user.user_info.name} 的权限详情</h5>
<button type='button' className='btn-close' onClick={onClose}></button>
</div>
<div className='modal-body'>
{successMessage && (
<div className='alert alert-success alert-dismissible fade show' role='alert'>
{successMessage}
<button
type='button'
className='btn-close'
onClick={() => setSuccessMessage(null)}
></button>
</div>
)}
{loading ? (
<div className='text-center py-4'>
<div className='spinner-border' role='status'>
@ -177,7 +143,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
</thead>
<tbody>
{userPermissions.map((item) => {
const currentPermissionType = getPermissionType(item.permission);
const currentPermissionType = getPermissionType(item.permissions);
const updatedPermissionType =
updatedPermissions[item.knowledge_base.id] ||
currentPermissionType;
@ -202,8 +168,8 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
</span>
</td>
<td>
{item.last_access_time
? new Date(item.last_access_time).toLocaleString()
{item.granted_at
? new Date(item.granted_at).toLocaleString()
: '从未访问'}
</td>
<td>
@ -243,13 +209,13 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
<th>知识库名称</th>
<th>所属部门</th>
<th>当前权限</th>
<th>最后访问时间</th>
<th>授权时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{userPermissions.map((item) => {
const currentPermissionType = getPermissionType(item.permission);
const currentPermissionType = getPermissionType(item.permissions);
const updatedPermissionType =
updatedPermissions[item.knowledge_base.id] || currentPermissionType;
@ -273,9 +239,9 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
</span>
</td>
<td>
{item.last_access_time
? new Date(item.last_access_time).toLocaleString()
: '从未访问'}
{item.granted_at
? new Date(item.granted_at).toLocaleString()
: '未记录'}
</td>
<td>
<select
@ -310,9 +276,20 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
type='button'
className='btn btn-dark'
onClick={handleSave}
disabled={loading || Object.keys(updatedPermissions).length === 0}
disabled={loading || savingPermissions || Object.keys(updatedPermissions).length === 0}
>
保存更改
{savingPermissions ? (
<>
<span
className='spinner-border spinner-border-sm me-2'
role='status'
aria-hidden='true'
></span>
保存中...
</>
) : (
'保存更改'
)}
</button>
</div>
</div>

View File

@ -36,8 +36,8 @@ export default function UserPermissions() {
//
const totalPages = Math.ceil(total / page_size);
const handleOpenDetailsModal = (user) => {
setSelectedUser(user);
const handleOpenDetailsModal = (data) => {
setSelectedUser(data);
setShowDetailsModal(true);
};
@ -46,11 +46,11 @@ export default function UserPermissions() {
setShowDetailsModal(false);
};
const handleSavePermissions = async (userId, updatedPermissions) => {
const handleSavePermissions = async (userId) => {
try {
await dispatch(updateUserPermissions({ userId, permissions: updatedPermissions })).unwrap();
// Permission updates are now handled directly in the UserPermissionDetails component
// Just refresh the users list to reflect the updated permissions
handleCloseDetailsModal();
//
dispatch(fetchAllUserPermissions({ page: currentPage, page_size: pageSize }));
} catch (error) {
console.error('更新权限失败:', error);
@ -82,10 +82,10 @@ export default function UserPermissions() {
return users.filter(
(user) =>
user.user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.user.department.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.user.role.toLowerCase().includes(searchTerm.toLowerCase())
user.user_info?.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.user_info?.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.user_info?.department.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.user_info?.role.toLowerCase().includes(searchTerm.toLowerCase())
);
};
@ -190,12 +190,13 @@ export default function UserPermissions() {
};
return (
<div className='card'>
<div className='card-header'>
<>
<div className='d-flex justify-content-between align-items-center mb-3'>
<h5 className='mb-0'>用户权限管理</h5>
</div>
<div className='card-body'>
<div className='mb-3 d-flex justify-content-between align-items-center'>
<>
{/* <div className='mb-3 d-flex justify-content-between align-items-center'>
<div className='search-bar' style={{ maxWidth: '300px' }}>
<div className='input-group'>
<span className='input-group-text bg-light border-end-0'>
@ -210,7 +211,7 @@ export default function UserPermissions() {
/>
</div>
</div>
</div>
</div> */}
{loading === 'loading' ? (
<div className='text-center my-5'>
@ -241,58 +242,70 @@ export default function UserPermissions() {
<tbody>
{filteredUsers.length > 0 ? (
filteredUsers.map((userPermission) => (
<tr key={userPermission.id}>
<td className='align-middle'>{userPermission.id}</td>
<td className='align-middle'>{userPermission.user.username}</td>
<td className='align-middle'>{userPermission.user.name}</td>
<td className='align-middle'>{userPermission.user.department}</td>
<tr key={userPermission.user_info?.id}>
<td className='align-middle'>{userPermission.user_info?.id}</td>
<td className='align-middle'>
{userPermission.user_info?.username || 'N/A'}
</td>
<td className='align-middle'>
{userPermission.user_info?.name || 'N/A'}
</td>
<td className='align-middle'>
{userPermission.user_info?.department || 'N/A'}
</td>
<td className='align-middle'>
<span
className={`badge ${
userPermission.user.role === 'admin'
userPermission.user_info?.role === 'admin'
? 'bg-danger-subtle text-danger'
: userPermission.user.role === 'leader'
: userPermission.user_info?.role === 'leader'
? 'bg-warning-subtle text-warning'
: 'bg-info-subtle text-info'
}`}
>
{userPermission.user.role === 'admin'
{userPermission.user_info?.role === 'admin'
? '管理员'
: userPermission.user.role === 'leader'
: userPermission.user_info?.role === 'leader'
? '组长'
: '成员'}
</span>
</td>
<td className='align-middle'>
{userPermission.user.permissions_count && (
<div className='d-flex flex-wrap gap-1'>
{userPermission.user.permissions_count.read > 0 && (
<span className='badge
bg-success-subtle text-success
d-flex align-items-center gap-1'>
完全访问: {userPermission.user.
permissions_count.read}
{userPermission.stats?.by_permission?.full_access > 0 && (
<span
className='badge
bg-success-subtle text-success
d-flex align-items-center gap-1'
>
完全访问:{' '}
{userPermission.stats?.by_permission?.full_access}
</span>
)}
{userPermission.user.permissions_count.edit > 0 && (
<span className='badge
bg-warning-subtle text-warning
d-flex align-items-center gap-1'>
只读访问: {userPermission.user.
permissions_count.edit}
{userPermission.stats?.by_permission?.read_only > 0 && (
<span
className='badge
bg-warning-subtle text-warning
d-flex align-items-center gap-1'
>
只读访问:{' '}
{userPermission.stats?.by_permission?.read_only}
</span>
)}
{userPermission.user.permissions_count.admin > 0 && (
<span className='badge
bg-dark-subtle d-flex
align-items-center gap-1'>
无访问权限: {userPermission.user.
permissions_count.admin}
{userPermission.stats?.by_permission?.read_write > 0 && (
<span
className='badge
bg-info-subtle text-info
d-flex align-items-center gap-1'
>
读写权限:{' '}
{userPermission.stats?.by_permission?.read_write}
</span>
)}
</div>
)}
</td>
</td>
<td className='align-middle'>
<button
className='btn btn-sm btn-outline-dark'
@ -305,7 +318,7 @@ export default function UserPermissions() {
))
) : (
<tr>
<td colSpan='5' className='text-center py-4'>
<td colSpan='7' className='text-center py-4'>
无匹配的用户记录
</td>
</tr>
@ -325,7 +338,7 @@ export default function UserPermissions() {
onSave={handleSavePermissions}
/>
)}
</div>
</div>
</>
</>
);
}

View File

@ -27,7 +27,10 @@ const initialState = {
error: null,
},
pending: {
items: [],
results: [], // 更改为results
total: 0, // 添加total
page: 1, // 添加page
page_size: 10, // 添加page_size
status: 'idle',
error: null,
},
@ -91,7 +94,10 @@ const permissionsSlice = createSlice({
})
.addCase(fetchPermissionsThunk.fulfilled, (state, action) => {
state.pending.status = 'succeeded';
state.pending.items = action.payload;
state.pending.results = action.payload.results || [];
state.pending.total = action.payload.total || 0;
state.pending.page = action.payload.page || 1;
state.pending.page_size = action.payload.page_size || 10;
})
.addCase(fetchPermissionsThunk.rejected, (state, action) => {
state.pending.status = 'failed';

View File

@ -5,17 +5,22 @@ import { showNotification } from '../notification.slice';
// 获取权限申请列表
export const fetchPermissionsThunk = createAsyncThunk(
'permissions/fetchPermissions',
async (_, { rejectWithValue }) => {
async (params = {}, { rejectWithValue }) => {
try {
const { data, message, code } = await get('/permissions/');
const response = await get('/permissions/', { params });
if (code === 200) {
return data.items || [];
if (response && response.code === 200) {
return {
results: response.data.results || [],
total: response.data.total || 0,
page: response.data.page || 1,
page_size: response.data.page_size || 10,
};
}
return rejectWithValue('获取权限申请列表失败');
} catch (error) {
console.error('获取权限申请列表失败:', error);
return rejectWithValue('获取权限申请列表失败');
return rejectWithValue(error.response?.data?.message || '获取权限申请列表失败');
}
}
);