mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-07 23:08:15 +08:00
[dev]permissions page
This commit is contained in:
parent
1ba460b4cf
commit
cc0f56a5d4
12
src/App.jsx
12
src/App.jsx
@ -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连接
|
||||
|
@ -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>}
|
||||
|
@ -173,6 +173,17 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 所有用户都可以修改为admin或private类型,无需额外检查
|
||||
if (newType !== 'admin' && newType !== 'private' && currentUser.role === 'member') {
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: '您只能将知识库修改为公共(admin)或私有(private)类型',
|
||||
type: 'warning',
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAdmin && !validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 || '';
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -1,120 +1,93 @@
|
||||
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) {
|
||||
// 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);
|
||||
} else {
|
||||
// Otherwise fetch the detailed permissions
|
||||
fetchUserPermissions();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
// 处理权限变更
|
||||
const handlePermissionChange = (knowledgeBaseId, permissionType) => {
|
||||
setUpdatedPermissions({
|
||||
...updatedPermissions,
|
||||
[knowledgeBaseId]: permissionType,
|
||||
});
|
||||
const fetchUserPermissions = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await get(`/api/users/${user.user_info.id}/permissions`);
|
||||
setUserPermissions(response.permissions || []);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError('获取用户权限详情失败: ' + (err.message || '未知错误'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理保存
|
||||
const handleSave = () => {
|
||||
onSave(user.user.id, updatedPermissions);
|
||||
const handlePermissionChange = (knowledgeBaseId, newPermissionType) => {
|
||||
setUpdatedPermissions((prev) => ({
|
||||
...prev,
|
||||
[knowledgeBaseId]: newPermissionType,
|
||||
}));
|
||||
};
|
||||
|
||||
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('权限更新成功');
|
||||
// Refresh permissions list
|
||||
fetchUserPermissions();
|
||||
// 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 +109,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 +120,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 +161,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 +186,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 +227,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 +257,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 +294,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>
|
||||
|
@ -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>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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 || '获取权限申请列表失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user