[dev]pending approve

This commit is contained in:
susie-laptop 2025-03-26 22:09:52 -04:00
parent 56c4878065
commit 4c4bfe7e56
12 changed files with 246 additions and 102 deletions

View File

@ -20,18 +20,29 @@ export default function AccessRequestModal({
isSubmitting = false,
}) {
const [accessRequestData, setAccessRequestData] = useState({
accessType: '只读访问',
duration: '一周',
permissions: {
can_read: true,
can_edit: false,
can_delete: false,
},
duration: '30', // 30
reason: '',
});
const [accessRequestErrors, setAccessRequestErrors] = useState({});
const handleAccessRequestInputChange = (e) => {
const { name, value } = e.target;
if (name === 'duration') {
setAccessRequestData((prev) => ({
...prev,
[name]: value,
}));
} else if (name === 'reason') {
setAccessRequestData((prev) => ({
...prev,
[name]: value,
}));
}
// Clear error when user types
if (accessRequestErrors[name]) {
@ -42,6 +53,17 @@ export default function AccessRequestModal({
}
};
const handlePermissionChange = (permissionType) => {
setAccessRequestData((prev) => ({
...prev,
permissions: {
can_read: true, // true
can_edit: permissionType === '编辑权限',
can_delete: false, //
},
}));
};
const validateAccessRequestForm = () => {
const errors = {};
@ -59,17 +81,27 @@ export default function AccessRequestModal({
return;
}
//
const expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + parseInt(accessRequestData.duration));
const expiresAt = expirationDate.toISOString();
//
onSubmit({
id: knowledgeBaseId,
title: knowledgeBaseTitle,
...accessRequestData,
knowledge_base: knowledgeBaseId,
permissions: accessRequestData.permissions,
reason: accessRequestData.reason,
expires_at: expiresAt,
});
//
setAccessRequestData({
accessType: '只读访问',
duration: '一周',
permissions: {
can_read: true,
can_edit: false,
can_delete: false,
},
duration: '30',
reason: '',
});
setAccessRequestErrors({});
@ -123,9 +155,8 @@ export default function AccessRequestModal({
</label>
<select
className='form-select'
name='accessType'
value={accessRequestData.accessType}
onChange={handleAccessRequestInputChange}
value={accessRequestData.permissions.can_edit ? '编辑权限' : '只读访问'}
onChange={(e) => handlePermissionChange(e.target.value)}
>
<option value='只读访问'>只读访问</option>
<option value='编辑权限'>编辑权限</option>
@ -143,11 +174,13 @@ export default function AccessRequestModal({
value={accessRequestData.duration}
onChange={handleAccessRequestInputChange}
>
<option value='一周'>一周</option>
<option value='一个月'>一个月</option>
<option value='三个月'>三个月</option>
<option value='六个月'>六个月</option>
<option value='永久'>永久</option>
<option value='7'>7</option>
<option value='15'>15</option>
<option value='30'>30</option>
<option value='60'>60</option>
<option value='90'>90</option>
<option value='180'>180</option>
<option value='365'>365</option>
</select>
</div>
<div className='mb-3'>

View File

@ -127,7 +127,7 @@ export default function HeaderWithNav() {
<Link
className='dropdown-item'
to='#'
onClick={() => setShowSettings(true)}
// onClick={() => setShowSettings(true)}
>
个人设置
</Link>

View File

@ -235,8 +235,8 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
<div
key={message.id}
className={`d-flex ${
message.role === 'user' ? 'justify-content-end' : 'justify-content-start'
} mb-3`}
message.role === 'user' ? 'align-item-end' : 'align-item-start'
} mb-3 flex-column`}
>
<div
className={`chat-message p-3 rounded-3 ${
@ -248,11 +248,11 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
}}
>
<div className='message-content'>{message.content}</div>
</div>
<div className='message-time small text-muted mt-1'>
{message.created_at && new Date(message.created_at).toLocaleTimeString()}
</div>
</div>
</div>
))}
{sendStatus === 'loading' && (

View File

@ -333,7 +333,7 @@ export default function SettingsTab({ knowledgeBase }) {
/>
{/* User Permissions Manager */}
<UserPermissionsManager knowledgeBase={knowledgeBase} />
{/* <UserPermissionsManager knowledgeBase={knowledgeBase} /> */}
{/* Delete confirmation modal */}
<DeleteConfirmModal

View File

@ -260,7 +260,13 @@ const KnowledgeBaseForm = ({
'保存设置'
)}
</button>
<button type='button' className='btn btn-danger' onClick={onDelete} disabled={isSubmitting}>
<button
type='button'
className='btn btn-danger'
onClick={onDelete}
disabled='true'
// disabled={isSubmitting}
>
删除知识库
</button>
</div>

View File

@ -381,17 +381,10 @@ export default function KnowledgeBase() {
setIsSubmittingRequest(true);
try {
// 使
await requestKnowledgeBaseAccess(requestData);
// 使 - dispatchthunk
await dispatch(requestKnowledgeBaseAccess(requestData)).unwrap();
dispatch(
showNotification({
message: '权限申请已提交',
type: 'success',
})
);
// Close modal
// Close modal after success
setShowAccessRequestModal(false);
} catch (error) {
dispatch(

View File

@ -18,11 +18,11 @@ export default function PermissionsPage() {
return (
<div className='permissions-container'>
<div className='permissions-section mb-4'>
<div className='permissions-section mb-4 container my-4'>
<PendingRequests />
</div>
{user && user.role === 'admin' && (
<div className='permissions-section'>
<div className='permissions-section container my-4'>
<UserPermissions />
</div>
)}

View File

@ -24,7 +24,7 @@ export default function PendingRequests() {
// Redux store
const {
results: pendingRequests,
results: permissionRequests,
status: fetchStatus,
error: fetchError,
page: currentPage,
@ -185,32 +185,69 @@ export default function PendingRequests() {
);
};
//
const renderStatusBadge = (status) => {
switch (status) {
case 'approved':
return (
<span
className='badge bg-success-subtle text-success d-flex align-items-center gap-1'
style={{ width: 'fit-content' }}
>
<SvgIcon className='circle-check' />
已批准
</span>
);
case 'rejected':
return (
<span
className='badge bg-danger-subtle text-danger d-flex align-items-center gap-1'
style={{ width: 'fit-content' }}
>
<SvgIcon className='circle-xmark' />
已拒绝
</span>
);
case 'pending':
return (
<span
className='badge bg-warning-subtle text-warning d-flex align-items-center gap-1'
style={{ width: 'fit-content' }}
>
待处理
</span>
);
default:
return null;
}
};
//
if (fetchStatus === 'loading' && pendingRequests.length === 0) {
if (fetchStatus === 'loading' && permissionRequests.length === 0) {
return (
<div className='text-center py-5'>
<div className='spinner-border' role='status'>
<span className='visually-hidden'>加载中...</span>
</div>
<p className='mt-3'>加载待处理申请...</p>
<p className='mt-3'>加载权限申请...</p>
</div>
);
}
//
if (fetchStatus === 'failed' && pendingRequests.length === 0) {
if (fetchStatus === 'failed' && permissionRequests.length === 0) {
return (
<div className='alert alert-danger' role='alert'>
{fetchError || '获取待处理申请失败'}
{fetchError || '获取权限申请失败'}
</div>
);
}
//
if (pendingRequests.length === 0) {
if (permissionRequests.length === 0) {
return (
<div className='alert alert-info' role='alert'>
暂无待处理的权限申请
暂无权限申请记录
</div>
);
}
@ -219,12 +256,14 @@ export default function PendingRequests() {
return (
<>
<div className='d-flex justify-content-between align-items-center mb-3'>
<h5 className='mb-0'>待处理申请</h5>
<div className='badge bg-danger'>{total}个待处理</div>
<h5 className='mb-0'>权限申请列表</h5>
<div className='badge bg-danger'>
{permissionRequests.filter((req) => req.status === 'pending').length}个待处理
</div>
</div>
<div className='pending-requests-list'>
{pendingRequests.map((request) => (
{permissionRequests.map((request) => (
<div key={request.id} className='pending-request-item' onClick={() => handleRowClick(request)}>
<div className='request-header'>
<div className='user-info'>
@ -235,7 +274,10 @@ export default function PendingRequests() {
</div>
<div className='request-content'>
<p className='mb-2'>申请访问{request.knowledge_base.name}</p>
<div className='d-flex justify-content-between align-items-start mb-2'>
<p className='mb-0'>申请访问{request.knowledge_base.name}</p>
{renderStatusBadge(request.status)}
</div>
{request.permissions.can_edit ? (
<span
@ -264,8 +306,16 @@ export default function PendingRequests() {
</small>
</div>
)}
{request.response_message && (
<div className='mt-2'>
<small className='text-muted'>审批意见: {request.response_message}</small>
</div>
)}
</div>
<div className='request-actions'>
{request.status === 'pending' ? (
<>
<button
className='btn btn-outline-danger btn-sm'
onClick={(e) => {
@ -274,7 +324,9 @@ export default function PendingRequests() {
}}
disabled={processingId === request.id && approveRejectStatus === 'loading'}
>
{processingId === request.id && approveRejectStatus === 'loading' && !isApproving
{processingId === request.id &&
approveRejectStatus === 'loading' &&
!isApproving
? '处理中...'
: '拒绝'}
</button>
@ -290,6 +342,17 @@ export default function PendingRequests() {
? '处理中...'
: '批准'}
</button>
</>
) : (
<div className='status-text'>
{request.status === 'approved' ? '已批准' : '已拒绝'}
{request.updated_at && (
<small className='text-muted d-block'>
{new Date(request.updated_at).toLocaleDateString()}
</small>
)}
</div>
)}
</div>
</div>
))}

View File

@ -13,6 +13,7 @@ export default function UserPermissions() {
const [selectedUser, setSelectedUser] = useState(null);
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all'); // 'all', 'pending', 'approved', 'rejected'
//
const [currentPage, setCurrentPage] = useState(1);
@ -62,6 +63,11 @@ export default function UserPermissions() {
setCurrentPage(1); //
};
const handleStatusFilterChange = (e) => {
setStatusFilter(e.target.value);
setCurrentPage(1); //
};
const handlePageChange = (page) => {
if (page > 0 && page <= totalPages) {
setCurrentPage(page);
@ -76,17 +82,27 @@ export default function UserPermissions() {
//
const getFilteredUsers = () => {
if (!searchTerm.trim()) {
return users;
let filtered = users;
//
if (statusFilter !== 'all') {
filtered = filtered.filter((user) =>
user.permissions.some((permission) => permission.status === statusFilter)
);
}
return users.filter(
//
if (searchTerm.trim()) {
filtered = filtered.filter(
(user) =>
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())
);
}
return filtered;
};
const filteredUsers = getFilteredUsers();
@ -196,7 +212,8 @@ export default function UserPermissions() {
</div>
<>
{/* <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='d-flex gap-3'>
<div className='search-bar' style={{ maxWidth: '300px' }}>
<div className='input-group'>
<span className='input-group-text bg-light border-end-0'>
@ -211,7 +228,19 @@ export default function UserPermissions() {
/>
</div>
</div>
</div> */}
<select
className='form-select'
value={statusFilter}
onChange={handleStatusFilterChange}
style={{ width: 'auto' }}
>
<option value='all'>全部状态</option>
<option value='pending'>待处理</option>
<option value='approved'>已批准</option>
<option value='rejected'>已拒绝</option>
</select>
</div>
</div>
{loading === 'loading' ? (
<div className='text-center my-5'>

View File

@ -214,4 +214,21 @@ export const switchToRealApi = async () => {
return isServerUp;
};
// 权限相关API
export const applyPermission = (data) => {
return post('/permissions/', data);
};
export const updatePermission = (data) => {
return post('/permissions/update_permission/', data);
};
export const approvePermission = (permissionId) => {
return post(`/permissions/approve_permission/${permissionId}`);
};
export const rejectPermission = (permissionId) => {
return post(`/permissions/reject_permission/${permissionId}`);
};
export { get, post, put, del, upload };

View File

@ -31,7 +31,8 @@ export const calculateExpiresAt = (duration) => {
};
/**
* 申请知识库访问权限
* 申请知识库访问权限已废弃请使用store/knowledgeBase/knowledgeBase.thunks中的requestKnowledgeBaseAccess
* @deprecated 请使用Redux thunk版本
* @param {Object} requestData - 请求数据
* @param {string} requestData.id - 知识库ID
* @param {string} requestData.accessType - 访问类型 '只读访问', '编辑权限'
@ -39,7 +40,7 @@ export const calculateExpiresAt = (duration) => {
* @param {string} requestData.reason - 申请原因
* @returns {Promise} - API 请求的 Promise
*/
export const requestKnowledgeBaseAccess = async (requestData) => {
export const legacyRequestKnowledgeBaseAccess = async (requestData) => {
const apiRequestData = {
knowledge_base: requestData.id,
permissions: {

View File

@ -165,15 +165,17 @@ export const changeKnowledgeBaseType = createAsyncThunk(
/**
* 申请知识库访问权限
* @param {Object} params - 参数
* @param {string} params.knowledgeBaseId - 知识库ID
* @param {string} params.knowledge_base - 知识库ID
* @param {Object} params.permissions - 权限对象
* @param {string} params.reason - 申请原因
* @param {string} params.expires_at - 过期时间
* @returns {Promise} - Promise对象
*/
export const requestKnowledgeBaseAccess = createAsyncThunk(
'knowledgeBase/requestAccess',
async (params, { rejectWithValue, dispatch }) => {
try {
const { knowledgeBaseId } = params;
const response = await post(`/knowledge-bases/${knowledgeBaseId}/request_access/`);
const response = await post('/permissions/', params);
dispatch(
showNotification({
type: 'success',