mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 05:28:16 +08:00
[dev]pending approve
This commit is contained in:
parent
56c4878065
commit
4c4bfe7e56
@ -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'>
|
||||
|
@ -127,7 +127,7 @@ export default function HeaderWithNav() {
|
||||
<Link
|
||||
className='dropdown-item'
|
||||
to='#'
|
||||
onClick={() => setShowSettings(true)}
|
||||
// onClick={() => setShowSettings(true)}
|
||||
>
|
||||
个人设置
|
||||
</Link>
|
||||
|
@ -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' && (
|
||||
|
@ -333,7 +333,7 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
/>
|
||||
|
||||
{/* User Permissions Manager */}
|
||||
<UserPermissionsManager knowledgeBase={knowledgeBase} />
|
||||
{/* <UserPermissionsManager knowledgeBase={knowledgeBase} /> */}
|
||||
|
||||
{/* Delete confirmation modal */}
|
||||
<DeleteConfirmModal
|
||||
|
@ -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>
|
||||
|
@ -381,17 +381,10 @@ export default function KnowledgeBase() {
|
||||
setIsSubmittingRequest(true);
|
||||
|
||||
try {
|
||||
// 使用权限服务发送请求
|
||||
await requestKnowledgeBaseAccess(requestData);
|
||||
// 使用权限服务发送请求 - 通过dispatch调用thunk
|
||||
await dispatch(requestKnowledgeBaseAccess(requestData)).unwrap();
|
||||
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: '权限申请已提交',
|
||||
type: 'success',
|
||||
})
|
||||
);
|
||||
|
||||
// Close modal
|
||||
// Close modal after success
|
||||
setShowAccessRequestModal(false);
|
||||
} catch (error) {
|
||||
dispatch(
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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>
|
||||
))}
|
||||
|
@ -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'>
|
||||
|
@ -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 };
|
||||
|
@ -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: {
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user