2025-03-04 07:22:05 +08:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
import { useNavigate } from 'react-router-dom';
|
2025-02-27 06:54:19 +08:00
|
|
|
import KnowledgeCard from './KnowledgeCard';
|
2025-03-01 05:48:24 +08:00
|
|
|
import { useDispatch } from 'react-redux';
|
2025-03-01 08:34:56 +08:00
|
|
|
import { showNotification } from '../../store/notification.slice';
|
|
|
|
import SvgIcon from '../../components/SvgIcon';
|
2025-02-27 06:54:19 +08:00
|
|
|
|
|
|
|
export default function KnowledgeBase() {
|
2025-03-01 05:48:24 +08:00
|
|
|
const dispatch = useDispatch();
|
2025-03-04 07:22:05 +08:00
|
|
|
const navigate = useNavigate();
|
|
|
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
2025-03-04 08:37:47 +08:00
|
|
|
const [showAccessRequestModal, setShowAccessRequestModal] = useState(false);
|
|
|
|
const [formErrors, setFormErrors] = useState({});
|
|
|
|
const [accessRequestErrors, setAccessRequestErrors] = useState({});
|
|
|
|
const [accessRequestData, setAccessRequestData] = useState({
|
|
|
|
id: '',
|
|
|
|
title: '',
|
|
|
|
accessType: '只读访问',
|
|
|
|
duration: '一周',
|
|
|
|
projectInfo: '',
|
|
|
|
reason: '',
|
|
|
|
});
|
2025-03-04 07:22:05 +08:00
|
|
|
const [newKnowledgeBase, setNewKnowledgeBase] = useState({
|
|
|
|
title: '',
|
|
|
|
description: '',
|
|
|
|
});
|
2025-03-01 05:48:24 +08:00
|
|
|
|
2025-02-27 06:54:19 +08:00
|
|
|
const knowledgeList = [
|
|
|
|
{
|
2025-03-04 07:22:05 +08:00
|
|
|
id: '1',
|
2025-02-27 06:54:19 +08:00
|
|
|
title: '产品开发知识库',
|
|
|
|
description: '产品开发流程及规范说明文档',
|
|
|
|
documents: 24,
|
|
|
|
date: '2025-02-15',
|
|
|
|
access: 'full',
|
|
|
|
},
|
|
|
|
{
|
2025-03-04 07:22:05 +08:00
|
|
|
id: '2',
|
2025-02-27 06:54:19 +08:00
|
|
|
title: '市场分析知识库',
|
|
|
|
description: '2025年Q1市场分析总结',
|
|
|
|
documents: 12,
|
|
|
|
date: '2025-02-10',
|
|
|
|
access: 'read',
|
|
|
|
},
|
2025-03-04 07:22:05 +08:00
|
|
|
{
|
|
|
|
id: '3',
|
|
|
|
title: '财务知识库',
|
|
|
|
description: '月度财务分析报告',
|
|
|
|
documents: 8,
|
|
|
|
date: '2025-02-01',
|
|
|
|
access: 'none',
|
|
|
|
},
|
2025-02-27 06:54:19 +08:00
|
|
|
];
|
|
|
|
|
2025-03-04 07:22:05 +08:00
|
|
|
const handleInputChange = (e) => {
|
|
|
|
const { name, value } = e.target;
|
|
|
|
setNewKnowledgeBase((prev) => ({
|
|
|
|
...prev,
|
|
|
|
[name]: value,
|
|
|
|
}));
|
2025-03-04 08:37:47 +08:00
|
|
|
|
|
|
|
// Clear error when user types
|
|
|
|
if (formErrors[name]) {
|
|
|
|
setFormErrors((prev) => ({
|
|
|
|
...prev,
|
|
|
|
[name]: '',
|
|
|
|
}));
|
|
|
|
}
|
2025-03-04 07:22:05 +08:00
|
|
|
};
|
|
|
|
|
2025-03-04 08:37:47 +08:00
|
|
|
const handleAccessRequestInputChange = (e) => {
|
|
|
|
const { name, value } = e.target;
|
|
|
|
setAccessRequestData((prev) => ({
|
|
|
|
...prev,
|
|
|
|
[name]: value,
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Clear error when user types
|
|
|
|
if (accessRequestErrors[name]) {
|
|
|
|
setAccessRequestErrors((prev) => ({
|
|
|
|
...prev,
|
|
|
|
[name]: '',
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const validateCreateForm = () => {
|
|
|
|
const errors = {};
|
|
|
|
|
2025-03-04 07:22:05 +08:00
|
|
|
if (!newKnowledgeBase.title.trim()) {
|
2025-03-04 08:37:47 +08:00
|
|
|
errors.title = '请输入知识库名称';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!newKnowledgeBase.description.trim()) {
|
|
|
|
errors.description = '请输入知识库描述';
|
|
|
|
}
|
|
|
|
|
|
|
|
setFormErrors(errors);
|
|
|
|
return Object.keys(errors).length === 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const validateAccessRequestForm = () => {
|
|
|
|
const errors = {};
|
|
|
|
|
|
|
|
if (!accessRequestData.projectInfo.trim()) {
|
|
|
|
errors.projectInfo = '请输入项目信息';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!accessRequestData.reason.trim()) {
|
|
|
|
errors.reason = '请输入申请原因';
|
|
|
|
}
|
|
|
|
|
|
|
|
setAccessRequestErrors(errors);
|
|
|
|
return Object.keys(errors).length === 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCreateKnowledgeBase = () => {
|
|
|
|
// Validate form
|
|
|
|
if (!validateCreateForm()) {
|
2025-03-04 07:22:05 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For now, just show a success notification
|
|
|
|
dispatch(
|
|
|
|
showNotification({
|
|
|
|
message: '知识库创建成功',
|
|
|
|
type: 'success',
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// In a real application, you would get the ID from the API response
|
|
|
|
// For now, we'll generate a mock ID
|
|
|
|
const newId = Date.now().toString();
|
|
|
|
|
|
|
|
// Reset form and close modal
|
|
|
|
setNewKnowledgeBase({ title: '', description: '' });
|
2025-03-04 08:37:47 +08:00
|
|
|
setFormErrors({});
|
2025-03-04 07:22:05 +08:00
|
|
|
setShowCreateModal(false);
|
|
|
|
|
|
|
|
// Navigate to the newly created knowledge base with datasets tab
|
|
|
|
navigate(`/knowledge-base/${newId}/datasets`);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCardClick = (id) => {
|
|
|
|
navigate(`/knowledge-base/${id}/datasets`);
|
|
|
|
};
|
|
|
|
|
2025-03-04 08:37:47 +08:00
|
|
|
const handleRequestAccess = (id, title) => {
|
|
|
|
setAccessRequestData((prev) => ({
|
|
|
|
...prev,
|
|
|
|
id,
|
|
|
|
title,
|
|
|
|
}));
|
|
|
|
setAccessRequestErrors({});
|
|
|
|
setShowAccessRequestModal(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleSubmitAccessRequest = () => {
|
|
|
|
// Validate form
|
|
|
|
if (!validateAccessRequestForm()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Here you would typically call an API to submit the access request
|
|
|
|
dispatch(
|
|
|
|
showNotification({
|
|
|
|
message: '权限申请已提交',
|
|
|
|
type: 'success',
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// Reset form and close modal
|
|
|
|
setAccessRequestData((prev) => ({
|
|
|
|
...prev,
|
|
|
|
projectInfo: '',
|
|
|
|
reason: '',
|
|
|
|
}));
|
|
|
|
setAccessRequestErrors({});
|
|
|
|
setShowAccessRequestModal(false);
|
|
|
|
};
|
|
|
|
|
2025-02-27 06:54:19 +08:00
|
|
|
return (
|
|
|
|
<div className='container'>
|
|
|
|
<div className='d-flex justify-content-between align-items-center mb-3'>
|
|
|
|
<input type='text' className='form-control w-50' placeholder='搜索知识库...' />
|
2025-03-04 07:22:05 +08:00
|
|
|
<button
|
|
|
|
className='btn btn-dark d-flex align-items-center gap-1'
|
|
|
|
onClick={() => setShowCreateModal(true)}
|
|
|
|
>
|
2025-03-01 08:34:56 +08:00
|
|
|
<SvgIcon className={'plus'} />
|
2025-02-27 06:54:19 +08:00
|
|
|
新建知识库
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
2025-03-04 07:22:05 +08:00
|
|
|
<div className='row gap-3 m-0'>
|
|
|
|
{knowledgeList.map((item) => (
|
|
|
|
<React.Fragment key={item.id}>
|
2025-03-04 08:37:47 +08:00
|
|
|
<KnowledgeCard
|
|
|
|
{...item}
|
|
|
|
onClick={() => handleCardClick(item.id)}
|
|
|
|
onRequestAccess={handleRequestAccess}
|
|
|
|
/>
|
2025-03-04 07:22:05 +08:00
|
|
|
</React.Fragment>
|
2025-02-27 06:54:19 +08:00
|
|
|
))}
|
|
|
|
</div>
|
2025-03-04 07:22:05 +08:00
|
|
|
|
|
|
|
{/* 新建知识库弹窗 */}
|
|
|
|
{showCreateModal && (
|
|
|
|
<div
|
|
|
|
className='modal-backdrop'
|
|
|
|
style={{
|
|
|
|
position: 'fixed',
|
|
|
|
top: 0,
|
|
|
|
left: 0,
|
|
|
|
right: 0,
|
|
|
|
bottom: 0,
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'center',
|
|
|
|
zIndex: 1050,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className='modal-content bg-white rounded shadow'
|
|
|
|
style={{
|
|
|
|
width: '500px',
|
|
|
|
maxWidth: '90%',
|
|
|
|
padding: '20px',
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div className='modal-header d-flex justify-content-between align-items-center mb-3'>
|
|
|
|
<h5 className='modal-title m-0'>新建知识库</h5>
|
|
|
|
<button
|
|
|
|
type='button'
|
|
|
|
className='btn-close'
|
|
|
|
onClick={() => setShowCreateModal(false)}
|
|
|
|
aria-label='Close'
|
|
|
|
></button>
|
|
|
|
</div>
|
|
|
|
<div className='modal-body'>
|
|
|
|
<div className='mb-3'>
|
|
|
|
<label htmlFor='knowledgeTitle' className='form-label'>
|
2025-03-04 08:37:47 +08:00
|
|
|
知识库名称 <span className='text-danger'>*</span>
|
2025-03-04 07:22:05 +08:00
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type='text'
|
2025-03-04 08:37:47 +08:00
|
|
|
className={`form-control ${formErrors.title ? 'is-invalid' : ''}`}
|
2025-03-04 07:22:05 +08:00
|
|
|
id='knowledgeTitle'
|
|
|
|
name='title'
|
|
|
|
value={newKnowledgeBase.title}
|
|
|
|
onChange={handleInputChange}
|
|
|
|
placeholder='请输入知识库名称'
|
|
|
|
required
|
|
|
|
/>
|
2025-03-04 08:37:47 +08:00
|
|
|
{formErrors.title && <div className='invalid-feedback'>{formErrors.title}</div>}
|
2025-03-04 07:22:05 +08:00
|
|
|
</div>
|
|
|
|
<div className='mb-3'>
|
|
|
|
<label htmlFor='knowledgeDescription' className='form-label'>
|
2025-03-04 08:37:47 +08:00
|
|
|
知识库描述 <span className='text-danger'>*</span>
|
2025-03-04 07:22:05 +08:00
|
|
|
</label>
|
|
|
|
<textarea
|
2025-03-04 08:37:47 +08:00
|
|
|
className={`form-control ${formErrors.description ? 'is-invalid' : ''}`}
|
2025-03-04 07:22:05 +08:00
|
|
|
id='knowledgeDescription'
|
|
|
|
name='description'
|
|
|
|
value={newKnowledgeBase.description}
|
|
|
|
onChange={handleInputChange}
|
|
|
|
placeholder='请输入知识库描述'
|
|
|
|
rows='3'
|
2025-03-04 08:37:47 +08:00
|
|
|
required
|
2025-03-04 07:22:05 +08:00
|
|
|
></textarea>
|
2025-03-04 08:37:47 +08:00
|
|
|
{formErrors.description && (
|
|
|
|
<div className='invalid-feedback'>{formErrors.description}</div>
|
|
|
|
)}
|
2025-03-04 07:22:05 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className='modal-footer d-flex justify-content-end gap-2'>
|
|
|
|
<button
|
|
|
|
type='button'
|
|
|
|
className='btn btn-outline-secondary'
|
|
|
|
onClick={() => setShowCreateModal(false)}
|
|
|
|
>
|
|
|
|
取消
|
|
|
|
</button>
|
|
|
|
<button type='button' className='btn btn-dark' onClick={handleCreateKnowledgeBase}>
|
|
|
|
创建
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
2025-03-04 08:37:47 +08:00
|
|
|
|
|
|
|
{/* 申请访问权限弹窗 */}
|
|
|
|
{showAccessRequestModal && (
|
|
|
|
<div
|
|
|
|
className='modal-backdrop'
|
|
|
|
style={{
|
|
|
|
position: 'fixed',
|
|
|
|
top: 0,
|
|
|
|
left: 0,
|
|
|
|
right: 0,
|
|
|
|
bottom: 0,
|
|
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: 'center',
|
|
|
|
zIndex: 1050,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className='modal-content bg-white rounded shadow'
|
|
|
|
style={{
|
|
|
|
width: '500px',
|
|
|
|
maxWidth: '90%',
|
|
|
|
padding: '20px',
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div className='modal-header d-flex justify-content-between align-items-center mb-3'>
|
|
|
|
<h5 className='modal-title m-0'>申请访问权限</h5>
|
|
|
|
<button
|
|
|
|
type='button'
|
|
|
|
className='btn-close'
|
|
|
|
onClick={() => setShowAccessRequestModal(false)}
|
|
|
|
aria-label='Close'
|
|
|
|
></button>
|
|
|
|
</div>
|
|
|
|
<div className='modal-body'>
|
|
|
|
<div className='mb-4'>
|
|
|
|
<label className='form-label d-flex align-items-center gap-1'>
|
|
|
|
<SvgIcon className='key' />
|
|
|
|
访问级别 <span className='text-danger'>*</span>
|
|
|
|
</label>
|
|
|
|
<div className='d-flex gap-2'>
|
|
|
|
<div
|
|
|
|
className={`p-3 rounded border bg-warning-subtle ${
|
|
|
|
accessRequestData.accessType === '只读访问'
|
|
|
|
? 'border-warning bg-warning-subtle'
|
|
|
|
: 'border-warning-subtle opacity-50'
|
|
|
|
}`}
|
|
|
|
style={{ flex: 1, cursor: 'pointer' }}
|
|
|
|
onClick={() =>
|
|
|
|
setAccessRequestData((prev) => ({ ...prev, accessType: '只读访问' }))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div className='text-center text-warning fw-bold mb-1'>只读访问</div>
|
|
|
|
<div className='text-center text-muted small'>仅查看数据集内容</div>
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className={`p-3 rounded border bg-success-subtle ${
|
|
|
|
accessRequestData.accessType === '完全访问'
|
|
|
|
? 'border-success'
|
|
|
|
: 'border-success-subtle opacity-50'
|
|
|
|
}`}
|
|
|
|
style={{ flex: 1, cursor: 'pointer' }}
|
|
|
|
onClick={() =>
|
|
|
|
setAccessRequestData((prev) => ({ ...prev, accessType: '完全访问' }))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div className='text-center text-success fw-bold mb-1'>完全访问</div>
|
|
|
|
<div className='text-center text-muted small'>查看、编辑和管理数据</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className='mb-3'>
|
|
|
|
<label className='form-label d-flex align-items-center gap-1'>
|
|
|
|
<SvgIcon className='calendar' />
|
|
|
|
访问时长 <span className='text-danger'>*</span>
|
|
|
|
</label>
|
|
|
|
<select
|
|
|
|
className='form-select'
|
|
|
|
name='duration'
|
|
|
|
value={accessRequestData.duration}
|
|
|
|
onChange={handleAccessRequestInputChange}
|
|
|
|
required
|
|
|
|
>
|
|
|
|
<option value='一周'>一周</option>
|
|
|
|
<option value='一个月'>一个月</option>
|
|
|
|
<option value='三个月'>三个月</option>
|
|
|
|
<option value='六个月'>六个月</option>
|
|
|
|
<option value='永久'>永久</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className='mb-3'>
|
|
|
|
<label className='form-label d-flex align-items-center gap-1'>
|
|
|
|
<SvgIcon className='clipboard' />
|
|
|
|
项目信息 <span className='text-danger'>*</span>
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type='text'
|
|
|
|
className={`form-control ${accessRequestErrors.projectInfo ? 'is-invalid' : ''}`}
|
|
|
|
name='projectInfo'
|
|
|
|
value={accessRequestData.projectInfo}
|
|
|
|
onChange={handleAccessRequestInputChange}
|
|
|
|
placeholder='请输入项目信息'
|
|
|
|
required
|
|
|
|
/>
|
|
|
|
{accessRequestErrors.projectInfo && (
|
|
|
|
<div className='invalid-feedback'>{accessRequestErrors.projectInfo}</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className='mb-3'>
|
|
|
|
<label className='form-label d-flex align-items-center gap-1'>
|
|
|
|
<SvgIcon className='chat' />
|
|
|
|
申请原因 <span className='text-danger'>*</span>
|
|
|
|
</label>
|
|
|
|
<textarea
|
|
|
|
className={`form-control ${accessRequestErrors.reason ? 'is-invalid' : ''}`}
|
|
|
|
name='reason'
|
|
|
|
value={accessRequestData.reason}
|
|
|
|
onChange={handleAccessRequestInputChange}
|
|
|
|
placeholder='请输入申请原因'
|
|
|
|
rows='4'
|
|
|
|
required
|
|
|
|
></textarea>
|
|
|
|
{accessRequestErrors.reason && (
|
|
|
|
<div className='invalid-feedback'>{accessRequestErrors.reason}</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className='modal-footer d-flex justify-content-end gap-2'>
|
|
|
|
<button
|
|
|
|
type='button'
|
|
|
|
className='btn btn-outline-secondary'
|
|
|
|
onClick={() => setShowAccessRequestModal(false)}
|
|
|
|
>
|
|
|
|
取消
|
|
|
|
</button>
|
|
|
|
<button type='button' className='btn btn-dark' onClick={handleSubmitAccessRequest}>
|
|
|
|
提交申请
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
2025-02-27 06:54:19 +08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|