mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-07 23:08:15 +08:00
[dev]add permission page
This commit is contained in:
parent
c9236cfff4
commit
6cf31165f9
@ -8,6 +8,7 @@ export default function HeaderWithNav() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
console.log('user', user);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
@ -22,6 +23,9 @@ export default function HeaderWithNav() {
|
||||
return location.pathname.startsWith(path);
|
||||
};
|
||||
|
||||
// 检查用户是否有管理权限(leader 或 admin)
|
||||
const hasManagePermission = user && (user.role === 'leader' || user.role === 'admin');
|
||||
|
||||
return (
|
||||
<header className=' navbar navbar-expand-lg p-0'>
|
||||
<nav className='navbar navbar-expand-lg border-bottom p-3 w-100'>
|
||||
@ -44,7 +48,9 @@ export default function HeaderWithNav() {
|
||||
<ul className='navbar-nav me-auto mb-lg-0'>
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={`nav-link ${isActive('/') && !isActive('/chat') ? 'active' : ''}`}
|
||||
className={`nav-link ${
|
||||
isActive('/') && !isActive('/chat') && !isActive('/permissions') ? 'active' : ''
|
||||
}`}
|
||||
aria-current='page'
|
||||
to='/'
|
||||
>
|
||||
@ -56,6 +62,16 @@ export default function HeaderWithNav() {
|
||||
Chat
|
||||
</Link>
|
||||
</li>
|
||||
{hasManagePermission && (
|
||||
<li className='nav-item'>
|
||||
<Link
|
||||
className={`nav-link ${isActive('/permissions') ? 'active' : ''}`}
|
||||
to='/permissions'
|
||||
>
|
||||
权限管理
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
{!!user ? (
|
||||
<div className='flex-shrink-0 dropdown'>
|
||||
|
44
src/pages/Permissions/PermissionsPage.jsx
Normal file
44
src/pages/Permissions/PermissionsPage.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PendingRequests from './components/PendingRequests';
|
||||
import UserPermissions from './components/UserPermissions';
|
||||
|
||||
export default function PermissionsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const [activeTab, setActiveTab] = useState('pending');
|
||||
|
||||
// 检查用户是否有管理权限(leader 或 admin)
|
||||
useEffect(() => {
|
||||
if (!user || (user.role !== 'leader' && user.role !== 'admin')) {
|
||||
navigate('/');
|
||||
}
|
||||
}, [user, navigate]);
|
||||
|
||||
return (
|
||||
<div className='permissions-container container py-4'>
|
||||
|
||||
<ul className='nav nav-tabs mb-4'>
|
||||
<li className='nav-item'>
|
||||
<button
|
||||
className={`nav-link ${activeTab === 'pending' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('pending')}
|
||||
>
|
||||
待处理申请
|
||||
</button>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<button
|
||||
className={`nav-link ${activeTab === 'users' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('users')}
|
||||
>
|
||||
用户权限管理
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className='tab-content'>{activeTab === 'pending' ? <PendingRequests /> : <UserPermissions />}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
37
src/pages/Permissions/components/PendingRequests.css
Normal file
37
src/pages/Permissions/components/PendingRequests.css
Normal file
@ -0,0 +1,37 @@
|
||||
.permission-requests {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.permission-card {
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 8px;
|
||||
background-color: #343a40;
|
||||
}
|
||||
|
||||
.permission-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.custom-badge {
|
||||
padding: 0.35em 0.65em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.badge.bg-info {
|
||||
background-color: #0dcaf0 !important;
|
||||
}
|
||||
|
||||
.badge.bg-success {
|
||||
background-color: #198754 !important;
|
||||
}
|
||||
|
||||
.badge.bg-danger {
|
||||
background-color: #dc3545 !important;
|
||||
}
|
||||
|
||||
.badge.bg-secondary {
|
||||
background-color: #6c757d !important;
|
||||
}
|
292
src/pages/Permissions/components/PendingRequests.jsx
Normal file
292
src/pages/Permissions/components/PendingRequests.jsx
Normal file
@ -0,0 +1,292 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { showNotification } from '../../../store/notification.slice';
|
||||
import {
|
||||
fetchPermissionsThunk,
|
||||
approvePermissionThunk,
|
||||
rejectPermissionThunk,
|
||||
} from '../../../store/permissions/permissions.thunks';
|
||||
import { resetApproveRejectStatus } from '../../../store/permissions/permissions.slice';
|
||||
import './PendingRequests.css'; // 引入外部CSS文件
|
||||
|
||||
export default function PendingRequests() {
|
||||
const dispatch = useDispatch();
|
||||
const [responseMessage, setResponseMessage] = useState('');
|
||||
const [showResponseInput, setShowResponseInput] = useState(false);
|
||||
const [currentRequestId, setCurrentRequestId] = useState(null);
|
||||
const [isApproving, setIsApproving] = useState(false);
|
||||
|
||||
// 从Redux store获取权限申请列表和状态
|
||||
const {
|
||||
items: pendingRequests,
|
||||
status: fetchStatus,
|
||||
error: fetchError,
|
||||
} = useSelector((state) => state.permissions.permissions);
|
||||
|
||||
const {
|
||||
status: approveRejectStatus,
|
||||
error: approveRejectError,
|
||||
currentId: processingId,
|
||||
} = useSelector((state) => state.permissions.approveReject);
|
||||
|
||||
// 获取待处理申请列表
|
||||
useEffect(() => {
|
||||
dispatch(fetchPermissionsThunk());
|
||||
}, [dispatch]);
|
||||
|
||||
// 监听批准/拒绝操作的状态变化
|
||||
useEffect(() => {
|
||||
if (approveRejectStatus === 'succeeded') {
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: isApproving ? '已批准申请' : '已拒绝申请',
|
||||
type: 'success',
|
||||
})
|
||||
);
|
||||
setShowResponseInput(false);
|
||||
setCurrentRequestId(null);
|
||||
setResponseMessage('');
|
||||
// 重置状态
|
||||
dispatch(resetApproveRejectStatus());
|
||||
} else if (approveRejectStatus === 'failed') {
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: approveRejectError || '处理申请失败',
|
||||
type: 'danger',
|
||||
})
|
||||
);
|
||||
// 重置状态
|
||||
dispatch(resetApproveRejectStatus());
|
||||
}
|
||||
}, [approveRejectStatus, approveRejectError, dispatch, isApproving]);
|
||||
|
||||
// 打开回复输入框
|
||||
const handleOpenResponseInput = (requestId, approving) => {
|
||||
setCurrentRequestId(requestId);
|
||||
setIsApproving(approving);
|
||||
setShowResponseInput(true);
|
||||
};
|
||||
|
||||
// 关闭回复输入框
|
||||
const handleCloseResponseInput = () => {
|
||||
setShowResponseInput(false);
|
||||
setCurrentRequestId(null);
|
||||
setResponseMessage('');
|
||||
};
|
||||
|
||||
// 处理申请(批准或拒绝)
|
||||
const handleProcessRequest = () => {
|
||||
if (!currentRequestId) return;
|
||||
|
||||
const params = {
|
||||
id: currentRequestId,
|
||||
responseMessage,
|
||||
};
|
||||
|
||||
if (isApproving) {
|
||||
dispatch(approvePermissionThunk(params));
|
||||
} else {
|
||||
dispatch(rejectPermissionThunk(params));
|
||||
}
|
||||
};
|
||||
|
||||
// 获取权限类型文本和样式
|
||||
const getPermissionTypeText = (permissions) => {
|
||||
if (permissions.can_read && !permissions.can_edit) {
|
||||
return '只读访问';
|
||||
} else if (permissions.can_edit) {
|
||||
return '完全访问';
|
||||
} else {
|
||||
return '未知权限';
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染加载状态
|
||||
if (fetchStatus === 'loading') {
|
||||
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 (fetchStatus === 'failed') {
|
||||
return (
|
||||
<div className='alert alert-danger' role='alert'>
|
||||
{fetchError || '获取待处理申请失败'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染空状态
|
||||
if (pendingRequests.length === 0) {
|
||||
return (
|
||||
<div className='alert alert-info' role='alert'>
|
||||
暂无待处理的权限申请
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染申请列表
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex justify-content-between align-items-center mb-4'>
|
||||
{/* <h5 className='mb-0'>待处理申请</h5> */}
|
||||
<div className='bg-danger rounded-pill text-white py-1 px-2'>{pendingRequests.length}个待处理</div>
|
||||
</div>
|
||||
|
||||
<div className='permission-requests'>
|
||||
{pendingRequests.map((request) => (
|
||||
<div key={request.id} className='permission-card card mb-3 border-0 shadow-sm bg-light text-dark'>
|
||||
<div className='card-body p-4 d-flex flex-column gap-3'>
|
||||
<div className='d-flex justify-content-between'>
|
||||
<div>
|
||||
<h5 className='mb-1'>{request.applicant.name || request.applicant}</h5>
|
||||
<p className='text-dark-50 mb-0'>{request.applicant.department || '无归属部门'}</p>
|
||||
</div>
|
||||
<div className='text-end'>
|
||||
<div className='text-dark-50 mb-0'>
|
||||
{new Date(request.created_at).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column gap-3'>
|
||||
<div>申请访问:{request.knowledge_base.name || request.knowledge_base}</div>
|
||||
<div className='d-flex flex-wrap gap-2'>
|
||||
{request.permissions.can_read && (
|
||||
<span className='badge bg-info custom-badge'>只读</span>
|
||||
)}
|
||||
{request.permissions.can_edit && (
|
||||
<span className='badge bg-success custom-badge'>编辑</span>
|
||||
)}
|
||||
{request.permissions.can_delete && (
|
||||
<span className='badge bg-danger custom-badge'>删除</span>
|
||||
)}
|
||||
{request.expires_at && (
|
||||
<span className='badge bg-secondary custom-badge'>
|
||||
至 {new Date(request.expires_at).toLocaleDateString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='text-dark-50'>
|
||||
<strong>申请理由:</strong> {request.reason}
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex justify-content-end'>
|
||||
<button
|
||||
className='btn btn-outline-danger me-2'
|
||||
onClick={() => handleOpenResponseInput(request.id, false)}
|
||||
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-success'
|
||||
onClick={() => handleOpenResponseInput(request.id, true)}
|
||||
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>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 回复输入弹窗 */}
|
||||
{showResponseInput && (
|
||||
<div className='modal fade show' style={{ display: 'block' }} tabIndex='-1'>
|
||||
<div className='modal-dialog'>
|
||||
<div className='modal-content'>
|
||||
<div className='modal-header'>
|
||||
<h5 className='modal-title'>{isApproving ? '批准' : '拒绝'}申请</h5>
|
||||
<button
|
||||
type='button'
|
||||
className='btn-close'
|
||||
onClick={handleCloseResponseInput}
|
||||
disabled={approveRejectStatus === 'loading'}
|
||||
></button>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='responseMessage' className='form-label'>
|
||||
审批意见
|
||||
</label>
|
||||
<textarea
|
||||
className='form-control'
|
||||
id='responseMessage'
|
||||
rows='3'
|
||||
value={responseMessage}
|
||||
onChange={(e) => setResponseMessage(e.target.value)}
|
||||
placeholder={isApproving ? '请输入批准意见(可选)' : '请输入拒绝理由(可选)'}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-secondary'
|
||||
onClick={handleCloseResponseInput}
|
||||
disabled={approveRejectStatus === 'loading'}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className={`btn ${isApproving ? 'btn-success' : 'btn-danger'}`}
|
||||
onClick={handleProcessRequest}
|
||||
disabled={approveRejectStatus === 'loading'}
|
||||
>
|
||||
{approveRejectStatus === 'loading' ? (
|
||||
<>
|
||||
<span
|
||||
className='spinner-border spinner-border-sm me-1'
|
||||
role='status'
|
||||
aria-hidden='true'
|
||||
></span>
|
||||
处理中...
|
||||
</>
|
||||
) : isApproving ? (
|
||||
'确认批准'
|
||||
) : (
|
||||
'确认拒绝'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
182
src/pages/Permissions/components/UserPermissionDetails.jsx
Normal file
182
src/pages/Permissions/components/UserPermissionDetails.jsx
Normal file
@ -0,0 +1,182 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { get } from '../../../services/api';
|
||||
|
||||
export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||
const [userPermissions, setUserPermissions] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [updatedPermissions, setUpdatedPermissions] = useState({});
|
||||
|
||||
// 获取用户权限详情
|
||||
useEffect(() => {
|
||||
const fetchUserPermissions = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await get(`/users/${user.id}/permissions/`);
|
||||
|
||||
if (response && response.code === 200) {
|
||||
setUserPermissions(response.data.permissions || []);
|
||||
} else {
|
||||
setError('获取用户权限详情失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户权限详情失败:', error);
|
||||
setError('获取用户权限详情失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (user) {
|
||||
fetchUserPermissions();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
// 处理权限变更
|
||||
const handlePermissionChange = (knowledgeBaseId, permissionType) => {
|
||||
setUpdatedPermissions({
|
||||
...updatedPermissions,
|
||||
[knowledgeBaseId]: permissionType,
|
||||
});
|
||||
};
|
||||
|
||||
// 处理保存
|
||||
const handleSave = () => {
|
||||
onSave(user.id, updatedPermissions);
|
||||
};
|
||||
|
||||
// 获取权限类型的显示文本
|
||||
const getPermissionTypeText = (permissionType) => {
|
||||
switch (permissionType) {
|
||||
case 'none':
|
||||
return '无权限';
|
||||
case 'read':
|
||||
return '只读访问';
|
||||
case 'edit':
|
||||
return '编辑权限';
|
||||
case 'admin':
|
||||
return '管理权限';
|
||||
default:
|
||||
return '未知权限';
|
||||
}
|
||||
};
|
||||
|
||||
// 获取权限类型的值
|
||||
const getPermissionType = (permission) => {
|
||||
if (!permission) return 'none';
|
||||
if (permission.can_admin) return 'admin';
|
||||
if (permission.can_edit) return 'edit';
|
||||
if (permission.can_read) return 'read';
|
||||
return 'none';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='modal fade show' style={{ display: 'block' }} tabIndex='-1'>
|
||||
<div className='modal-dialog modal-lg modal-dialog-scrollable'>
|
||||
<div className='modal-content'>
|
||||
<div className='modal-header'>
|
||||
<h5 className='modal-title'>{user.name} 的权限详情</h5>
|
||||
<button type='button' className='btn-close' onClick={onClose}></button>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
{loading ? (
|
||||
<div className='text-center py-4'>
|
||||
<div className='spinner-border' role='status'>
|
||||
<span className='visually-hidden'>加载中...</span>
|
||||
</div>
|
||||
<p className='mt-3'>加载权限详情...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className='alert alert-danger' role='alert'>
|
||||
{error}
|
||||
</div>
|
||||
) : userPermissions.length === 0 ? (
|
||||
<div className='alert alert-info' role='alert'>
|
||||
该用户暂无任何知识库权限
|
||||
</div>
|
||||
) : (
|
||||
<div className='table-responsive'>
|
||||
<table className='table table-hover'>
|
||||
<thead className='table-light'>
|
||||
<tr>
|
||||
<th>知识库名称</th>
|
||||
<th>所属部门</th>
|
||||
<th>当前权限</th>
|
||||
<th>最后访问时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userPermissions.map((item) => {
|
||||
const currentPermissionType = getPermissionType(item.permission);
|
||||
const updatedPermissionType =
|
||||
updatedPermissions[item.knowledge_base.id] || currentPermissionType;
|
||||
|
||||
return (
|
||||
<tr key={item.knowledge_base.id}>
|
||||
<td>{item.knowledge_base.name}</td>
|
||||
<td>{item.knowledge_base.department || '未指定'}</td>
|
||||
<td>
|
||||
<span
|
||||
className={`badge ${
|
||||
currentPermissionType === 'admin'
|
||||
? 'bg-danger'
|
||||
: currentPermissionType === 'edit'
|
||||
? 'bg-success'
|
||||
: currentPermissionType === 'read'
|
||||
? 'bg-info'
|
||||
: 'bg-secondary'
|
||||
}`}
|
||||
>
|
||||
{getPermissionTypeText(currentPermissionType)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{item.last_access_time
|
||||
? new Date(item.last_access_time).toLocaleString()
|
||||
: '从未访问'}
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
className='form-select form-select-sm'
|
||||
value={updatedPermissionType}
|
||||
onChange={(e) =>
|
||||
handlePermissionChange(
|
||||
item.knowledge_base.id,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value='none'>无权限</option>
|
||||
<option value='read'>只读访问</option>
|
||||
<option value='edit'>编辑权限</option>
|
||||
<option value='admin'>管理权限</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<button type='button' className='btn btn-secondary' onClick={onClose}>
|
||||
关闭
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary'
|
||||
onClick={handleSave}
|
||||
disabled={loading || Object.keys(updatedPermissions).length === 0}
|
||||
>
|
||||
保存更改
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
</div>
|
||||
);
|
||||
}
|
206
src/pages/Permissions/components/UserPermissions.jsx
Normal file
206
src/pages/Permissions/components/UserPermissions.jsx
Normal file
@ -0,0 +1,206 @@
|
||||
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';
|
||||
|
||||
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);
|
||||
|
||||
// 获取用户列表
|
||||
useEffect(() => {
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await get('/users/permissions/');
|
||||
|
||||
if (response && response.code === 200) {
|
||||
setUsers(response.data.users || []);
|
||||
} else {
|
||||
setError('获取用户列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error);
|
||||
setError('获取用户列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUsers();
|
||||
}, []);
|
||||
|
||||
// 打开用户权限详情弹窗
|
||||
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',
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染加载状态
|
||||
if (loading) {
|
||||
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) {
|
||||
return (
|
||||
<div className='alert alert-danger' role='alert'>
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染空状态
|
||||
if (users.length === 0) {
|
||||
return (
|
||||
<div className='alert alert-info' role='alert'>
|
||||
暂无用户数据
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 渲染用户列表
|
||||
return (
|
||||
<>
|
||||
<div className='card'>
|
||||
<div className='card-header bg-white'>
|
||||
<h5 className='mb-0'>用户权限管理</h5>
|
||||
</div>
|
||||
<div className='card-body p-0'>
|
||||
<div className='table-responsive'>
|
||||
<table className='table table-hover mb-0'>
|
||||
<thead className='table-light'>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>姓名</th>
|
||||
<th>部门</th>
|
||||
<th>职位</th>
|
||||
<th>权限</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.name}</td>
|
||||
<td>{user.department}</td>
|
||||
<td>{user.position}</td>
|
||||
<td>
|
||||
{user.permissions_count && (
|
||||
<div className='d-flex gap-2'>
|
||||
{user.permissions_count.read > 0 && (
|
||||
<span className='badge bg-info'>
|
||||
只读: {user.permissions_count.read}
|
||||
</span>
|
||||
)}
|
||||
{user.permissions_count.edit > 0 && (
|
||||
<span className='badge bg-success'>
|
||||
编辑: {user.permissions_count.edit}
|
||||
</span>
|
||||
)}
|
||||
{user.permissions_count.admin > 0 && (
|
||||
<span className='badge bg-danger'>
|
||||
管理: {user.permissions_count.admin}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className='btn btn-sm btn-primary'
|
||||
onClick={() => handleOpenDetailsModal(user)}
|
||||
>
|
||||
权限详情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 用户权限详情弹窗 */}
|
||||
{showDetailsModal && selectedUser && (
|
||||
<UserPermissionDetails
|
||||
user={selectedUser}
|
||||
onClose={handleCloseDetailsModal}
|
||||
onSave={handleSavePermissions}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -4,6 +4,7 @@ import Mainlayout from '../layouts/Mainlayout';
|
||||
import KnowledgeBase from '../pages/KnowledgeBase/KnowledgeBase';
|
||||
import KnowledgeBaseDetail from '../pages/KnowledgeBase/Detail/KnowledgeBaseDetail';
|
||||
import Chat from '../pages/Chat/Chat';
|
||||
import PermissionsPage from '../pages/Permissions/PermissionsPage';
|
||||
import Loading from '../components/Loading';
|
||||
import Login from '../pages/Auth/Login';
|
||||
import Signup from '../pages/Auth/Signup';
|
||||
@ -13,6 +14,9 @@ import { useSelector } from 'react-redux';
|
||||
function AppRouter() {
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
// 检查用户是否有管理权限(leader 或 admin)
|
||||
const hasManagePermission = user && (user.role === 'leader' || user.role === 'admin');
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Routes>
|
||||
@ -65,6 +69,15 @@ function AppRouter() {
|
||||
</Mainlayout>
|
||||
}
|
||||
/>
|
||||
{/* 权限管理页面路由 - 仅对 leader 或 admin 角色可见 */}
|
||||
<Route
|
||||
path='/permissions'
|
||||
element={
|
||||
<Mainlayout>
|
||||
<PermissionsPage />
|
||||
</Mainlayout>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path='/login' element={<Login />} />
|
||||
<Route path='/signup' element={<Signup />} />
|
||||
|
55
src/services/permissionService.js
Normal file
55
src/services/permissionService.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { post } from './api';
|
||||
|
||||
/**
|
||||
* 计算权限过期时间
|
||||
* @param {string} duration - 权限持续时间,如 '一周', '一个月', '三个月', '六个月', '永久'
|
||||
* @returns {string} - ISO 格式的日期字符串
|
||||
*/
|
||||
export const calculateExpiresAt = (duration) => {
|
||||
const now = new Date();
|
||||
switch (duration) {
|
||||
case '一周':
|
||||
now.setDate(now.getDate() + 7);
|
||||
break;
|
||||
case '一个月':
|
||||
now.setMonth(now.getMonth() + 1);
|
||||
break;
|
||||
case '三个月':
|
||||
now.setMonth(now.getMonth() + 3);
|
||||
break;
|
||||
case '六个月':
|
||||
now.setMonth(now.getMonth() + 6);
|
||||
break;
|
||||
case '永久':
|
||||
// 设置为较远的未来日期
|
||||
now.setFullYear(now.getFullYear() + 10);
|
||||
break;
|
||||
default:
|
||||
now.setDate(now.getDate() + 7);
|
||||
}
|
||||
return now.toISOString();
|
||||
};
|
||||
|
||||
/**
|
||||
* 申请知识库访问权限
|
||||
* @param {Object} requestData - 请求数据
|
||||
* @param {string} requestData.id - 知识库ID
|
||||
* @param {string} requestData.accessType - 访问类型,如 '只读访问', '编辑权限'
|
||||
* @param {string} requestData.duration - 访问时长,如 '一周', '一个月'
|
||||
* @param {string} requestData.reason - 申请原因
|
||||
* @returns {Promise} - API 请求的 Promise
|
||||
*/
|
||||
export const requestKnowledgeBaseAccess = async (requestData) => {
|
||||
const apiRequestData = {
|
||||
knowledge_base: requestData.id,
|
||||
permissions: {
|
||||
can_read: true,
|
||||
can_edit: requestData.accessType === '编辑权限',
|
||||
can_delete: false,
|
||||
},
|
||||
reason: requestData.reason,
|
||||
expires_at: calculateExpiresAt(requestData.duration),
|
||||
};
|
||||
|
||||
return post('/permissions/', apiRequestData);
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
// 模拟的当前用户数据
|
||||
export const mockCurrentUser = {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
name: 'John Doe',
|
||||
department: '研发部',
|
||||
group: '前端开发组',
|
||||
role: 'developer',
|
||||
avatar: 'https://via.placeholder.com/150',
|
||||
created_at: '2023-01-15T08:30:00Z',
|
||||
updated_at: '2023-12-20T14:45:00Z',
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { checkAuthThunk, loginThunk, logoutThunk, signupThunk } from './auth.thunk';
|
||||
import { mockCurrentUser } from './auth.mock';
|
||||
|
||||
const setPending = (state) => {
|
||||
state.loading = true;
|
||||
@ -24,7 +23,7 @@ const authSlice = createSlice({
|
||||
initialState: {
|
||||
loading: false,
|
||||
error: null,
|
||||
user: mockCurrentUser, // 使用模拟的当前用户数据
|
||||
user: null,
|
||||
},
|
||||
reducers: {
|
||||
login: (state, action) => {
|
||||
|
@ -54,7 +54,8 @@ export const signupThunk = createAsyncThunk('auth/signup', async (config, { reje
|
||||
|
||||
export const checkAuthThunk = createAsyncThunk('auth/verify', async (_, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const { user, message } = await post('/auth/verify-token/');
|
||||
const { data, message } = await post('/auth/verify-token/');
|
||||
const { user } = data;
|
||||
if (!user) {
|
||||
dispatch(logout());
|
||||
throw new Error(message || 'No token found');
|
||||
|
@ -1,153 +0,0 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// 模拟聊天历史数据
|
||||
export const mockChatHistory = [
|
||||
{
|
||||
id: '1',
|
||||
title: '关于产品开发流程的咨询',
|
||||
knowledge_base_id: '1',
|
||||
knowledge_base_name: '产品开发知识库',
|
||||
created_at: '2025-03-10T10:30:00Z',
|
||||
updated_at: '2025-03-10T11:45:00Z',
|
||||
message_count: 8,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '市场分析报告查询',
|
||||
knowledge_base_id: '2',
|
||||
knowledge_base_name: '市场分析知识库',
|
||||
created_at: '2025-03-09T14:20:00Z',
|
||||
updated_at: '2025-03-09T15:10:00Z',
|
||||
message_count: 5,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '技术架构设计讨论',
|
||||
knowledge_base_id: '4',
|
||||
knowledge_base_name: '技术架构知识库',
|
||||
created_at: '2025-03-08T09:15:00Z',
|
||||
updated_at: '2025-03-08T10:30:00Z',
|
||||
message_count: 12,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '用户反馈分析',
|
||||
knowledge_base_id: '5',
|
||||
knowledge_base_name: '用户研究知识库',
|
||||
created_at: '2025-03-07T16:40:00Z',
|
||||
updated_at: '2025-03-07T17:25:00Z',
|
||||
message_count: 6,
|
||||
},
|
||||
];
|
||||
|
||||
// 内存存储,用于模拟数据库操作
|
||||
let chatHistoryStore = [...mockChatHistory];
|
||||
|
||||
/**
|
||||
* 模拟获取聊天历史列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Object} - 分页结果
|
||||
*/
|
||||
export const mockGetChatHistory = (params = { page: 1, page_size: 10 }) => {
|
||||
const { page, page_size } = params;
|
||||
const startIndex = (page - 1) * page_size;
|
||||
const endIndex = startIndex + page_size;
|
||||
const paginatedItems = chatHistoryStore.slice(startIndex, endIndex);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total: chatHistoryStore.length,
|
||||
page,
|
||||
page_size,
|
||||
results: paginatedItems,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 模拟创建新聊天
|
||||
* @param {Object} chatData - 聊天数据
|
||||
* @returns {Object} - 创建结果
|
||||
*/
|
||||
export const mockCreateChat = (chatData) => {
|
||||
const newChat = {
|
||||
id: uuidv4(),
|
||||
title: chatData.title || '新的聊天',
|
||||
knowledge_base_id: chatData.knowledge_base_id,
|
||||
knowledge_base_name: chatData.knowledge_base_name || '未知知识库',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
message_count: 0,
|
||||
};
|
||||
|
||||
chatHistoryStore.unshift(newChat);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '创建成功',
|
||||
data: {
|
||||
chat: newChat,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 模拟更新聊天
|
||||
* @param {string} id - 聊天ID
|
||||
* @param {Object} data - 更新数据
|
||||
* @returns {Object} - 更新结果
|
||||
*/
|
||||
export const mockUpdateChat = (id, data) => {
|
||||
const index = chatHistoryStore.findIndex((chat) => chat.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return {
|
||||
code: 404,
|
||||
message: '聊天不存在',
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const updatedChat = {
|
||||
...chatHistoryStore[index],
|
||||
...data,
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
chatHistoryStore[index] = updatedChat;
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '更新成功',
|
||||
data: {
|
||||
chat: updatedChat,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 模拟删除聊天
|
||||
* @param {string} id - 聊天ID
|
||||
* @returns {Object} - 删除结果
|
||||
*/
|
||||
export const mockDeleteChat = (id) => {
|
||||
const index = chatHistoryStore.findIndex((chat) => chat.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
return {
|
||||
code: 404,
|
||||
message: '聊天不存在',
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
chatHistoryStore.splice(index, 1);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '删除成功',
|
||||
data: null,
|
||||
};
|
||||
};
|
@ -1,130 +0,0 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { fetchChatHistory, createChat, deleteChat, updateChat } from './chatHistory.thunks';
|
||||
|
||||
// 初始状态
|
||||
const initialState = {
|
||||
// 聊天历史列表
|
||||
list: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: null,
|
||||
},
|
||||
|
||||
// 当前聊天
|
||||
currentChat: {
|
||||
data: null,
|
||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: null,
|
||||
},
|
||||
|
||||
// 操作状态(创建、更新、删除)
|
||||
operations: {
|
||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
|
||||
// 创建 slice
|
||||
const chatHistorySlice = createSlice({
|
||||
name: 'chatHistory',
|
||||
initialState,
|
||||
reducers: {
|
||||
// 重置操作状态
|
||||
resetOperationStatus: (state) => {
|
||||
state.operations.status = 'idle';
|
||||
state.operations.error = null;
|
||||
},
|
||||
|
||||
// 重置当前聊天
|
||||
resetCurrentChat: (state) => {
|
||||
state.currentChat.data = null;
|
||||
state.currentChat.status = 'idle';
|
||||
state.currentChat.error = null;
|
||||
},
|
||||
|
||||
// 设置当前聊天
|
||||
setCurrentChat: (state, action) => {
|
||||
state.currentChat.data = action.payload;
|
||||
state.currentChat.status = 'succeeded';
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// 获取聊天历史
|
||||
builder
|
||||
.addCase(fetchChatHistory.pending, (state) => {
|
||||
state.list.status = 'loading';
|
||||
})
|
||||
.addCase(fetchChatHistory.fulfilled, (state, action) => {
|
||||
state.list.status = 'succeeded';
|
||||
state.list.items = action.payload.results;
|
||||
state.list.total = action.payload.total;
|
||||
state.list.page = action.payload.page;
|
||||
state.list.page_size = action.payload.page_size;
|
||||
})
|
||||
.addCase(fetchChatHistory.rejected, (state, action) => {
|
||||
state.list.status = 'failed';
|
||||
state.list.error = action.payload || action.error.message;
|
||||
})
|
||||
|
||||
// 创建聊天
|
||||
.addCase(createChat.pending, (state) => {
|
||||
state.operations.status = 'loading';
|
||||
})
|
||||
.addCase(createChat.fulfilled, (state, action) => {
|
||||
state.operations.status = 'succeeded';
|
||||
state.list.items.unshift(action.payload);
|
||||
state.list.total += 1;
|
||||
state.currentChat.data = action.payload;
|
||||
state.currentChat.status = 'succeeded';
|
||||
})
|
||||
.addCase(createChat.rejected, (state, action) => {
|
||||
state.operations.status = 'failed';
|
||||
state.operations.error = action.payload || action.error.message;
|
||||
})
|
||||
|
||||
// 删除聊天
|
||||
.addCase(deleteChat.pending, (state) => {
|
||||
state.operations.status = 'loading';
|
||||
})
|
||||
.addCase(deleteChat.fulfilled, (state, action) => {
|
||||
state.operations.status = 'succeeded';
|
||||
state.list.items = state.list.items.filter((chat) => chat.id !== action.payload);
|
||||
state.list.total -= 1;
|
||||
if (state.currentChat.data && state.currentChat.data.id === action.payload) {
|
||||
state.currentChat.data = null;
|
||||
}
|
||||
})
|
||||
.addCase(deleteChat.rejected, (state, action) => {
|
||||
state.operations.status = 'failed';
|
||||
state.operations.error = action.payload || action.error.message;
|
||||
})
|
||||
|
||||
// 更新聊天
|
||||
.addCase(updateChat.pending, (state) => {
|
||||
state.operations.status = 'loading';
|
||||
})
|
||||
.addCase(updateChat.fulfilled, (state, action) => {
|
||||
state.operations.status = 'succeeded';
|
||||
const index = state.list.items.findIndex((chat) => chat.id === action.payload.id);
|
||||
if (index !== -1) {
|
||||
state.list.items[index] = action.payload;
|
||||
}
|
||||
if (state.currentChat.data && state.currentChat.data.id === action.payload.id) {
|
||||
state.currentChat.data = action.payload;
|
||||
}
|
||||
})
|
||||
.addCase(updateChat.rejected, (state, action) => {
|
||||
state.operations.status = 'failed';
|
||||
state.operations.error = action.payload || action.error.message;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 导出 actions
|
||||
export const { resetOperationStatus, resetCurrentChat, setCurrentChat } = chatHistorySlice.actions;
|
||||
|
||||
// 导出 reducer
|
||||
export default chatHistorySlice.reducer;
|
@ -1,87 +0,0 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { get, post, put, del } from '../../services/api';
|
||||
|
||||
/**
|
||||
* 获取聊天历史列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.page_size - 每页数量
|
||||
*/
|
||||
export const fetchChatHistory = createAsyncThunk(
|
||||
'chatHistory/fetchChatHistory',
|
||||
async (params = { page: 1, page_size: 10 }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await get('/chat-history/', { params });
|
||||
|
||||
// 处理返回格式
|
||||
if (response.data && response.data.code === 200) {
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data || 'Failed to fetch chat history');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 创建新聊天
|
||||
* @param {Object} chatData - 聊天数据
|
||||
* @param {string} chatData.knowledge_base_id - 知识库ID
|
||||
* @param {string} chatData.title - 聊天标题
|
||||
*/
|
||||
export const createChat = createAsyncThunk('chatHistory/createChat', async (chatData, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await post('/chat-history/', chatData);
|
||||
|
||||
// 处理返回格式
|
||||
if (response.data && response.data.code === 200) {
|
||||
return response.data.data.chat;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data || 'Failed to create chat');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新聊天
|
||||
* @param {Object} params - 更新参数
|
||||
* @param {string} params.id - 聊天ID
|
||||
* @param {Object} params.data - 更新数据
|
||||
*/
|
||||
export const updateChat = createAsyncThunk('chatHistory/updateChat', async ({ id, data }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await put(`/chat-history/${id}/`, data);
|
||||
|
||||
// 处理返回格式
|
||||
if (response.data && response.data.code === 200) {
|
||||
return response.data.data.chat;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data || 'Failed to update chat');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除聊天
|
||||
* @param {string} id - 聊天ID
|
||||
*/
|
||||
export const deleteChat = createAsyncThunk('chatHistory/deleteChat', async (id, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await del(`/chat-history/${id}/`);
|
||||
|
||||
// 处理返回格式
|
||||
if (response.data && response.data.code === 200) {
|
||||
return id;
|
||||
}
|
||||
|
||||
return id;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data || 'Failed to delete chat');
|
||||
}
|
||||
});
|
83
src/store/permissions/permissions.slice.js
Normal file
83
src/store/permissions/permissions.slice.js
Normal file
@ -0,0 +1,83 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { fetchPermissionsThunk, approvePermissionThunk, rejectPermissionThunk } from './permissions.thunks';
|
||||
|
||||
const initialState = {
|
||||
permissions: {
|
||||
items: [],
|
||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: null,
|
||||
},
|
||||
approveReject: {
|
||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: null,
|
||||
currentId: null,
|
||||
},
|
||||
};
|
||||
|
||||
const permissionsSlice = createSlice({
|
||||
name: 'permissions',
|
||||
initialState,
|
||||
reducers: {
|
||||
resetApproveRejectStatus: (state) => {
|
||||
state.approveReject = {
|
||||
status: 'idle',
|
||||
error: null,
|
||||
currentId: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// 获取权限申请列表
|
||||
builder
|
||||
.addCase(fetchPermissionsThunk.pending, (state) => {
|
||||
state.permissions.status = 'loading';
|
||||
state.permissions.error = null;
|
||||
})
|
||||
.addCase(fetchPermissionsThunk.fulfilled, (state, action) => {
|
||||
state.permissions.status = 'succeeded';
|
||||
state.permissions.items = action.payload;
|
||||
})
|
||||
.addCase(fetchPermissionsThunk.rejected, (state, action) => {
|
||||
state.permissions.status = 'failed';
|
||||
state.permissions.error = action.payload || '获取权限申请列表失败';
|
||||
});
|
||||
|
||||
// 批准权限申请
|
||||
builder
|
||||
.addCase(approvePermissionThunk.pending, (state, action) => {
|
||||
state.approveReject.status = 'loading';
|
||||
state.approveReject.error = null;
|
||||
state.approveReject.currentId = action.meta.arg.id;
|
||||
})
|
||||
.addCase(approvePermissionThunk.fulfilled, (state, action) => {
|
||||
state.approveReject.status = 'succeeded';
|
||||
// 从列表中移除已批准的申请
|
||||
state.permissions.items = state.permissions.items.filter((item) => item.id !== action.meta.arg.id);
|
||||
})
|
||||
.addCase(approvePermissionThunk.rejected, (state, action) => {
|
||||
state.approveReject.status = 'failed';
|
||||
state.approveReject.error = action.payload || '批准权限申请失败';
|
||||
});
|
||||
|
||||
// 拒绝权限申请
|
||||
builder
|
||||
.addCase(rejectPermissionThunk.pending, (state, action) => {
|
||||
state.approveReject.status = 'loading';
|
||||
state.approveReject.error = null;
|
||||
state.approveReject.currentId = action.meta.arg.id;
|
||||
})
|
||||
.addCase(rejectPermissionThunk.fulfilled, (state, action) => {
|
||||
state.approveReject.status = 'succeeded';
|
||||
// 从列表中移除已拒绝的申请
|
||||
state.permissions.items = state.permissions.items.filter((item) => item.id !== action.meta.arg.id);
|
||||
})
|
||||
.addCase(rejectPermissionThunk.rejected, (state, action) => {
|
||||
state.approveReject.status = 'failed';
|
||||
state.approveReject.error = action.payload || '拒绝权限申请失败';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { resetApproveRejectStatus } = permissionsSlice.actions;
|
||||
|
||||
export default permissionsSlice.reducer;
|
48
src/store/permissions/permissions.thunks.js
Normal file
48
src/store/permissions/permissions.thunks.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { get, post } from '../../services/api';
|
||||
|
||||
// 获取权限申请列表
|
||||
export const fetchPermissionsThunk = createAsyncThunk(
|
||||
'permissions/fetchPermissions',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await get('/permissions/');
|
||||
return response || [];
|
||||
} catch (error) {
|
||||
console.error('获取权限申请列表失败:', error);
|
||||
return rejectWithValue('获取权限申请列表失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 批准权限申请
|
||||
export const approvePermissionThunk = createAsyncThunk(
|
||||
'permissions/approvePermission',
|
||||
async ({ id, responseMessage }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await post(`/permissions/${id}/approve/`, {
|
||||
response_message: responseMessage || '已批准',
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('批准权限申请失败:', error);
|
||||
return rejectWithValue('批准权限申请失败');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 拒绝权限申请
|
||||
export const rejectPermissionThunk = createAsyncThunk(
|
||||
'permissions/rejectPermission',
|
||||
async ({ id, responseMessage }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await post(`/permissions/${id}/reject/`, {
|
||||
response_message: responseMessage || '已拒绝',
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('拒绝权限申请失败:', error);
|
||||
return rejectWithValue('拒绝权限申请失败');
|
||||
}
|
||||
}
|
||||
);
|
@ -5,12 +5,14 @@ import notificationReducer from './notification.slice.js';
|
||||
import authReducer from './auth/auth.slice.js';
|
||||
import knowledgeBaseReducer from './knowledgeBase/knowledgeBase.slice.js';
|
||||
import chatReducer from './chat/chat.slice.js';
|
||||
import permissionsReducer from './permissions/permissions.slice.js';
|
||||
|
||||
const rootRducer = combineReducers({
|
||||
auth: authReducer,
|
||||
notification: notificationReducer,
|
||||
knowledgeBase: knowledgeBaseReducer,
|
||||
chat: chatReducer,
|
||||
permissions: permissionsReducer,
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
|
Loading…
Reference in New Issue
Block a user