mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-07 23:22:26 +08:00
[dev]Update Knowledge base detail page
Add mock data
This commit is contained in:
parent
6c1f0f3166
commit
4915514bde
15
package-lock.json
generated
15
package-lock.json
generated
@ -17,7 +17,8 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.2.0",
|
||||
"redux-persist": "^6.0.0"
|
||||
"redux-persist": "^6.0.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
@ -4540,6 +4541,18 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.2.0",
|
||||
"redux-persist": "^6.0.0"
|
||||
"redux-persist": "^6.0.0",
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
|
@ -270,7 +270,7 @@ export default function DatasetTab({ knowledgeBase }) {
|
||||
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||
<div className='d-flex gap-2'>
|
||||
<button
|
||||
className='btn btn-primary d-flex align-items-center gap-1'
|
||||
className='btn btn-dark d-flex align-items-center gap-1'
|
||||
onClick={() => setShowAddFileModal(true)}
|
||||
>
|
||||
<SvgIcon className='plus' />
|
||||
@ -326,7 +326,35 @@ export default function DatasetTab({ knowledgeBase }) {
|
||||
onSelectDocument={handleSelectDocument}
|
||||
onDeleteDocument={handleDeleteDocument}
|
||||
/>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className='d-flex justify-content-between align-items-center mt-3'>
|
||||
<div>
|
||||
每页行数:
|
||||
<select className='form-select form-select d-inline-block ms-2' style={{ width: '70px' }}>
|
||||
<option value='5'>5</option>
|
||||
<option value='10'>10</option>
|
||||
<option value='20'>20</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className='d-flex align-items-center'>
|
||||
<span className='me-3'>1-5 of 10</span>
|
||||
<nav aria-label='Page navigation'>
|
||||
<ul className='pagination pagination mb-0'>
|
||||
<li className='page-item'>
|
||||
<button className='page-link' aria-label='Previous'>
|
||||
<span aria-hidden='true'>«</span>
|
||||
</button>
|
||||
</li>
|
||||
<li className='page-item'>
|
||||
<button className='page-link' aria-label='Next'>
|
||||
<span aria-hidden='true'>»</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File upload modal */}
|
||||
<FileUploadModal
|
||||
show={showAddFileModal}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { showNotification } from '../../store/notification.slice';
|
||||
import SvgIcon from '../../components/SvgIcon';
|
||||
import DatasetTab from './Detail/DatasetTab';
|
||||
import SettingsTab from './Detail/SettingsTab';
|
||||
import { showNotification } from '../../../store/notification.slice';
|
||||
import SvgIcon from '../../../components/SvgIcon';
|
||||
import DatasetTab from './DatasetTab';
|
||||
import SettingsTab from './SettingsTab';
|
||||
|
||||
export default function KnowledgeBaseDetail() {
|
||||
const { id, tab } = useParams();
|
||||
@ -60,7 +60,9 @@ export default function KnowledgeBaseDetail() {
|
||||
<div className='col-md-3 col-lg-2 border-end'>
|
||||
<div className='py-4'>
|
||||
<div className='h4 mb-3 text-center'>{knowledgeBase.name}</div>
|
||||
<p className='text-center text-muted small mb-4'>{knowledgeBase.desc}</p>
|
||||
<p className='text-center text-muted small mb-4'>
|
||||
{knowledgeBase.desc || knowledgeBase.description || ''}
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
|
@ -8,6 +8,7 @@ import { updateKnowledgeBase, deleteKnowledgeBase } from '../../../store/knowled
|
||||
import Breadcrumb from './components/Breadcrumb';
|
||||
import KnowledgeBaseForm from './components/KnowledgeBaseForm';
|
||||
import DeleteConfirmModal from './components/DeleteConfirmModal';
|
||||
import UserPermissionsManager from './components/UserPermissionsManager';
|
||||
|
||||
export default function SettingsTab({ knowledgeBase }) {
|
||||
const dispatch = useDispatch();
|
||||
@ -16,114 +17,13 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
// State for knowledge base form
|
||||
const [knowledgeBaseForm, setKnowledgeBaseForm] = useState({
|
||||
name: knowledgeBase.name,
|
||||
desc: knowledgeBase.desc,
|
||||
desc: knowledgeBase.desc || knowledgeBase.description || '',
|
||||
type: knowledgeBase.type || 'private', // 默认为私有知识库
|
||||
});
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
|
||||
// State for pagination
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const usersPerPage = 10;
|
||||
|
||||
// State for edit modal
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [editUser, setEditUser] = useState(null);
|
||||
|
||||
// Mock data for users with permissions - convert to state so we can update it
|
||||
const [users, setUsers] = useState([
|
||||
{
|
||||
id: '1001',
|
||||
username: '张三',
|
||||
email: 'zhang@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
},
|
||||
{
|
||||
id: '1002',
|
||||
username: '李四',
|
||||
email: 'li@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '永久',
|
||||
},
|
||||
{
|
||||
id: '1003',
|
||||
username: '王五',
|
||||
email: 'wang@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '三个月',
|
||||
},
|
||||
{
|
||||
id: '1004',
|
||||
username: '赵六',
|
||||
email: 'zhao@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '六个月',
|
||||
},
|
||||
{
|
||||
id: '1005',
|
||||
username: '钱七',
|
||||
email: 'qian@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一周',
|
||||
},
|
||||
{
|
||||
id: '1006',
|
||||
username: '孙八',
|
||||
email: 'sun@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
},
|
||||
{
|
||||
id: '1007',
|
||||
username: '周九',
|
||||
email: 'zhou@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '永久',
|
||||
},
|
||||
{
|
||||
id: '1008',
|
||||
username: '吴十',
|
||||
email: 'wu@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '三个月',
|
||||
},
|
||||
{
|
||||
id: '1009',
|
||||
username: '郑十一',
|
||||
email: 'zheng@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '六个月',
|
||||
},
|
||||
{
|
||||
id: '1010',
|
||||
username: '冯十二',
|
||||
email: 'feng@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一周',
|
||||
},
|
||||
{
|
||||
id: '1011',
|
||||
username: '陈十三',
|
||||
email: 'chen@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
},
|
||||
{
|
||||
id: '1012',
|
||||
username: '褚十四',
|
||||
email: 'chu@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '永久',
|
||||
},
|
||||
]);
|
||||
|
||||
// Get current users for pagination
|
||||
const indexOfLastUser = currentPage * usersPerPage;
|
||||
const indexOfFirstUser = indexOfLastUser - usersPerPage;
|
||||
const currentUsers = users.slice(indexOfFirstUser, indexOfLastUser);
|
||||
const totalPages = Math.ceil(users.length / usersPerPage);
|
||||
|
||||
// Handle knowledge base form input change
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
@ -153,6 +53,10 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
errors.desc = '请输入知识库描述';
|
||||
}
|
||||
|
||||
if (!knowledgeBaseForm.type) {
|
||||
errors.type = '请选择知识库类型';
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
@ -175,6 +79,8 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
data: {
|
||||
name: knowledgeBaseForm.name,
|
||||
desc: knowledgeBaseForm.desc,
|
||||
description: knowledgeBaseForm.desc, // Add description field for compatibility
|
||||
type: knowledgeBaseForm.type,
|
||||
},
|
||||
})
|
||||
)
|
||||
@ -228,83 +134,6 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
});
|
||||
};
|
||||
|
||||
// Handle edit user permissions
|
||||
const handleEditUser = (user) => {
|
||||
setEditUser({ ...user });
|
||||
setFormErrors({});
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
// Handle input change in edit modal
|
||||
const handleEditInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setEditUser((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
|
||||
// Clear error if exists
|
||||
if (formErrors[name]) {
|
||||
setFormErrors((prev) => ({
|
||||
...prev,
|
||||
[name]: '',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Validate edit form
|
||||
const validateEditForm = () => {
|
||||
const errors = {};
|
||||
|
||||
if (!editUser.permissionType) {
|
||||
errors.permissionType = '请选择权限类型';
|
||||
}
|
||||
|
||||
if (!editUser.accessDuration) {
|
||||
errors.accessDuration = '请选择访问时长';
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
// Handle save user permissions
|
||||
const handleSaveUserPermissions = () => {
|
||||
// Validate form
|
||||
if (!validateEditForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Here you would typically call an API to update the user permissions
|
||||
console.log('Updating user permissions:', editUser);
|
||||
|
||||
// Update the users array with the edited user
|
||||
setUsers((prevUsers) => prevUsers.map((user) => (user.id === editUser.id ? { ...editUser } : user)));
|
||||
|
||||
// Show success notification (in a real app)
|
||||
dispatch(showNotification({ message: '用户权限已更新', type: 'success' }));
|
||||
|
||||
// Close modal
|
||||
setShowEditModal(false);
|
||||
};
|
||||
|
||||
// Handle pagination
|
||||
const handlePageChange = (pageNumber) => {
|
||||
setCurrentPage(pageNumber);
|
||||
};
|
||||
|
||||
// Handle delete user
|
||||
const handleDeleteUser = (userId) => {
|
||||
// Here you would typically call an API to delete the user
|
||||
console.log('Deleting user:', userId);
|
||||
|
||||
// Update the users array by removing the deleted user
|
||||
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
||||
|
||||
// Show success notification (in a real app)
|
||||
dispatch(showNotification({ message: '用户已删除', type: 'success' }));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Breadcrumb navigation */}
|
||||
@ -315,12 +144,14 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
formData={knowledgeBaseForm}
|
||||
formErrors={formErrors}
|
||||
isSubmitting={isSubmitting}
|
||||
knowledgeBase={knowledgeBase}
|
||||
onInputChange={handleInputChange}
|
||||
onSubmit={handleSubmit}
|
||||
onDelete={() => setShowDeleteConfirm(true)}
|
||||
/>
|
||||
|
||||
{/* User Permissions Manager */}
|
||||
<UserPermissionsManager knowledgeBase={knowledgeBase} />
|
||||
|
||||
{/* Delete confirmation modal */}
|
||||
<DeleteConfirmModal
|
||||
show={showDeleteConfirm}
|
||||
@ -329,143 +160,6 @@ export default function SettingsTab({ knowledgeBase }) {
|
||||
onCancel={() => setShowDeleteConfirm(false)}
|
||||
onConfirm={handleDelete}
|
||||
/>
|
||||
|
||||
{/* Edit User Permissions Modal */}
|
||||
{showEditModal && (
|
||||
<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={() => setShowEditModal(false)}
|
||||
aria-label='Close'
|
||||
></button>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
<div className='mb-3'>
|
||||
<label className='form-label'>用户信息</label>
|
||||
<div className='card bg-light'>
|
||||
<div className='card-body'>
|
||||
<div className='d-flex justify-content-between mb-2'>
|
||||
<span className='text-muted'>ID:</span>
|
||||
<span>#{editUser?.id}</span>
|
||||
</div>
|
||||
<div className='d-flex justify-content-between mb-2'>
|
||||
<span className='text-muted'>用户名:</span>
|
||||
<span>{editUser?.username}</span>
|
||||
</div>
|
||||
<div className='d-flex justify-content-between'>
|
||||
<span className='text-muted'>邮箱:</span>
|
||||
<span>{editUser?.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-4'>
|
||||
<label className='form-label d-flex align-items-center'>
|
||||
<SvgIcon className='key me-2' width='16' height='16' />
|
||||
权限类型 <span className='text-danger'>*</span>
|
||||
</label>
|
||||
<div className='d-flex gap-2'>
|
||||
<div
|
||||
className={`p-3 rounded border bg-warning-subtle ${
|
||||
editUser?.permissionType === '只读'
|
||||
? 'border-warning'
|
||||
: 'border-warning-subtle opacity-50'
|
||||
}`}
|
||||
style={{ flex: 1, cursor: 'pointer' }}
|
||||
onClick={() =>
|
||||
handleEditInputChange({ target: { name: 'permissionType', value: '只读' } })
|
||||
}
|
||||
>
|
||||
<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 ${
|
||||
editUser?.permissionType === '完全访问'
|
||||
? 'border-success'
|
||||
: 'border-success-subtle opacity-50'
|
||||
}`}
|
||||
style={{ flex: 1, cursor: 'pointer' }}
|
||||
onClick={() =>
|
||||
handleEditInputChange({
|
||||
target: { name: 'permissionType', value: '完全访问' },
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className='text-center text-success fw-bold mb-1'>完全访问</div>
|
||||
<div className='text-center text-muted small'>查看、编辑和管理数据</div>
|
||||
</div>
|
||||
</div>
|
||||
{formErrors.permissionType && (
|
||||
<div className='text-danger small mt-1'>{formErrors.permissionType}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='mb-3'>
|
||||
<label className='form-label d-flex align-items-center'>
|
||||
<SvgIcon className='clock me-2' width='16' height='16' />
|
||||
访问时长 <span className='text-danger'>*</span>
|
||||
</label>
|
||||
<select
|
||||
className={`form-select ${formErrors.accessDuration ? 'is-invalid' : ''}`}
|
||||
name='accessDuration'
|
||||
value={editUser?.accessDuration || ''}
|
||||
onChange={handleEditInputChange}
|
||||
required
|
||||
>
|
||||
<option value=''>请选择访问时长</option>
|
||||
<option value='一周'>一周</option>
|
||||
<option value='一个月'>一个月</option>
|
||||
<option value='三个月'>三个月</option>
|
||||
<option value='六个月'>六个月</option>
|
||||
<option value='永久'>永久</option>
|
||||
</select>
|
||||
{formErrors.accessDuration && (
|
||||
<div className='invalid-feedback'>{formErrors.accessDuration}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-footer d-flex justify-content-end gap-2'>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-outline-secondary'
|
||||
onClick={() => setShowEditModal(false)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button type='button' className='btn btn-dark' onClick={handleSaveUserPermissions}>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ const Breadcrumb = ({ knowledgeBase, activeTab }) => {
|
||||
<nav aria-label='breadcrumb'>
|
||||
<ol className='breadcrumb mb-0'>
|
||||
<li className='breadcrumb-item'>
|
||||
<Link className='text-secondary text-decoration-none' to='/knowledge-base'>
|
||||
<Link className='text-secondary text-decoration-none' to='/'>
|
||||
知识库
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -7,7 +7,6 @@ const KnowledgeBaseForm = ({
|
||||
formData,
|
||||
formErrors,
|
||||
isSubmitting,
|
||||
knowledgeBase,
|
||||
onInputChange,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
@ -54,10 +53,11 @@ const KnowledgeBaseForm = ({
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='radio'
|
||||
name='knowledgeType'
|
||||
name='type'
|
||||
id='typePrivate'
|
||||
checked={knowledgeBase.type === 'private'}
|
||||
readOnly
|
||||
value='private'
|
||||
checked={formData.type === 'private'}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='typePrivate'>
|
||||
私有知识库
|
||||
@ -67,27 +67,17 @@ const KnowledgeBaseForm = ({
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='radio'
|
||||
name='knowledgeType'
|
||||
name='type'
|
||||
id='typePublic'
|
||||
checked={knowledgeBase.type === 'public'}
|
||||
readOnly
|
||||
value='public'
|
||||
checked={formData.type === 'public'}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
<label className='form-check-label' htmlFor='typePublic'>
|
||||
公共知识库
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-3'>
|
||||
<label className='form-label'>创建时间</label>
|
||||
<p className='form-control-static'>{new Date(knowledgeBase.create_time).toLocaleString()}</p>
|
||||
</div>
|
||||
|
||||
<div className='mb-3'>
|
||||
<label className='form-label'>最后更新时间</label>
|
||||
<p className='form-control-static'>{new Date(knowledgeBase.update_time).toLocaleString()}</p>
|
||||
</div>
|
||||
|
||||
<div className='d-flex justify-content-between'>
|
||||
<button type='submit' className='btn btn-primary' disabled={isSubmitting}>
|
||||
{isSubmitting ? (
|
||||
|
@ -0,0 +1,789 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { showNotification } from '../../../../store/notification.slice';
|
||||
import SvgIcon from '../../../../components/SvgIcon';
|
||||
|
||||
/**
|
||||
* 用户权限管理组件
|
||||
*/
|
||||
const UserPermissionsManager = ({ knowledgeBase }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// State for pagination
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const usersPerPage = 10;
|
||||
|
||||
// State for edit modal
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [editUser, setEditUser] = useState(null);
|
||||
|
||||
// State for add user modal
|
||||
const [showAddUserModal, setShowAddUserModal] = useState(false);
|
||||
const [newUser, setNewUser] = useState({
|
||||
username: '',
|
||||
email: '',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
});
|
||||
|
||||
// State for batch operations
|
||||
const [selectedUsers, setSelectedUsers] = useState([]);
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
const [showBatchDropdown, setShowBatchDropdown] = useState(false);
|
||||
const batchDropdownRef = useRef(null);
|
||||
|
||||
// State for batch edit modal
|
||||
const [showBatchEditModal, setShowBatchEditModal] = useState(false);
|
||||
const [batchEditData, setBatchEditData] = useState({
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
});
|
||||
|
||||
// Form errors
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
|
||||
// Mock data for users with permissions
|
||||
const [users, setUsers] = useState([
|
||||
{
|
||||
id: '1001',
|
||||
username: '张三',
|
||||
email: 'zhang@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
},
|
||||
{
|
||||
id: '1002',
|
||||
username: '李四',
|
||||
email: 'li@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '永久',
|
||||
},
|
||||
{
|
||||
id: '1003',
|
||||
username: '王五',
|
||||
email: 'wang@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '三个月',
|
||||
},
|
||||
{
|
||||
id: '1004',
|
||||
username: '赵六',
|
||||
email: 'zhao@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '六个月',
|
||||
},
|
||||
{
|
||||
id: '1005',
|
||||
username: '钱七',
|
||||
email: 'qian@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一周',
|
||||
},
|
||||
{
|
||||
id: '1006',
|
||||
username: '孙八',
|
||||
email: 'sun@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
},
|
||||
{
|
||||
id: '1007',
|
||||
username: '周九',
|
||||
email: 'zhou@abc.com',
|
||||
permissionType: '完全访问',
|
||||
accessDuration: '永久',
|
||||
},
|
||||
{
|
||||
id: '1008',
|
||||
username: '吴十',
|
||||
email: 'wu@abc.com',
|
||||
permissionType: '只读',
|
||||
accessDuration: '三个月',
|
||||
},
|
||||
]);
|
||||
|
||||
// Get current users for pagination
|
||||
const indexOfLastUser = currentPage * usersPerPage;
|
||||
const indexOfFirstUser = indexOfLastUser - usersPerPage;
|
||||
const currentUsers = users.slice(indexOfFirstUser, indexOfLastUser);
|
||||
const totalPages = Math.ceil(users.length / usersPerPage);
|
||||
|
||||
// Handle click outside batch dropdown
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (batchDropdownRef.current && !batchDropdownRef.current.contains(event.target)) {
|
||||
setShowBatchDropdown(false);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle edit user permissions
|
||||
const handleEditUser = (user) => {
|
||||
setEditUser({ ...user });
|
||||
setFormErrors({});
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
// Handle input change in edit modal
|
||||
const handleEditInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setEditUser((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
|
||||
// Clear error if exists
|
||||
if (formErrors[name]) {
|
||||
setFormErrors((prev) => ({
|
||||
...prev,
|
||||
[name]: '',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle input change in add user modal
|
||||
const handleAddUserInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setNewUser((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
|
||||
// Clear error if exists
|
||||
if (formErrors[name]) {
|
||||
setFormErrors((prev) => ({
|
||||
...prev,
|
||||
[name]: '',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle input change in batch edit modal
|
||||
const handleBatchEditInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setBatchEditData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
// Validate edit form
|
||||
const validateEditForm = () => {
|
||||
const errors = {};
|
||||
|
||||
if (!editUser.username.trim()) {
|
||||
errors.username = '请输入用户名';
|
||||
}
|
||||
|
||||
if (!editUser.email.trim()) {
|
||||
errors.email = '请输入邮箱';
|
||||
} else if (!/\S+@\S+\.\S+/.test(editUser.email)) {
|
||||
errors.email = '请输入有效的邮箱地址';
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
// Validate add user form
|
||||
const validateAddUserForm = () => {
|
||||
const errors = {};
|
||||
|
||||
if (!newUser.username.trim()) {
|
||||
errors.username = '请输入用户名';
|
||||
}
|
||||
|
||||
if (!newUser.email.trim()) {
|
||||
errors.email = '请输入邮箱';
|
||||
} else if (!/\S+@\S+\.\S+/.test(newUser.email)) {
|
||||
errors.email = '请输入有效的邮箱地址';
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
// Handle save user permissions
|
||||
const handleSaveUserPermissions = () => {
|
||||
// Validate form
|
||||
if (!validateEditForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Here you would typically call an API to update the user permissions
|
||||
console.log('Updating user permissions:', editUser);
|
||||
|
||||
// Update the users array with the edited user
|
||||
setUsers((prevUsers) => prevUsers.map((user) => (user.id === editUser.id ? { ...editUser } : user)));
|
||||
|
||||
// Show success notification
|
||||
dispatch(showNotification({ message: '用户权限已更新', type: 'success' }));
|
||||
|
||||
// Close modal
|
||||
setShowEditModal(false);
|
||||
};
|
||||
|
||||
// Handle add new user
|
||||
const handleAddUser = () => {
|
||||
// Validate form
|
||||
if (!validateAddUserForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate a new ID
|
||||
const newId = `user-${Date.now()}`;
|
||||
|
||||
// Create new user object
|
||||
const userToAdd = {
|
||||
id: newId,
|
||||
username: newUser.username,
|
||||
email: newUser.email,
|
||||
permissionType: newUser.permissionType,
|
||||
accessDuration: newUser.accessDuration,
|
||||
};
|
||||
|
||||
// Add new user to the users array
|
||||
setUsers((prevUsers) => [...prevUsers, userToAdd]);
|
||||
|
||||
// Show success notification
|
||||
dispatch(showNotification({ message: '用户已添加', type: 'success' }));
|
||||
|
||||
// Reset form and close modal
|
||||
setNewUser({
|
||||
username: '',
|
||||
email: '',
|
||||
permissionType: '只读',
|
||||
accessDuration: '一个月',
|
||||
});
|
||||
setShowAddUserModal(false);
|
||||
};
|
||||
|
||||
// Handle delete user
|
||||
const handleDeleteUser = (userId) => {
|
||||
// Here you would typically call an API to delete the user
|
||||
console.log('Deleting user:', userId);
|
||||
|
||||
// Remove user from the users array
|
||||
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
||||
|
||||
// Remove from selected users if present
|
||||
setSelectedUsers((prev) => prev.filter((id) => id !== userId));
|
||||
|
||||
// Show success notification
|
||||
dispatch(showNotification({ message: '用户已删除', type: 'success' }));
|
||||
};
|
||||
|
||||
// Handle batch delete
|
||||
const handleBatchDelete = () => {
|
||||
if (selectedUsers.length === 0) return;
|
||||
|
||||
// Here you would typically call an API to delete the selected users
|
||||
console.log('Batch deleting users:', selectedUsers);
|
||||
|
||||
// Remove selected users from the users array
|
||||
setUsers((prevUsers) => prevUsers.filter((user) => !selectedUsers.includes(user.id)));
|
||||
|
||||
// Reset selection
|
||||
setSelectedUsers([]);
|
||||
setSelectAll(false);
|
||||
setShowBatchDropdown(false);
|
||||
|
||||
// Show success notification
|
||||
dispatch(showNotification({ message: `已删除 ${selectedUsers.length} 个用户`, type: 'success' }));
|
||||
};
|
||||
|
||||
// Handle batch edit
|
||||
const handleBatchEdit = () => {
|
||||
if (selectedUsers.length === 0) return;
|
||||
|
||||
// Here you would typically call an API to update the selected users
|
||||
console.log('Batch editing users:', selectedUsers, 'with data:', batchEditData);
|
||||
|
||||
// Update selected users in the users array
|
||||
setUsers((prevUsers) =>
|
||||
prevUsers.map((user) => (selectedUsers.includes(user.id) ? { ...user, ...batchEditData } : user))
|
||||
);
|
||||
|
||||
// Close modal
|
||||
setShowBatchEditModal(false);
|
||||
setShowBatchDropdown(false);
|
||||
|
||||
// Show success notification
|
||||
dispatch(showNotification({ message: `已更新 ${selectedUsers.length} 个用户的权限`, type: 'success' }));
|
||||
};
|
||||
|
||||
// Handle select all checkbox
|
||||
const handleSelectAll = () => {
|
||||
if (selectAll) {
|
||||
setSelectedUsers([]);
|
||||
} else {
|
||||
setSelectedUsers(currentUsers.map((user) => user.id));
|
||||
}
|
||||
setSelectAll(!selectAll);
|
||||
};
|
||||
|
||||
// Handle individual user selection
|
||||
const handleSelectUser = (userId) => {
|
||||
if (selectedUsers.includes(userId)) {
|
||||
setSelectedUsers(selectedUsers.filter((id) => id !== userId));
|
||||
setSelectAll(false);
|
||||
} else {
|
||||
setSelectedUsers([...selectedUsers, userId]);
|
||||
// Check if all current page users are now selected
|
||||
if (selectedUsers.length + 1 === currentUsers.length) {
|
||||
setSelectAll(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle page change
|
||||
const handlePageChange = (pageNumber) => {
|
||||
setCurrentPage(pageNumber);
|
||||
// Reset selection when changing pages
|
||||
setSelectedUsers([]);
|
||||
setSelectAll(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='card border-0 shadow-sm mt-4'>
|
||||
<div className='card-body'>
|
||||
<div className='d-flex justify-content-between align-items-center mb-4'>
|
||||
<h5 className='card-title mb-0'>用户权限管理</h5>
|
||||
<div className='d-flex gap-2'>
|
||||
{selectedUsers.length > 0 && (
|
||||
<div className='dropdown' ref={batchDropdownRef}>
|
||||
<button
|
||||
className='btn btn-outline-primary btn-sm dropdown-toggle'
|
||||
onClick={() => setShowBatchDropdown(!showBatchDropdown)}
|
||||
>
|
||||
批量操作 ({selectedUsers.length})
|
||||
</button>
|
||||
{showBatchDropdown && (
|
||||
<ul className='dropdown-menu show'>
|
||||
<li>
|
||||
<button
|
||||
className='dropdown-item'
|
||||
onClick={() => {
|
||||
setShowBatchEditModal(true);
|
||||
setShowBatchDropdown(false);
|
||||
}}
|
||||
>
|
||||
<SvgIcon className='edit' />
|
||||
<span className='ms-2'>批量修改权限</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className='dropdown-item text-danger' onClick={handleBatchDelete}>
|
||||
<SvgIcon className='trash' />
|
||||
<span className='ms-2'>批量删除</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<button className='btn btn-dark btn-sm' onClick={() => setShowAddUserModal(true)}>
|
||||
<SvgIcon className='plus' />
|
||||
添加用户
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='table-responsive'>
|
||||
<table className='table table-hover'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div className='form-check'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
checked={selectAll}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>权限类型</th>
|
||||
<th>访问期限</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentUsers.length > 0 ? (
|
||||
currentUsers.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td>
|
||||
<div className='form-check'>
|
||||
<input
|
||||
className='form-check-input'
|
||||
type='checkbox'
|
||||
checked={selectedUsers.includes(user.id)}
|
||||
onChange={() => handleSelectUser(user.id)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>
|
||||
<span
|
||||
className={`badge ${
|
||||
user.permissionType === '完全访问'
|
||||
? 'bg-success-subtle text-success'
|
||||
: 'bg-warning-subtle text-warning'
|
||||
}`}
|
||||
>
|
||||
{user.permissionType}
|
||||
</span>
|
||||
</td>
|
||||
<td>{user.accessDuration}</td>
|
||||
<td>
|
||||
<div className='btn-group'>
|
||||
<button
|
||||
className='btn btn-sm text-primary'
|
||||
onClick={() => handleEditUser(user)}
|
||||
>
|
||||
<SvgIcon className='edit' />
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-sm text-danger'
|
||||
onClick={() => handleDeleteUser(user.id)}
|
||||
>
|
||||
<SvgIcon className='trash' />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan='6' className='text-center'>
|
||||
暂无用户
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<nav aria-label='Page navigation'>
|
||||
<ul className='pagination justify-content-center'>
|
||||
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}>
|
||||
<button
|
||||
className='page-link'
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
</li>
|
||||
{[...Array(totalPages)].map((_, index) => (
|
||||
<li key={index} className={`page-item ${currentPage === index + 1 ? 'active' : ''}`}>
|
||||
<button className='page-link' onClick={() => handlePageChange(index + 1)}>
|
||||
{index + 1}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
<li className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`}>
|
||||
<button
|
||||
className='page-link'
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{/* Edit User Modal */}
|
||||
{showEditModal && (
|
||||
<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'>编辑用户权限</h5>
|
||||
<button
|
||||
type='button'
|
||||
className='btn-close'
|
||||
onClick={() => setShowEditModal(false)}
|
||||
></button>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
<form>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='username' className='form-label'>
|
||||
用户名
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className={`form-control ${formErrors.username ? 'is-invalid' : ''}`}
|
||||
id='username'
|
||||
name='username'
|
||||
value={editUser.username}
|
||||
onChange={handleEditInputChange}
|
||||
/>
|
||||
{formErrors.username && (
|
||||
<div className='invalid-feedback'>{formErrors.username}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='email' className='form-label'>
|
||||
邮箱
|
||||
</label>
|
||||
<input
|
||||
type='email'
|
||||
className={`form-control ${formErrors.email ? 'is-invalid' : ''}`}
|
||||
id='email'
|
||||
name='email'
|
||||
value={editUser.email}
|
||||
onChange={handleEditInputChange}
|
||||
/>
|
||||
{formErrors.email && (
|
||||
<div className='invalid-feedback'>{formErrors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='permissionType' className='form-label'>
|
||||
权限类型
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
id='permissionType'
|
||||
name='permissionType'
|
||||
value={editUser.permissionType}
|
||||
onChange={handleEditInputChange}
|
||||
>
|
||||
<option value='只读'>只读</option>
|
||||
<option value='完全访问'>完全访问</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='accessDuration' className='form-label'>
|
||||
访问期限
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
id='accessDuration'
|
||||
name='accessDuration'
|
||||
value={editUser.accessDuration}
|
||||
onChange={handleEditInputChange}
|
||||
>
|
||||
<option value='一周'>一周</option>
|
||||
<option value='一个月'>一个月</option>
|
||||
<option value='三个月'>三个月</option>
|
||||
<option value='六个月'>六个月</option>
|
||||
<option value='永久'>永久</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-secondary'
|
||||
onClick={() => setShowEditModal(false)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary'
|
||||
onClick={handleSaveUserPermissions}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add User Modal */}
|
||||
{showAddUserModal && (
|
||||
<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'>添加用户</h5>
|
||||
<button
|
||||
type='button'
|
||||
className='btn-close'
|
||||
onClick={() => setShowAddUserModal(false)}
|
||||
></button>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
<form>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='newUsername' className='form-label'>
|
||||
用户名
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className={`form-control ${formErrors.username ? 'is-invalid' : ''}`}
|
||||
id='newUsername'
|
||||
name='username'
|
||||
value={newUser.username}
|
||||
onChange={handleAddUserInputChange}
|
||||
/>
|
||||
{formErrors.username && (
|
||||
<div className='invalid-feedback'>{formErrors.username}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='newEmail' className='form-label'>
|
||||
邮箱
|
||||
</label>
|
||||
<input
|
||||
type='email'
|
||||
className={`form-control ${formErrors.email ? 'is-invalid' : ''}`}
|
||||
id='newEmail'
|
||||
name='email'
|
||||
value={newUser.email}
|
||||
onChange={handleAddUserInputChange}
|
||||
/>
|
||||
{formErrors.email && (
|
||||
<div className='invalid-feedback'>{formErrors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='newPermissionType' className='form-label'>
|
||||
权限类型
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
id='newPermissionType'
|
||||
name='permissionType'
|
||||
value={newUser.permissionType}
|
||||
onChange={handleAddUserInputChange}
|
||||
>
|
||||
<option value='只读'>只读</option>
|
||||
<option value='完全访问'>完全访问</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='newAccessDuration' className='form-label'>
|
||||
访问期限
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
id='newAccessDuration'
|
||||
name='accessDuration'
|
||||
value={newUser.accessDuration}
|
||||
onChange={handleAddUserInputChange}
|
||||
>
|
||||
<option value='一周'>一周</option>
|
||||
<option value='一个月'>一个月</option>
|
||||
<option value='三个月'>三个月</option>
|
||||
<option value='六个月'>六个月</option>
|
||||
<option value='永久'>永久</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-secondary'
|
||||
onClick={() => setShowAddUserModal(false)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button type='button' className='btn btn-primary' onClick={handleAddUser}>
|
||||
添加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Batch Edit Modal */}
|
||||
{showBatchEditModal && (
|
||||
<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'>批量修改权限</h5>
|
||||
<button
|
||||
type='button'
|
||||
className='btn-close'
|
||||
onClick={() => setShowBatchEditModal(false)}
|
||||
></button>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
<p>
|
||||
您正在修改 <strong>{selectedUsers.length}</strong> 个用户的权限
|
||||
</p>
|
||||
<form>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='batchPermissionType' className='form-label'>
|
||||
权限类型
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
id='batchPermissionType'
|
||||
name='permissionType'
|
||||
value={batchEditData.permissionType}
|
||||
onChange={handleBatchEditInputChange}
|
||||
>
|
||||
<option value='只读'>只读</option>
|
||||
<option value='完全访问'>完全访问</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='batchAccessDuration' className='form-label'>
|
||||
访问期限
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
id='batchAccessDuration'
|
||||
name='accessDuration'
|
||||
value={batchEditData.accessDuration}
|
||||
onChange={handleBatchEditInputChange}
|
||||
>
|
||||
<option value='一周'>一周</option>
|
||||
<option value='一个月'>一个月</option>
|
||||
<option value='三个月'>三个月</option>
|
||||
<option value='六个月'>六个月</option>
|
||||
<option value='永久'>永久</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-secondary'
|
||||
onClick={() => setShowBatchEditModal(false)}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button type='button' className='btn btn-primary' onClick={handleBatchEdit}>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal backdrop */}
|
||||
{(showEditModal || showAddUserModal || showBatchEditModal) && (
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPermissionsManager;
|
@ -246,6 +246,7 @@ export default function KnowledgeBase() {
|
||||
createKnowledgeBase({
|
||||
name: newKnowledgeBase.name,
|
||||
desc: newKnowledgeBase.desc,
|
||||
description: newKnowledgeBase.desc,
|
||||
type: 'private', // Default type
|
||||
})
|
||||
);
|
||||
@ -295,11 +296,17 @@ export default function KnowledgeBase() {
|
||||
setShowAccessRequestModal(false);
|
||||
};
|
||||
|
||||
const handleDelete = (e, id) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
console.log(id);
|
||||
};
|
||||
|
||||
// Calculate total pages
|
||||
const totalPages = Math.ceil(displayTotal / pagination.page_size);
|
||||
|
||||
return (
|
||||
<div className='knowledge-base container mt-4'>
|
||||
<div className='knowledge-base container my-4'>
|
||||
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||
<SearchBar
|
||||
searchKeyword={searchKeyword}
|
||||
@ -336,6 +343,7 @@ export default function KnowledgeBase() {
|
||||
isSearching={isSearching}
|
||||
onCardClick={handleCardClick}
|
||||
onRequestAccess={handleRequestAccess}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
|
||||
{/* Pagination */}
|
||||
@ -401,8 +409,47 @@ export default function KnowledgeBase() {
|
||||
<label className='form-label'>知识库名称</label>
|
||||
<input type='text' className='form-control' value={accessRequestData.title} readOnly />
|
||||
</div>
|
||||
{/* <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'>权限类型</label>
|
||||
<label className='form-label d-flex align-items-center gap-1'>
|
||||
<SvgIcon className='key' />
|
||||
访问级别 <span className='text-danger'>*</span>
|
||||
</label>
|
||||
<select
|
||||
className='form-select'
|
||||
name='accessType'
|
||||
@ -413,8 +460,12 @@ export default function KnowledgeBase() {
|
||||
<option value='编辑权限'>编辑权限</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className='mb-3'>
|
||||
<label className='form-label'>访问时长</label>
|
||||
<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'
|
||||
@ -429,7 +480,8 @@ export default function KnowledgeBase() {
|
||||
</select>
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='projectInfo' className='form-label'>
|
||||
<label className='form-label d-flex align-items-center gap-1'>
|
||||
<SvgIcon className='clipboard' />
|
||||
项目信息 <span className='text-danger'>*</span>
|
||||
</label>
|
||||
<input
|
||||
@ -446,7 +498,8 @@ export default function KnowledgeBase() {
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-3'>
|
||||
<label htmlFor='reason' className='form-label'>
|
||||
<label className='form-label d-flex align-items-center gap-1'>
|
||||
<SvgIcon className='chat' />
|
||||
申请原因 <span className='text-danger'>*</span>
|
||||
</label>
|
||||
<textarea
|
||||
@ -463,7 +516,7 @@ export default function KnowledgeBase() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<div className='modal-footer gap-2'>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-secondary'
|
||||
@ -471,7 +524,7 @@ export default function KnowledgeBase() {
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button type='button' className='btn btn-primary' onClick={handleSubmitAccessRequest}>
|
||||
<button type='button' className='btn btn-dark' onClick={handleSubmitAccessRequest}>
|
||||
提交申请
|
||||
</button>
|
||||
</div>
|
||||
|
@ -65,11 +65,11 @@ const CreateKnowledgeBaseModal = ({ show, formData, formErrors, isSubmitting, on
|
||||
{formErrors.desc && <div className='invalid-feedback'>{formErrors.desc}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<div className='modal-footer gap-2'>
|
||||
<button type='button' className='btn btn-secondary' onClick={onClose}>
|
||||
取消
|
||||
</button>
|
||||
<button type='button' className='btn btn-primary' onClick={onSubmit} disabled={isSubmitting}>
|
||||
<button type='button' className='btn btn-dark' onClick={onSubmit} disabled={isSubmitting}>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<span
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import KnowledgeCard from '../KnowledgeCard';
|
||||
import KnowledgeCard from './KnowledgeCard';
|
||||
|
||||
/**
|
||||
* 知识库列表组件
|
||||
*/
|
||||
const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequestAccess }) => {
|
||||
const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequestAccess, onDelete }) => {
|
||||
if (knowledgeBases.length === 0) {
|
||||
return (
|
||||
<div className='alert alert-warning'>
|
||||
@ -20,12 +20,13 @@ const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequest
|
||||
<KnowledgeCard
|
||||
id={item.id}
|
||||
title={item.name}
|
||||
description={item.desc}
|
||||
description={item.description || item.desc || ''}
|
||||
documents={item.document_count || 0}
|
||||
date={new Date(item.create_time).toLocaleDateString()}
|
||||
access={item.permissions.can_edit ? 'full' : item.permissions.can_read ? 'read' : 'none'}
|
||||
date={new Date(item.create_time || item.created_at).toLocaleDateString()}
|
||||
access={item.permissions?.can_edit ? 'full' : item.permissions?.can_read ? 'read' : 'none'}
|
||||
onClick={() => onCardClick(item.id)}
|
||||
onRequestAccess={onRequestAccess}
|
||||
onDelete={(e) => onDelete(e, item.id)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
@ -1,8 +1,18 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '../../components/SvgIcon';
|
||||
import SvgIcon from '../../../components/SvgIcon';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export default function KnowledgeCard({ id, title, description, documents, date, access, onClick, onRequestAccess }) {
|
||||
export default function KnowledgeCard({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
documents,
|
||||
date,
|
||||
access,
|
||||
onClick,
|
||||
onRequestAccess,
|
||||
onDelete,
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleNewChat = (e) => {
|
||||
@ -17,6 +27,16 @@ export default function KnowledgeCard({ id, title, description, documents, date,
|
||||
onRequestAccess(id, title);
|
||||
};
|
||||
|
||||
// 自定义样式,限制文本最多显示两行
|
||||
const descriptionStyle = {
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
minHeight: '3rem', // 保持一致的高度,即使描述很短
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='knowledge-card card shadow border-0 p-0 col' onClick={onClick}>
|
||||
<div className='card-body'>
|
||||
@ -26,13 +46,15 @@ export default function KnowledgeCard({ id, title, description, documents, date,
|
||||
<SvgIcon className={'more-dot'} />
|
||||
</button>
|
||||
<ul className='hoverdown-menu shadow bg-white p-1 rounded'>
|
||||
<li className='p-1 hoverdown-item px-2'>
|
||||
<li className='p-1 hoverdown-item px-2' onClick={onDelete}>
|
||||
删除
|
||||
<SvgIcon className={'trash'} />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p className='card-text text-muted'>{description}</p>
|
||||
<p className='card-text text-muted mb-3' style={descriptionStyle} title={description}>
|
||||
{description}
|
||||
</p>
|
||||
<div className='text-muted d-flex align-items-center gap-1'>
|
||||
<SvgIcon className={'file'} />
|
||||
{documents} 文档
|
@ -2,7 +2,7 @@ import React, { Suspense } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import Mainlayout from '../layouts/Mainlayout';
|
||||
import KnowledgeBase from '../pages/KnowledgeBase/KnowledgeBase';
|
||||
import KnowledgeBaseDetail from '../pages/KnowledgeBase/KnowledgeBaseDetail';
|
||||
import KnowledgeBaseDetail from '../pages/KnowledgeBase/Detail/KnowledgeBaseDetail';
|
||||
import Chat from '../pages/Chat/Chat';
|
||||
import Loading from '../components/Loading';
|
||||
import Login from '../pages/Auth/Login';
|
||||
|
@ -1,8 +1,12 @@
|
||||
import axios from 'axios';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { mockGet, mockPost, mockPut, mockDel } from './mockApi';
|
||||
|
||||
const secretKey = import.meta.env.VITE_SECRETKEY;
|
||||
|
||||
// Flag to enable/disable mock API
|
||||
const USE_MOCK_API = true; // Set to false to use real API
|
||||
|
||||
// Create Axios instance with base URL
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
@ -58,12 +62,30 @@ api.interceptors.response.use(
|
||||
|
||||
// Define common HTTP methods
|
||||
const get = async (url, params = {}) => {
|
||||
if (USE_MOCK_API) {
|
||||
try {
|
||||
const response = await mockGet(url, params);
|
||||
return { data: response };
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
const res = await api.get(url, { params });
|
||||
return res.data;
|
||||
};
|
||||
|
||||
// Handle POST requests for JSON data
|
||||
const post = async (url, data, isMultipart = false) => {
|
||||
if (USE_MOCK_API) {
|
||||
try {
|
||||
const response = await mockPost(url, data);
|
||||
return { data: response };
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
const headers = isMultipart
|
||||
? { 'Content-Type': 'multipart/form-data' } // For file uploads
|
||||
: { 'Content-Type': 'application/json' }; // For JSON data
|
||||
@ -74,6 +96,15 @@ const post = async (url, data, isMultipart = false) => {
|
||||
|
||||
// Handle PUT requests
|
||||
const put = async (url, data) => {
|
||||
if (USE_MOCK_API) {
|
||||
try {
|
||||
const response = await mockPut(url, data);
|
||||
return { data: response };
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
const res = await api.put(url, data, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
@ -82,11 +113,25 @@ const put = async (url, data) => {
|
||||
|
||||
// Handle DELETE requests
|
||||
const del = async (url) => {
|
||||
if (USE_MOCK_API) {
|
||||
try {
|
||||
const response = await mockDel(url);
|
||||
return { data: response };
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
const res = await api.delete(url);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
const upload = async (url, data) => {
|
||||
if (USE_MOCK_API) {
|
||||
console.warn('[MOCK API] Upload functionality is not mocked');
|
||||
return { success: true, message: 'Mock upload successful' };
|
||||
}
|
||||
|
||||
const axiosInstance = await axios.create({
|
||||
baseURL: '/api',
|
||||
headers: {
|
||||
@ -96,4 +141,5 @@ const upload = async (url, data) => {
|
||||
const res = await axiosInstance.post(url, data);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export { get, post, put, del, upload };
|
||||
|
352
src/services/mockApi.js
Normal file
352
src/services/mockApi.js
Normal file
@ -0,0 +1,352 @@
|
||||
// Mock API service for development without backend
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// Mock data for knowledge bases
|
||||
const mockKnowledgeBases = [
|
||||
{
|
||||
id: 'kb-001',
|
||||
name: 'Frontend Development',
|
||||
description: 'Resources and guides for frontend development including React, Vue, and Angular',
|
||||
created_at: '2023-10-15T08:30:00Z',
|
||||
updated_at: '2023-12-20T14:45:00Z',
|
||||
create_time: '2023-10-15T08:30:00Z',
|
||||
update_time: '2023-12-20T14:45:00Z',
|
||||
type: 'private',
|
||||
owner: {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
document_count: 15,
|
||||
tags: ['react', 'javascript', 'frontend'],
|
||||
permissions: {
|
||||
can_edit: true,
|
||||
can_read: true,
|
||||
},
|
||||
documents: [
|
||||
{
|
||||
id: 'doc-001',
|
||||
name: 'React Best Practices.pdf',
|
||||
description: 'A guide to React best practices and patterns',
|
||||
size: '1.2MB',
|
||||
create_time: '2023-10-20T09:15:00Z',
|
||||
update_time: '2023-10-20T09:15:00Z',
|
||||
},
|
||||
{
|
||||
id: 'doc-002',
|
||||
name: 'Vue.js Tutorial.docx',
|
||||
description: 'Step-by-step tutorial for Vue.js beginners',
|
||||
size: '850KB',
|
||||
create_time: '2023-11-05T14:30:00Z',
|
||||
update_time: '2023-11-05T14:30:00Z',
|
||||
},
|
||||
{
|
||||
id: 'doc-003',
|
||||
name: 'JavaScript ES6 Features.pdf',
|
||||
description: 'Overview of ES6 features and examples',
|
||||
size: '1.5MB',
|
||||
create_time: '2023-11-15T11:45:00Z',
|
||||
update_time: '2023-11-15T11:45:00Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'kb-002',
|
||||
name: 'Backend Technologies',
|
||||
description: 'Information about backend frameworks, databases, and server configurations',
|
||||
created_at: '2023-09-05T10:15:00Z',
|
||||
updated_at: '2024-01-10T09:20:00Z',
|
||||
create_time: '2023-09-05T10:15:00Z',
|
||||
update_time: '2024-01-10T09:20:00Z',
|
||||
type: 'private',
|
||||
owner: {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
document_count: 23,
|
||||
tags: ['nodejs', 'python', 'databases'],
|
||||
permissions: {
|
||||
can_edit: true,
|
||||
can_read: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'kb-003',
|
||||
name: 'DevOps Practices',
|
||||
description: 'Best practices for CI/CD, containerization, and cloud deployment',
|
||||
created_at: '2023-11-12T15:45:00Z',
|
||||
updated_at: '2024-02-05T11:30:00Z',
|
||||
create_time: '2023-11-12T15:45:00Z',
|
||||
update_time: '2024-02-05T11:30:00Z',
|
||||
type: 'public',
|
||||
owner: {
|
||||
id: 'user-002',
|
||||
username: 'janedoe',
|
||||
email: 'jane@example.com',
|
||||
},
|
||||
document_count: 18,
|
||||
tags: ['docker', 'kubernetes', 'aws'],
|
||||
permissions: {
|
||||
can_edit: false,
|
||||
can_read: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'kb-004',
|
||||
name: 'Machine Learning Fundamentals',
|
||||
description: 'Introduction to machine learning concepts, algorithms, and frameworks',
|
||||
created_at: '2023-08-20T09:00:00Z',
|
||||
updated_at: '2024-01-25T16:15:00Z',
|
||||
create_time: '2023-08-20T09:00:00Z',
|
||||
update_time: '2024-01-25T16:15:00Z',
|
||||
type: 'public',
|
||||
owner: {
|
||||
id: 'user-003',
|
||||
username: 'alexsmith',
|
||||
email: 'alex@example.com',
|
||||
},
|
||||
document_count: 30,
|
||||
tags: ['ml', 'python', 'tensorflow'],
|
||||
permissions: {
|
||||
can_edit: false,
|
||||
can_read: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'kb-005',
|
||||
name: 'UI/UX Design Principles',
|
||||
description: 'Guidelines for creating effective and user-friendly interfaces',
|
||||
created_at: '2023-12-01T13:20:00Z',
|
||||
updated_at: '2024-02-15T10:45:00Z',
|
||||
create_time: '2023-12-01T13:20:00Z',
|
||||
update_time: '2024-02-15T10:45:00Z',
|
||||
type: 'private',
|
||||
owner: {
|
||||
id: 'user-002',
|
||||
username: 'janedoe',
|
||||
email: 'jane@example.com',
|
||||
},
|
||||
document_count: 12,
|
||||
tags: ['design', 'ui', 'ux'],
|
||||
permissions: {
|
||||
can_edit: true,
|
||||
can_read: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'kb-006',
|
||||
name: 'Mobile App Development',
|
||||
description: 'Resources for iOS, Android, and cross-platform mobile development',
|
||||
created_at: '2023-10-25T11:10:00Z',
|
||||
updated_at: '2024-01-30T14:00:00Z',
|
||||
create_time: '2023-10-25T11:10:00Z',
|
||||
update_time: '2024-01-30T14:00:00Z',
|
||||
type: 'private',
|
||||
owner: {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
document_count: 20,
|
||||
tags: ['mobile', 'react-native', 'flutter'],
|
||||
permissions: {
|
||||
can_edit: true,
|
||||
can_read: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'kb-007',
|
||||
name: 'Cybersecurity Best Practices',
|
||||
description: 'Guidelines for securing applications, networks, and data',
|
||||
created_at: '2023-09-18T14:30:00Z',
|
||||
updated_at: '2024-02-10T09:15:00Z',
|
||||
create_time: '2023-09-18T14:30:00Z',
|
||||
update_time: '2024-02-10T09:15:00Z',
|
||||
type: 'private',
|
||||
owner: {
|
||||
id: 'user-004',
|
||||
username: 'sarahwilson',
|
||||
email: 'sarah@example.com',
|
||||
},
|
||||
document_count: 25,
|
||||
tags: ['security', 'encryption', 'authentication'],
|
||||
permissions: {
|
||||
can_edit: false,
|
||||
can_read: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'kb-008',
|
||||
name: 'Data Science Toolkit',
|
||||
description: 'Tools and techniques for data analysis, visualization, and modeling',
|
||||
created_at: '2023-11-05T10:00:00Z',
|
||||
updated_at: '2024-01-20T15:30:00Z',
|
||||
create_time: '2023-11-05T10:00:00Z',
|
||||
update_time: '2024-01-20T15:30:00Z',
|
||||
type: 'public',
|
||||
owner: {
|
||||
id: 'user-003',
|
||||
username: 'alexsmith',
|
||||
email: 'alex@example.com',
|
||||
},
|
||||
document_count: 28,
|
||||
tags: ['data-science', 'python', 'visualization'],
|
||||
permissions: {
|
||||
can_edit: false,
|
||||
can_read: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// In-memory store for CRUD operations
|
||||
let knowledgeBases = [...mockKnowledgeBases];
|
||||
|
||||
// Helper function for pagination
|
||||
const paginate = (array, page_size, page) => {
|
||||
const startIndex = (page - 1) * page_size;
|
||||
const endIndex = startIndex + page_size;
|
||||
const items = array.slice(startIndex, endIndex);
|
||||
|
||||
return {
|
||||
items,
|
||||
total: array.length,
|
||||
page,
|
||||
page_size,
|
||||
};
|
||||
};
|
||||
|
||||
// Mock API functions
|
||||
export const mockGet = async (url, params = {}) => {
|
||||
console.log(`[MOCK API] GET ${url}`, params);
|
||||
|
||||
// Simulate network delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// Knowledge bases list with pagination
|
||||
if (url === '/knowledge-bases/') {
|
||||
const { page = 1, page_size = 10 } = params;
|
||||
return paginate(knowledgeBases, page_size, page);
|
||||
}
|
||||
|
||||
// Knowledge base search
|
||||
if (url === '/knowledge-bases/search/') {
|
||||
const { keyword = '', page = 1, page_size = 10 } = params;
|
||||
const filtered = knowledgeBases.filter(
|
||||
(kb) =>
|
||||
kb.name.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
kb.description.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
kb.tags.some((tag) => tag.toLowerCase().includes(keyword.toLowerCase()))
|
||||
);
|
||||
return paginate(filtered, page_size, page);
|
||||
}
|
||||
|
||||
// Knowledge base details
|
||||
if (url.match(/^\/knowledge-bases\/[^/]+\/$/)) {
|
||||
const id = url.split('/')[2];
|
||||
const knowledgeBase = knowledgeBases.find((kb) => kb.id === id);
|
||||
|
||||
if (!knowledgeBase) {
|
||||
throw { response: { status: 404, data: { message: 'Knowledge base not found' } } };
|
||||
}
|
||||
|
||||
return knowledgeBase;
|
||||
}
|
||||
|
||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||
};
|
||||
|
||||
export const mockPost = async (url, data) => {
|
||||
console.log(`[MOCK API] POST ${url}`, data);
|
||||
|
||||
// Simulate network delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
|
||||
// Create knowledge base
|
||||
if (url === '/knowledge-bases/') {
|
||||
const newKnowledgeBase = {
|
||||
id: `kb-${uuidv4().slice(0, 8)}`,
|
||||
name: data.name,
|
||||
description: data.description || '',
|
||||
desc: data.desc || data.description || '',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
create_time: new Date().toISOString(),
|
||||
update_time: new Date().toISOString(),
|
||||
type: data.type || 'private',
|
||||
owner: {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
document_count: 0,
|
||||
tags: data.tags || [],
|
||||
permissions: {
|
||||
can_edit: true,
|
||||
can_read: true,
|
||||
},
|
||||
documents: [],
|
||||
};
|
||||
|
||||
knowledgeBases.push(newKnowledgeBase);
|
||||
return newKnowledgeBase;
|
||||
}
|
||||
|
||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||
};
|
||||
|
||||
export const mockPut = async (url, data) => {
|
||||
console.log(`[MOCK API] PUT ${url}`, data);
|
||||
|
||||
// Simulate network delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 600));
|
||||
|
||||
// Update knowledge base
|
||||
if (url.match(/^\/knowledge-bases\/[^/]+\/$/)) {
|
||||
const id = url.split('/')[2];
|
||||
const index = knowledgeBases.findIndex((kb) => kb.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
throw { response: { status: 404, data: { message: 'Knowledge base not found' } } };
|
||||
}
|
||||
|
||||
const updatedKnowledgeBase = {
|
||||
...knowledgeBases[index],
|
||||
...data,
|
||||
updated_at: new Date().toISOString(),
|
||||
update_time: new Date().toISOString(),
|
||||
};
|
||||
|
||||
knowledgeBases[index] = updatedKnowledgeBase;
|
||||
return updatedKnowledgeBase;
|
||||
}
|
||||
|
||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||
};
|
||||
|
||||
export const mockDel = async (url) => {
|
||||
console.log(`[MOCK API] DELETE ${url}`);
|
||||
|
||||
// Simulate network delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
|
||||
// Delete knowledge base
|
||||
if (url.match(/^\/knowledge-bases\/[^/]+\/$/)) {
|
||||
const id = url.split('/')[2];
|
||||
const index = knowledgeBases.findIndex((kb) => kb.id === id);
|
||||
|
||||
if (index === -1) {
|
||||
throw { response: { status: 404, data: { message: 'Knowledge base not found' } } };
|
||||
}
|
||||
|
||||
knowledgeBases.splice(index, 1);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||
};
|
||||
|
||||
// Reset mock data to initial state (useful for testing)
|
||||
export const resetMockData = () => {
|
||||
knowledgeBases = [...mockKnowledgeBases];
|
||||
};
|
Loading…
Reference in New Issue
Block a user