mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 07:20:55 +08:00
[dev]knowledgebase mock data & pending requests
This commit is contained in:
parent
6cf31165f9
commit
b4a0874a4d
@ -19,8 +19,12 @@ function App() {
|
|||||||
console.log('app handleCheckAuth');
|
console.log('app handleCheckAuth');
|
||||||
try {
|
try {
|
||||||
await dispatch(checkAuthThunk()).unwrap();
|
await dispatch(checkAuthThunk()).unwrap();
|
||||||
if (user) navigate('/');
|
console.log('user', user);
|
||||||
} catch (error) {}
|
if (!user) navigate('/login');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error', error);
|
||||||
|
navigate('/login');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AppRouter></AppRouter>;
|
return <AppRouter></AppRouter>;
|
||||||
|
83
src/components/ApiModeSwitch.jsx
Normal file
83
src/components/ApiModeSwitch.jsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { switchToMockApi, switchToRealApi, checkServerStatus } from '../services/api';
|
||||||
|
|
||||||
|
export default function ApiModeSwitch() {
|
||||||
|
const [isMockMode, setIsMockMode] = useState(false);
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
const [showNotification, setShowNotification] = useState(false);
|
||||||
|
const [notification, setNotification] = useState({ message: '', type: 'info' });
|
||||||
|
|
||||||
|
// 组件加载时检查服务器状态
|
||||||
|
useEffect(() => {
|
||||||
|
const checkStatus = async () => {
|
||||||
|
setIsChecking(true);
|
||||||
|
const isServerUp = await checkServerStatus();
|
||||||
|
setIsMockMode(!isServerUp);
|
||||||
|
setIsChecking(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 切换API模式
|
||||||
|
const handleToggleMode = async () => {
|
||||||
|
setIsChecking(true);
|
||||||
|
|
||||||
|
if (isMockMode) {
|
||||||
|
// 尝试切换回真实API
|
||||||
|
const isServerUp = await switchToRealApi();
|
||||||
|
if (isServerUp) {
|
||||||
|
setIsMockMode(false);
|
||||||
|
showNotificationMessage('已切换到真实API模式', 'success');
|
||||||
|
} else {
|
||||||
|
showNotificationMessage('服务器连接失败,继续使用模拟数据', 'warning');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 切换到模拟API
|
||||||
|
switchToMockApi();
|
||||||
|
setIsMockMode(true);
|
||||||
|
showNotificationMessage('已切换到模拟API模式', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsChecking(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示通知消息
|
||||||
|
const showNotificationMessage = (message, type) => {
|
||||||
|
setNotification({ message, type });
|
||||||
|
setShowNotification(true);
|
||||||
|
|
||||||
|
// 3秒后自动隐藏通知
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowNotification(false);
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='api-mode-switch'>
|
||||||
|
<div className='d-flex align-items-center'>
|
||||||
|
<div className='form-check form-switch me-2'>
|
||||||
|
<input
|
||||||
|
className='form-check-input'
|
||||||
|
type='checkbox'
|
||||||
|
id='apiModeToggle'
|
||||||
|
checked={isMockMode}
|
||||||
|
onChange={handleToggleMode}
|
||||||
|
disabled={isChecking}
|
||||||
|
/>
|
||||||
|
<label className='form-check-label' htmlFor='apiModeToggle'>
|
||||||
|
{isChecking ? '检查中...' : isMockMode ? '模拟API模式' : '真实API模式'}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{isMockMode && <span className='badge bg-warning text-dark'>使用本地模拟数据</span>}
|
||||||
|
{!isMockMode && <span className='badge bg-success'>已连接到后端服务器</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showNotification && (
|
||||||
|
<div className={`alert alert-${notification.type} mt-2 py-2 px-3`} style={{ fontSize: '0.85rem' }}>
|
||||||
|
{notification.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -8,7 +8,6 @@ export default function HeaderWithNav() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { user } = useSelector((state) => state.auth);
|
const { user } = useSelector((state) => state.auth);
|
||||||
console.log('user', user);
|
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -10,7 +10,7 @@ import { PersistGate } from 'redux-persist/integration/react';
|
|||||||
import Loading from './components/Loading.jsx';
|
import Loading from './components/Loading.jsx';
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
// <StrictMode>
|
||||||
<PersistGate loading={<Loading />} persistor={persistor}>
|
<PersistGate loading={<Loading />} persistor={persistor}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@ -18,5 +18,5 @@ createRoot(document.getElementById('root')).render(
|
|||||||
</Provider>
|
</Provider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
</StrictMode>
|
// </StrictMode>
|
||||||
);
|
);
|
||||||
|
@ -52,10 +52,9 @@ export default function KnowledgeBase() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get knowledge bases from Redux store
|
// Get knowledge bases from Redux store
|
||||||
const { items: knowledgeBases, total, status, error } = useSelector((state) => state.knowledgeBase.list);
|
const { data, status, error } = useSelector((state) => state.knowledgeBase.list);
|
||||||
const {
|
const {
|
||||||
items: searchResults,
|
data: searchData,
|
||||||
total: searchTotal,
|
|
||||||
status: searchStatus,
|
status: searchStatus,
|
||||||
error: searchError,
|
error: searchError,
|
||||||
keyword: storeKeyword,
|
keyword: storeKeyword,
|
||||||
@ -63,8 +62,8 @@ export default function KnowledgeBase() {
|
|||||||
const { status: operationStatus, error: operationError } = useSelector((state) => state.knowledgeBase.operations);
|
const { status: operationStatus, error: operationError } = useSelector((state) => state.knowledgeBase.operations);
|
||||||
|
|
||||||
// Determine which data to display based on search state
|
// Determine which data to display based on search state
|
||||||
const displayData = isSearching ? searchResults : knowledgeBases;
|
const displayData = isSearching ? searchData?.items : data?.items;
|
||||||
const displayTotal = isSearching ? searchTotal : total;
|
const displayTotal = isSearching ? searchData?.total : data?.total;
|
||||||
const displayStatus = isSearching ? searchStatus : status;
|
const displayStatus = isSearching ? searchStatus : status;
|
||||||
const displayError = isSearching ? searchError : error;
|
const displayError = isSearching ? searchError : error;
|
||||||
|
|
||||||
|
39
src/pages/Permissions/Permissions.css
Normal file
39
src/pages/Permissions/Permissions.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
.permissions-container {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-section {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-mode-control {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-mode-control .api-mode-switch {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.permissions-container {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-section,
|
||||||
|
.api-mode-control {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-section:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import PendingRequests from './components/PendingRequests';
|
import PendingRequests from './components/PendingRequests';
|
||||||
import UserPermissions from './components/UserPermissions';
|
import UserPermissions from './components/UserPermissions';
|
||||||
|
import ApiModeSwitch from '../../components/ApiModeSwitch';
|
||||||
|
import './Permissions.css';
|
||||||
|
|
||||||
export default function PermissionsPage() {
|
export default function PermissionsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useSelector((state) => state.auth);
|
const { user } = useSelector((state) => state.auth);
|
||||||
const [activeTab, setActiveTab] = useState('pending');
|
|
||||||
|
|
||||||
// 检查用户是否有管理权限(leader 或 admin)
|
// 检查用户是否有管理权限(leader 或 admin)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -17,28 +18,18 @@ export default function PermissionsPage() {
|
|||||||
}, [user, navigate]);
|
}, [user, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='permissions-container container py-4'>
|
<div className='permissions-container'>
|
||||||
|
<div className='api-mode-control mb-3'>
|
||||||
<ul className='nav nav-tabs mb-4'>
|
<ApiModeSwitch />
|
||||||
<li className='nav-item'>
|
</div>
|
||||||
<button
|
|
||||||
className={`nav-link ${activeTab === 'pending' ? 'active' : ''}`}
|
<div className='permissions-section mb-4'>
|
||||||
onClick={() => setActiveTab('pending')}
|
<PendingRequests />
|
||||||
>
|
</div>
|
||||||
待处理申请
|
|
||||||
</button>
|
<div className='permissions-section'>
|
||||||
</li>
|
<UserPermissions />
|
||||||
<li className='nav-item'>
|
</div>
|
||||||
<button
|
|
||||||
className={`nav-link ${activeTab === 'users' ? 'active' : ''}`}
|
|
||||||
onClick={() => setActiveTab('users')}
|
|
||||||
>
|
|
||||||
用户权限管理
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div className='tab-content'>{activeTab === 'pending' ? <PendingRequests /> : <UserPermissions />}</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,218 @@
|
|||||||
|
|
||||||
.badge.bg-secondary {
|
.badge.bg-secondary {
|
||||||
background-color: #6c757d !important;
|
background-color: #6c757d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格行鼠标样式 */
|
||||||
|
.cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑动面板样式 */
|
||||||
|
.slide-over-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1040;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.3s ease, visibility 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over-backdrop.show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: -450px;
|
||||||
|
width: 450px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 1050;
|
||||||
|
transition: right 0.3s ease;
|
||||||
|
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over.show {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over-body {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-over-footer {
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid #e9ecef;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像占位符 */
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 新增样式 - 白色基调 */
|
||||||
|
.badge-count {
|
||||||
|
background-color: #ff4d4f;
|
||||||
|
color: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-requests-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-request-item {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-request-item:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info h6 {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-date {
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-content {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge.read {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge.edit {
|
||||||
|
background-color: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
border: 1px solid #b7eb8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge.delete {
|
||||||
|
background-color: #fff2f0;
|
||||||
|
color: #ff4d4f;
|
||||||
|
border: 1px solid #ffccc7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 分页控件样式 */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-button {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
color: #333;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-button:hover:not(:disabled) {
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-button:disabled {
|
||||||
|
color: #d9d9d9;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
margin: 0 15px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
@ -8,6 +8,140 @@ import {
|
|||||||
} from '../../../store/permissions/permissions.thunks';
|
} from '../../../store/permissions/permissions.thunks';
|
||||||
import { resetApproveRejectStatus } from '../../../store/permissions/permissions.slice';
|
import { resetApproveRejectStatus } from '../../../store/permissions/permissions.slice';
|
||||||
import './PendingRequests.css'; // 引入外部CSS文件
|
import './PendingRequests.css'; // 引入外部CSS文件
|
||||||
|
import SvgIcon from '../../../components/SvgIcon';
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const mockPendingRequests = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
applicant: {
|
||||||
|
name: '王五',
|
||||||
|
department: '达人组',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '达人直播数据报告',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要查看和编辑直播数据报告',
|
||||||
|
created_at: '2024-01-07T10:30:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
applicant: {
|
||||||
|
name: '赵六',
|
||||||
|
department: '直播组',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '人力资源政策文件',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要了解最新的人力资源政策',
|
||||||
|
created_at: '2024-01-06T14:20:00Z',
|
||||||
|
expires_at: '2025-01-06T14:20:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
applicant: {
|
||||||
|
name: '钱七',
|
||||||
|
department: '市场部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '市场分析报告',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要了解市场趋势',
|
||||||
|
created_at: '2024-01-05T09:15:00Z',
|
||||||
|
expires_at: '2024-07-05T09:15:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
applicant: {
|
||||||
|
name: '孙八',
|
||||||
|
department: '技术部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '技术架构文档',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: true,
|
||||||
|
},
|
||||||
|
reason: '需要进行技术架构更新',
|
||||||
|
created_at: '2024-01-04T16:45:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
applicant: {
|
||||||
|
name: '周九',
|
||||||
|
department: '产品部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '产品规划文档',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要参与产品规划讨论',
|
||||||
|
created_at: '2024-01-03T11:30:00Z',
|
||||||
|
expires_at: '2024-12-31T23:59:59Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
applicant: {
|
||||||
|
name: '吴十',
|
||||||
|
department: '设计部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '设计规范文档',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要参考设计规范',
|
||||||
|
created_at: '2024-01-02T14:20:00Z',
|
||||||
|
expires_at: '2024-06-30T23:59:59Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
applicant: {
|
||||||
|
name: '郑十一',
|
||||||
|
department: '财务部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '财务报表',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要查看财务数据',
|
||||||
|
created_at: '2024-01-01T09:00:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 每页显示的申请数量
|
||||||
|
const PAGE_SIZE = 5;
|
||||||
|
|
||||||
export default function PendingRequests() {
|
export default function PendingRequests() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -15,14 +149,19 @@ export default function PendingRequests() {
|
|||||||
const [showResponseInput, setShowResponseInput] = useState(false);
|
const [showResponseInput, setShowResponseInput] = useState(false);
|
||||||
const [currentRequestId, setCurrentRequestId] = useState(null);
|
const [currentRequestId, setCurrentRequestId] = useState(null);
|
||||||
const [isApproving, setIsApproving] = useState(false);
|
const [isApproving, setIsApproving] = useState(false);
|
||||||
|
const [selectedRequest, setSelectedRequest] = useState(null);
|
||||||
|
const [showSlideOver, setShowSlideOver] = useState(false);
|
||||||
|
|
||||||
// 从Redux store获取权限申请列表和状态
|
// 使用本地状态管理待处理申请列表
|
||||||
const {
|
const [pendingRequests, setPendingRequests] = useState([]);
|
||||||
items: pendingRequests,
|
const [fetchStatus, setFetchStatus] = useState('idle');
|
||||||
status: fetchStatus,
|
const [fetchError, setFetchError] = useState(null);
|
||||||
error: fetchError,
|
|
||||||
} = useSelector((state) => state.permissions.permissions);
|
|
||||||
|
|
||||||
|
// 分页状态
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
|
||||||
|
// 从Redux store获取批准/拒绝操作的状态
|
||||||
const {
|
const {
|
||||||
status: approveRejectStatus,
|
status: approveRejectStatus,
|
||||||
error: approveRejectError,
|
error: approveRejectError,
|
||||||
@ -31,7 +170,38 @@ export default function PendingRequests() {
|
|||||||
|
|
||||||
// 获取待处理申请列表
|
// 获取待处理申请列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchPermissionsThunk());
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
setFetchStatus('loading');
|
||||||
|
// 尝试从API获取数据
|
||||||
|
const result = await dispatch(fetchPermissionsThunk());
|
||||||
|
|
||||||
|
// 检查API返回的数据
|
||||||
|
if (result && result.payload && result.payload.length > 0) {
|
||||||
|
// 使用API返回的数据
|
||||||
|
setPendingRequests(result.payload);
|
||||||
|
setTotalPages(Math.ceil(result.payload.length / PAGE_SIZE));
|
||||||
|
console.log('使用API返回的待处理申请数据');
|
||||||
|
} else {
|
||||||
|
// API返回的数据为空,使用模拟数据
|
||||||
|
console.log('API返回的待处理申请数据为空,使用模拟数据');
|
||||||
|
setPendingRequests(mockPendingRequests);
|
||||||
|
setTotalPages(Math.ceil(mockPendingRequests.length / PAGE_SIZE));
|
||||||
|
}
|
||||||
|
setFetchStatus('succeeded');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取待处理申请失败:', error);
|
||||||
|
setFetchError('获取待处理申请失败');
|
||||||
|
setFetchStatus('failed');
|
||||||
|
|
||||||
|
// API请求失败,使用模拟数据作为后备
|
||||||
|
console.log('API请求失败,使用模拟数据作为后备');
|
||||||
|
setPendingRequests(mockPendingRequests);
|
||||||
|
setTotalPages(Math.ceil(mockPendingRequests.length / PAGE_SIZE));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
// 监听批准/拒绝操作的状态变化
|
// 监听批准/拒绝操作的状态变化
|
||||||
@ -46,6 +216,21 @@ export default function PendingRequests() {
|
|||||||
setShowResponseInput(false);
|
setShowResponseInput(false);
|
||||||
setCurrentRequestId(null);
|
setCurrentRequestId(null);
|
||||||
setResponseMessage('');
|
setResponseMessage('');
|
||||||
|
setShowSlideOver(false);
|
||||||
|
setSelectedRequest(null);
|
||||||
|
|
||||||
|
// 从列表中移除已处理的申请
|
||||||
|
const updatedRequests = pendingRequests.filter((req) => req.id !== currentRequestId);
|
||||||
|
setPendingRequests(updatedRequests);
|
||||||
|
|
||||||
|
// 更新总页数
|
||||||
|
setTotalPages(Math.ceil(updatedRequests.length / PAGE_SIZE));
|
||||||
|
|
||||||
|
// 如果当前页没有数据了,且不是第一页,则回到上一页
|
||||||
|
if (getCurrentPageData().length === 1 && currentPage > 1) {
|
||||||
|
setCurrentPage(currentPage - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// 重置状态
|
// 重置状态
|
||||||
dispatch(resetApproveRejectStatus());
|
dispatch(resetApproveRejectStatus());
|
||||||
} else if (approveRejectStatus === 'failed') {
|
} else if (approveRejectStatus === 'failed') {
|
||||||
@ -58,7 +243,15 @@ export default function PendingRequests() {
|
|||||||
// 重置状态
|
// 重置状态
|
||||||
dispatch(resetApproveRejectStatus());
|
dispatch(resetApproveRejectStatus());
|
||||||
}
|
}
|
||||||
}, [approveRejectStatus, approveRejectError, dispatch, isApproving]);
|
}, [
|
||||||
|
approveRejectStatus,
|
||||||
|
approveRejectError,
|
||||||
|
dispatch,
|
||||||
|
isApproving,
|
||||||
|
currentRequestId,
|
||||||
|
pendingRequests,
|
||||||
|
currentPage,
|
||||||
|
]);
|
||||||
|
|
||||||
// 打开回复输入框
|
// 打开回复输入框
|
||||||
const handleOpenResponseInput = (requestId, approving) => {
|
const handleOpenResponseInput = (requestId, approving) => {
|
||||||
@ -90,19 +283,80 @@ export default function PendingRequests() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取权限类型文本和样式
|
// 处理行点击,显示滑动面板
|
||||||
const getPermissionTypeText = (permissions) => {
|
const handleRowClick = (request) => {
|
||||||
if (permissions.can_read && !permissions.can_edit) {
|
setSelectedRequest(request);
|
||||||
return '只读访问';
|
setShowSlideOver(true);
|
||||||
} else if (permissions.can_edit) {
|
};
|
||||||
return '完全访问';
|
|
||||||
|
// 关闭滑动面板
|
||||||
|
const handleCloseSlideOver = () => {
|
||||||
|
setShowSlideOver(false);
|
||||||
|
setTimeout(() => {
|
||||||
|
setSelectedRequest(null);
|
||||||
|
}, 300); // 等待动画结束后再清除选中的申请
|
||||||
|
};
|
||||||
|
|
||||||
|
// 直接处理申请(不显示弹窗)
|
||||||
|
const handleDirectProcess = (requestId, approve) => {
|
||||||
|
setCurrentRequestId(requestId);
|
||||||
|
setIsApproving(approve);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
id: requestId,
|
||||||
|
responseMessage: approve ? '已批准' : '已拒绝',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (approve) {
|
||||||
|
dispatch(approvePermissionThunk(params));
|
||||||
} else {
|
} else {
|
||||||
return '未知权限';
|
dispatch(rejectPermissionThunk(params));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取当前页的数据
|
||||||
|
const getCurrentPageData = () => {
|
||||||
|
const startIndex = (currentPage - 1) * PAGE_SIZE;
|
||||||
|
const endIndex = startIndex + PAGE_SIZE;
|
||||||
|
return pendingRequests.slice(startIndex, endIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理页码变化
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染分页控件
|
||||||
|
const renderPagination = () => {
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='pagination-container'>
|
||||||
|
<button
|
||||||
|
className='pagination-button'
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className='pagination-info'>
|
||||||
|
{currentPage} / {totalPages}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className='pagination-button'
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 渲染加载状态
|
// 渲染加载状态
|
||||||
if (fetchStatus === 'loading') {
|
if (fetchStatus === 'loading' && pendingRequests.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='text-center py-5'>
|
<div className='text-center py-5'>
|
||||||
<div className='spinner-border' role='status'>
|
<div className='spinner-border' role='status'>
|
||||||
@ -114,7 +368,7 @@ export default function PendingRequests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染错误状态
|
// 渲染错误状态
|
||||||
if (fetchStatus === 'failed') {
|
if (fetchStatus === 'failed' && pendingRequests.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='alert alert-danger' role='alert'>
|
<div className='alert alert-danger' role='alert'>
|
||||||
{fetchError || '获取待处理申请失败'}
|
{fetchError || '获取待处理申请失败'}
|
||||||
@ -131,95 +385,196 @@ export default function PendingRequests() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前页的数据
|
||||||
|
const currentPageData = getCurrentPageData();
|
||||||
|
|
||||||
// 渲染申请列表
|
// 渲染申请列表
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='d-flex justify-content-between align-items-center mb-4'>
|
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||||
{/* <h5 className='mb-0'>待处理申请</h5> */}
|
<h5 className='mb-0'>待处理申请</h5>
|
||||||
<div className='bg-danger rounded-pill text-white py-1 px-2'>{pendingRequests.length}个待处理</div>
|
<div className='badge bg-danger'>{pendingRequests.length}个待处理</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='permission-requests'>
|
<div className='pending-requests-list'>
|
||||||
{pendingRequests.map((request) => (
|
{currentPageData.map((request) => (
|
||||||
<div key={request.id} className='permission-card card mb-3 border-0 shadow-sm bg-light text-dark'>
|
<div key={request.id} className='pending-request-item' onClick={() => handleRowClick(request)}>
|
||||||
<div className='card-body p-4 d-flex flex-column gap-3'>
|
<div className='request-header'>
|
||||||
<div className='d-flex justify-content-between'>
|
<div className='user-info'>
|
||||||
<div>
|
<h6 className='mb-0'>{request.applicant.name}</h6>
|
||||||
<h5 className='mb-1'>{request.applicant.name || request.applicant}</h5>
|
<p className='department'>{request.applicant.department}</p>
|
||||||
<p className='text-dark-50 mb-0'>{request.applicant.department || '无归属部门'}</p>
|
</div>
|
||||||
</div>
|
<div className='request-date'>{new Date(request.created_at).toLocaleDateString()}</div>
|
||||||
<div className='text-end'>
|
</div>
|
||||||
<div className='text-dark-50 mb-0'>
|
|
||||||
{new Date(request.created_at).toLocaleDateString()}
|
<div className='request-content'>
|
||||||
|
<p className='mb-2'>申请访问:{request.knowledge_base.name}</p>
|
||||||
|
|
||||||
|
{request.permissions.can_edit ? (
|
||||||
|
<span
|
||||||
|
className='badge bg-success-subtle text-success d-flex align-items-center gap-1'
|
||||||
|
style={{ width: 'fit-content' }}
|
||||||
|
>
|
||||||
|
<SvgIcon className={'circle-yes'} />
|
||||||
|
完全访问
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
request.permissions.can_read && (
|
||||||
|
<span
|
||||||
|
className='badge bg-warning-subtle text-warning d-flex align-items-center gap-1'
|
||||||
|
style={{ width: 'fit-content' }}
|
||||||
|
>
|
||||||
|
<SvgIcon className={'eye'} />
|
||||||
|
只读访问
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='request-actions'>
|
||||||
|
<button
|
||||||
|
className='btn btn-outline-danger'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDirectProcess(request.id, false);
|
||||||
|
}}
|
||||||
|
disabled={processingId === request.id && approveRejectStatus === 'loading'}
|
||||||
|
>
|
||||||
|
{processingId === request.id && approveRejectStatus === 'loading' && !isApproving
|
||||||
|
? '处理中...'
|
||||||
|
: '拒绝'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='btn btn-success'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDirectProcess(request.id, true);
|
||||||
|
}}
|
||||||
|
disabled={processingId === request.id && approveRejectStatus === 'loading'}
|
||||||
|
>
|
||||||
|
{processingId === request.id && approveRejectStatus === 'loading' && isApproving
|
||||||
|
? '处理中...'
|
||||||
|
: '批准'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分页控件 */}
|
||||||
|
{renderPagination()}
|
||||||
|
|
||||||
|
{/* 滑动面板 */}
|
||||||
|
<div className={`slide-over-backdrop ${showSlideOver ? 'show' : ''}`} onClick={handleCloseSlideOver}></div>
|
||||||
|
<div className={`slide-over ${showSlideOver ? 'show' : ''}`}>
|
||||||
|
{selectedRequest && (
|
||||||
|
<div className='slide-over-content'>
|
||||||
|
<div className='slide-over-header'>
|
||||||
|
<h5 className='mb-0'>申请详情</h5>
|
||||||
|
<button type='button' className='btn-close' onClick={handleCloseSlideOver}></button>
|
||||||
|
</div>
|
||||||
|
<div className='slide-over-body'>
|
||||||
|
<div className='mb-4'>
|
||||||
|
<h6 className='text-muted mb-2'>申请人信息</h6>
|
||||||
|
<div className='d-flex align-items-center mb-3'>
|
||||||
|
<div className='avatar-placeholder me-3 bg-dark'>
|
||||||
|
{(selectedRequest.applicant.name || selectedRequest.applicant).charAt(0)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 className='mb-1'>
|
||||||
|
{selectedRequest.applicant.name || selectedRequest.applicant}
|
||||||
|
</h5>
|
||||||
|
<p className='text-muted mb-0'>
|
||||||
|
{selectedRequest.applicant.department || '无归属部门'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='d-flex flex-column gap-3'>
|
<div className='mb-4'>
|
||||||
<div>申请访问:{request.knowledge_base.name || request.knowledge_base}</div>
|
<h6 className='text-muted mb-2'>知识库信息</h6>
|
||||||
<div className='d-flex flex-wrap gap-2'>
|
<p className='mb-1'>
|
||||||
{request.permissions.can_read && (
|
<strong>名称:</strong>{' '}
|
||||||
<span className='badge bg-info custom-badge'>只读</span>
|
{selectedRequest.knowledge_base.name || selectedRequest.knowledge_base}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mb-4'>
|
||||||
|
<h6 className='text-muted mb-2'>申请权限</h6>
|
||||||
|
<div className='d-flex flex-wrap gap-2 mb-3'>
|
||||||
|
{selectedRequest.permissions.can_read && !selectedRequest.permissions.can_edit && (
|
||||||
|
<span className='badge bg-warning-subtle text-warning d-flex align-items-center gap-1'>
|
||||||
|
只读
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{request.permissions.can_edit && (
|
{selectedRequest.permissions.can_edit && selectedRequest.permissions.can_delete && (
|
||||||
<span className='badge bg-success custom-badge'>编辑</span>
|
<span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'>
|
||||||
)}
|
完全访问
|
||||||
{request.permissions.can_delete && (
|
|
||||||
<span className='badge bg-danger custom-badge'>删除</span>
|
|
||||||
)}
|
|
||||||
{request.expires_at && (
|
|
||||||
<span className='badge bg-secondary custom-badge'>
|
|
||||||
至 {new Date(request.expires_at).toLocaleDateString()}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-dark-50'>
|
|
||||||
<strong>申请理由:</strong> {request.reason}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='d-flex justify-content-end'>
|
|
||||||
<button
|
<div className='mb-4'>
|
||||||
className='btn btn-outline-danger me-2'
|
<h6 className='text-muted mb-2'>申请时间</h6>
|
||||||
onClick={() => handleOpenResponseInput(request.id, false)}
|
<p className='mb-1'>{new Date(selectedRequest.created_at).toLocaleString()}</p>
|
||||||
disabled={processingId === request.id && approveRejectStatus === 'loading'}
|
</div>
|
||||||
>
|
|
||||||
{processingId === request.id &&
|
{selectedRequest.expires_at && (
|
||||||
approveRejectStatus === 'loading' &&
|
<div className='mb-4'>
|
||||||
!isApproving ? (
|
<h6 className='text-muted mb-2'>到期时间</h6>
|
||||||
<>
|
<p className='mb-1'>{new Date(selectedRequest.expires_at).toLocaleString()}</p>
|
||||||
<span
|
</div>
|
||||||
className='spinner-border spinner-border-sm me-1'
|
)}
|
||||||
role='status'
|
|
||||||
aria-hidden='true'
|
<div className='mb-4'>
|
||||||
></span>
|
<h6 className='text-muted mb-2'>申请理由</h6>
|
||||||
处理中...
|
<div className='p-3 bg-light rounded'>{selectedRequest.reason || '无申请理由'}</div>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>拒绝</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className='btn btn-success'
|
|
||||||
onClick={() => handleOpenResponseInput(request.id, true)}
|
|
||||||
disabled={processingId === request.id && approveRejectStatus === 'loading'}
|
|
||||||
>
|
|
||||||
{processingId === request.id && approveRejectStatus === 'loading' && isApproving ? (
|
|
||||||
<>
|
|
||||||
<span
|
|
||||||
className='spinner-border spinner-border-sm me-1'
|
|
||||||
role='status'
|
|
||||||
aria-hidden='true'
|
|
||||||
></span>
|
|
||||||
处理中...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>批准</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className='slide-over-footer'>
|
||||||
|
<button
|
||||||
|
className='btn btn-outline-danger me-2'
|
||||||
|
onClick={() => handleOpenResponseInput(selectedRequest.id, false)}
|
||||||
|
disabled={processingId === selectedRequest.id && approveRejectStatus === 'loading'}
|
||||||
|
>
|
||||||
|
{processingId === selectedRequest.id &&
|
||||||
|
approveRejectStatus === 'loading' &&
|
||||||
|
!isApproving ? (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className='spinner-border spinner-border-sm me-1'
|
||||||
|
role='status'
|
||||||
|
aria-hidden='true'
|
||||||
|
></span>
|
||||||
|
处理中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>拒绝</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='btn btn-success'
|
||||||
|
onClick={() => handleOpenResponseInput(selectedRequest.id, true)}
|
||||||
|
disabled={processingId === selectedRequest.id && approveRejectStatus === 'loading'}
|
||||||
|
>
|
||||||
|
{processingId === selectedRequest.id &&
|
||||||
|
approveRejectStatus === 'loading' &&
|
||||||
|
isApproving ? (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
className='spinner-border spinner-border-sm me-1'
|
||||||
|
role='status'
|
||||||
|
aria-hidden='true'
|
||||||
|
></span>
|
||||||
|
处理中...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>批准</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 回复输入弹窗 */}
|
{/* 回复输入弹窗 */}
|
||||||
|
@ -1,6 +1,62 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { get } from '../../../services/api';
|
import { get } from '../../../services/api';
|
||||||
|
|
||||||
|
// 模拟用户权限数据
|
||||||
|
const mockUserPermissions = [
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: '1',
|
||||||
|
name: '达人直播数据报告',
|
||||||
|
department: '达人组',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-10T14:30:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: '2',
|
||||||
|
name: '人力资源政策文件',
|
||||||
|
department: '人力资源组',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-08T09:15:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: '3',
|
||||||
|
name: '市场分析报告',
|
||||||
|
department: '市场部',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: '4',
|
||||||
|
name: '产品规划文档',
|
||||||
|
department: '产品部',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_admin: true,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-15T11:20:00Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function UserPermissionDetails({ user, onClose, onSave }) {
|
export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||||
const [userPermissions, setUserPermissions] = useState([]);
|
const [userPermissions, setUserPermissions] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@ -15,13 +71,29 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
|||||||
const response = await get(`/users/${user.id}/permissions/`);
|
const response = await get(`/users/${user.id}/permissions/`);
|
||||||
|
|
||||||
if (response && response.code === 200) {
|
if (response && response.code === 200) {
|
||||||
setUserPermissions(response.data.permissions || []);
|
// 检查API返回的数据
|
||||||
|
const apiPermissions = response.data.permissions || [];
|
||||||
|
if (apiPermissions.length > 0) {
|
||||||
|
// 使用API返回的数据
|
||||||
|
setUserPermissions(apiPermissions);
|
||||||
|
console.log('使用API返回的用户权限数据');
|
||||||
|
} else {
|
||||||
|
// API返回的数据为空,使用模拟数据
|
||||||
|
console.log('API返回的用户权限数据为空,使用模拟数据');
|
||||||
|
setUserPermissions(mockUserPermissions);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setError('获取用户权限详情失败');
|
// API请求失败,使用模拟数据
|
||||||
|
console.log('API请求失败,使用模拟数据');
|
||||||
|
setUserPermissions(mockUserPermissions);
|
||||||
|
setError('获取用户权限详情失败,显示模拟数据');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户权限详情失败:', error);
|
console.error('获取用户权限详情失败:', error);
|
||||||
setError('获取用户权限详情失败');
|
// API请求出错,使用模拟数据作为后备
|
||||||
|
console.log('API请求出错,使用模拟数据作为后备');
|
||||||
|
setUserPermissions(mockUserPermissions);
|
||||||
|
setError('获取用户权限详情失败,显示模拟数据');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -72,7 +144,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal fade show' style={{ display: 'block' }} tabIndex='-1'>
|
<div className='modal fade show' style={{ display: 'block' }} tabIndex='-1'>
|
||||||
<div className='modal-dialog modal-lg modal-dialog-scrollable'>
|
<div className='modal-dialog modal-lg modal-dialog-scrollable' style={{ zIndex: 9999 }}>
|
||||||
<div className='modal-content'>
|
<div className='modal-content'>
|
||||||
<div className='modal-header'>
|
<div className='modal-header'>
|
||||||
<h5 className='modal-title'>{user.name} 的权限详情</h5>
|
<h5 className='modal-title'>{user.name} 的权限详情</h5>
|
||||||
@ -87,8 +159,76 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
|||||||
<p className='mt-3'>加载权限详情...</p>
|
<p className='mt-3'>加载权限详情...</p>
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className='alert alert-danger' role='alert'>
|
<div>
|
||||||
{error}
|
<div className='alert alert-warning' role='alert'>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
{userPermissions.length > 0 && (
|
||||||
|
<div className='table-responsive'>
|
||||||
|
<table className='table table-hover'>
|
||||||
|
<thead className='table-light'>
|
||||||
|
<tr>
|
||||||
|
<th>知识库名称</th>
|
||||||
|
<th>所属部门</th>
|
||||||
|
<th>当前权限</th>
|
||||||
|
<th>最后访问时间</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{userPermissions.map((item) => {
|
||||||
|
const currentPermissionType = getPermissionType(item.permission);
|
||||||
|
const updatedPermissionType =
|
||||||
|
updatedPermissions[item.knowledge_base.id] || currentPermissionType;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={item.knowledge_base.id}>
|
||||||
|
<td>{item.knowledge_base.name}</td>
|
||||||
|
<td>{item.knowledge_base.department || '未指定'}</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
className={`badge ${
|
||||||
|
currentPermissionType === 'admin'
|
||||||
|
? 'bg-danger'
|
||||||
|
: currentPermissionType === 'edit'
|
||||||
|
? 'bg-success'
|
||||||
|
: currentPermissionType === 'read'
|
||||||
|
? 'bg-info'
|
||||||
|
: 'bg-secondary'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{getPermissionTypeText(currentPermissionType)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{item.last_access_time
|
||||||
|
? new Date(item.last_access_time).toLocaleString()
|
||||||
|
: '从未访问'}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
className='form-select form-select-sm'
|
||||||
|
value={updatedPermissionType}
|
||||||
|
onChange={(e) =>
|
||||||
|
handlePermissionChange(
|
||||||
|
item.knowledge_base.id,
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value='none'>无权限</option>
|
||||||
|
<option value='read'>只读访问</option>
|
||||||
|
<option value='edit'>编辑权限</option>
|
||||||
|
<option value='admin'>管理权限</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : userPermissions.length === 0 ? (
|
) : userPermissions.length === 0 ? (
|
||||||
<div className='alert alert-info' role='alert'>
|
<div className='alert alert-info' role='alert'>
|
||||||
@ -167,7 +307,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className='btn btn-primary'
|
className='btn btn-dark'
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={loading || Object.keys(updatedPermissions).length === 0}
|
disabled={loading || Object.keys(updatedPermissions).length === 0}
|
||||||
>
|
>
|
||||||
@ -176,7 +316,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='modal-backdrop fade show'></div>
|
<div className='modal-backdrop fade show' style={{ zIndex: 1050 }}></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
210
src/pages/Permissions/components/UserPermissions.css
Normal file
210
src/pages/Permissions/components/UserPermissions.css
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
.search-box {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 240px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-permissions-table {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cell {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cell:first-child {
|
||||||
|
flex: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row:hover {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell:first-child {
|
||||||
|
flex: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #1890ff;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-username {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge.read {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
border: 1px solid #91d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge.edit {
|
||||||
|
background-color: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
border: 1px solid #b7eb8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge.admin {
|
||||||
|
background-color: #fff2f0;
|
||||||
|
color: #ff4d4f;
|
||||||
|
border: 1px solid #ffccc7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-cell {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-details {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-details:hover {
|
||||||
|
background-color: #e6f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页控件样式 */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-button {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
color: #333;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-button:hover:not(:disabled) {
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-button:disabled {
|
||||||
|
color: #d9d9d9;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
margin: 0 15px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面大小选择器样式 */
|
||||||
|
.page-size-selector {
|
||||||
|
margin-left: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-size-select {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-size-select:hover, .page-size-select:focus {
|
||||||
|
border-color: #1890ff;
|
||||||
|
}
|
@ -3,6 +3,111 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { get, put } from '../../../services/api';
|
import { get, put } from '../../../services/api';
|
||||||
import { showNotification } from '../../../store/notification.slice';
|
import { showNotification } from '../../../store/notification.slice';
|
||||||
import UserPermissionDetails from './UserPermissionDetails';
|
import UserPermissionDetails from './UserPermissionDetails';
|
||||||
|
import './UserPermissions.css';
|
||||||
|
import SvgIcon from '../../../components/SvgIcon';
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const mockUsers = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
username: 'zhangsan',
|
||||||
|
name: '张三',
|
||||||
|
department: '达人组',
|
||||||
|
position: '达人对接',
|
||||||
|
permissions_count: {
|
||||||
|
read: 1,
|
||||||
|
edit: 0,
|
||||||
|
admin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
username: 'lisi',
|
||||||
|
name: '李四',
|
||||||
|
department: '人力资源组',
|
||||||
|
position: 'HR',
|
||||||
|
permissions_count: {
|
||||||
|
read: 1,
|
||||||
|
edit: 0,
|
||||||
|
admin: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
username: 'wangwu',
|
||||||
|
name: '王五',
|
||||||
|
department: '市场部',
|
||||||
|
position: '市场专员',
|
||||||
|
permissions_count: {
|
||||||
|
read: 2,
|
||||||
|
edit: 1,
|
||||||
|
admin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
username: 'zhaoliu',
|
||||||
|
name: '赵六',
|
||||||
|
department: '技术部',
|
||||||
|
position: '前端开发',
|
||||||
|
permissions_count: {
|
||||||
|
read: 3,
|
||||||
|
edit: 2,
|
||||||
|
admin: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
username: 'sunqi',
|
||||||
|
name: '孙七',
|
||||||
|
department: '产品部',
|
||||||
|
position: '产品经理',
|
||||||
|
permissions_count: {
|
||||||
|
read: 4,
|
||||||
|
edit: 2,
|
||||||
|
admin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
username: 'zhouba',
|
||||||
|
name: '周八',
|
||||||
|
department: '设计部',
|
||||||
|
position: 'UI设计师',
|
||||||
|
permissions_count: {
|
||||||
|
read: 1,
|
||||||
|
edit: 1,
|
||||||
|
admin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
username: 'wujiu',
|
||||||
|
name: '吴九',
|
||||||
|
department: '财务部',
|
||||||
|
position: '财务主管',
|
||||||
|
permissions_count: {
|
||||||
|
read: 2,
|
||||||
|
edit: 0,
|
||||||
|
admin: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
username: 'zhengshi',
|
||||||
|
name: '郑十',
|
||||||
|
department: '行政部',
|
||||||
|
position: '行政专员',
|
||||||
|
permissions_count: {
|
||||||
|
read: 1,
|
||||||
|
edit: 0,
|
||||||
|
admin: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 每页显示选项
|
||||||
|
const PAGE_SIZE_OPTIONS = [5, 10, 15, 20];
|
||||||
|
|
||||||
export default function UserPermissions() {
|
export default function UserPermissions() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -11,6 +116,12 @@ export default function UserPermissions() {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [selectedUser, setSelectedUser] = useState(null);
|
const [selectedUser, setSelectedUser] = useState(null);
|
||||||
const [showDetailsModal, setShowDetailsModal] = useState(false);
|
const [showDetailsModal, setShowDetailsModal] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
||||||
|
// 分页状态
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(5);
|
||||||
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -20,13 +131,25 @@ export default function UserPermissions() {
|
|||||||
const response = await get('/users/permissions/');
|
const response = await get('/users/permissions/');
|
||||||
|
|
||||||
if (response && response.code === 200) {
|
if (response && response.code === 200) {
|
||||||
setUsers(response.data.users || []);
|
// 只有当API返回的用户列表为空时才使用模拟数据
|
||||||
|
const apiUsers = response.data.users || [];
|
||||||
|
if (apiUsers.length > 0) {
|
||||||
|
setUsers(apiUsers);
|
||||||
|
} else {
|
||||||
|
console.log('API返回的用户列表为空,使用模拟数据');
|
||||||
|
setUsers(mockUsers);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setError('获取用户列表失败');
|
// API请求失败,使用模拟数据
|
||||||
|
console.log('API请求失败,使用模拟数据');
|
||||||
|
setUsers(mockUsers);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户列表失败:', error);
|
console.error('获取用户列表失败:', error);
|
||||||
setError('获取用户列表失败');
|
setError('获取用户列表失败');
|
||||||
|
// API请求出错,使用模拟数据作为后备
|
||||||
|
console.log('API请求出错,使用模拟数据作为后备');
|
||||||
|
setUsers(mockUsers);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -35,6 +158,16 @@ export default function UserPermissions() {
|
|||||||
fetchUsers();
|
fetchUsers();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 当用户列表或搜索词变化时,更新总页数
|
||||||
|
useEffect(() => {
|
||||||
|
const filteredUsers = getFilteredUsers();
|
||||||
|
setTotalPages(Math.ceil(filteredUsers.length / pageSize));
|
||||||
|
// 如果当前页超出了新的总页数,则重置为第一页
|
||||||
|
if (currentPage > Math.ceil(filteredUsers.length / pageSize)) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
|
}, [users, searchTerm, pageSize]);
|
||||||
|
|
||||||
// 打开用户权限详情弹窗
|
// 打开用户权限详情弹窗
|
||||||
const handleOpenDetailsModal = (user) => {
|
const handleOpenDetailsModal = (user) => {
|
||||||
setSelectedUser(user);
|
setSelectedUser(user);
|
||||||
@ -99,8 +232,99 @@ export default function UserPermissions() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理搜索输入变化
|
||||||
|
const handleSearchChange = (e) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
setCurrentPage(1); // 重置为第一页
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理页码变化
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理每页显示数量变化
|
||||||
|
const handlePageSizeChange = (e) => {
|
||||||
|
const newPageSize = parseInt(e.target.value);
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
setCurrentPage(1); // 重置为第一页
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取过滤后的用户列表
|
||||||
|
const getFilteredUsers = () => {
|
||||||
|
if (!searchTerm.trim()) return users;
|
||||||
|
|
||||||
|
return users.filter(
|
||||||
|
(user) =>
|
||||||
|
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
user.department.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
user.position.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前页的数据
|
||||||
|
const getCurrentPageData = () => {
|
||||||
|
const filteredUsers = getFilteredUsers();
|
||||||
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
|
const endIndex = startIndex + pageSize;
|
||||||
|
return filteredUsers.slice(startIndex, endIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染分页控件
|
||||||
|
const renderPagination = () => {
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='d-flex justify-content-center align-items-center mt-4'>
|
||||||
|
<nav aria-label='用户权限分页'>
|
||||||
|
<ul className='pagination'>
|
||||||
|
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}>
|
||||||
|
<button
|
||||||
|
className='page-link'
|
||||||
|
onClick={() => handlePageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li className='page-item active'>
|
||||||
|
<span className='page-link'>
|
||||||
|
{currentPage} / {totalPages}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`}>
|
||||||
|
<button
|
||||||
|
className='page-link'
|
||||||
|
onClick={() => handlePageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className='ms-3'>
|
||||||
|
<select
|
||||||
|
className='form-select form-select-sm'
|
||||||
|
value={pageSize}
|
||||||
|
onChange={handlePageSizeChange}
|
||||||
|
aria-label='每页显示数量'
|
||||||
|
>
|
||||||
|
{PAGE_SIZE_OPTIONS.map((size) => (
|
||||||
|
<option key={size} value={size}>
|
||||||
|
{size}条/页
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// 渲染加载状态
|
// 渲染加载状态
|
||||||
if (loading) {
|
if (loading && users.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='text-center py-5'>
|
<div className='text-center py-5'>
|
||||||
<div className='spinner-border' role='status'>
|
<div className='spinner-border' role='status'>
|
||||||
@ -112,7 +336,7 @@ export default function UserPermissions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染错误状态
|
// 渲染错误状态
|
||||||
if (error) {
|
if (error && users.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className='alert alert-danger' role='alert'>
|
<div className='alert alert-danger' role='alert'>
|
||||||
{error}
|
{error}
|
||||||
@ -129,70 +353,111 @@ export default function UserPermissions() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取当前页的数据
|
||||||
|
const currentPageData = getCurrentPageData();
|
||||||
|
|
||||||
|
// 获取过滤后的总用户数
|
||||||
|
const filteredUsersCount = getFilteredUsers().length;
|
||||||
|
|
||||||
// 渲染用户列表
|
// 渲染用户列表
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='card'>
|
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||||
<div className='card-header bg-white'>
|
<h5 className='mb-0'>用户权限管理</h5>
|
||||||
<h5 className='mb-0'>用户权限管理</h5>
|
<div className='input-group' style={{ maxWidth: '250px' }}>
|
||||||
</div>
|
<input
|
||||||
<div className='card-body p-0'>
|
type='text'
|
||||||
<div className='table-responsive'>
|
className='form-control'
|
||||||
<table className='table table-hover mb-0'>
|
placeholder='搜索用户...'
|
||||||
<thead className='table-light'>
|
value={searchTerm}
|
||||||
<tr>
|
onChange={handleSearchChange}
|
||||||
<th>用户名</th>
|
/>
|
||||||
<th>姓名</th>
|
<span className='input-group-text'>
|
||||||
<th>部门</th>
|
<i className='bi bi-search'></i>
|
||||||
<th>职位</th>
|
</span>
|
||||||
<th>权限</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{users.map((user) => (
|
|
||||||
<tr key={user.id}>
|
|
||||||
<td>{user.username}</td>
|
|
||||||
<td>{user.name}</td>
|
|
||||||
<td>{user.department}</td>
|
|
||||||
<td>{user.position}</td>
|
|
||||||
<td>
|
|
||||||
{user.permissions_count && (
|
|
||||||
<div className='d-flex gap-2'>
|
|
||||||
{user.permissions_count.read > 0 && (
|
|
||||||
<span className='badge bg-info'>
|
|
||||||
只读: {user.permissions_count.read}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{user.permissions_count.edit > 0 && (
|
|
||||||
<span className='badge bg-success'>
|
|
||||||
编辑: {user.permissions_count.edit}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{user.permissions_count.admin > 0 && (
|
|
||||||
<span className='badge bg-danger'>
|
|
||||||
管理: {user.permissions_count.admin}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button
|
|
||||||
className='btn btn-sm btn-primary'
|
|
||||||
onClick={() => handleOpenDetailsModal(user)}
|
|
||||||
>
|
|
||||||
权限详情
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{filteredUsersCount > 0 ? (
|
||||||
|
<>
|
||||||
|
<div className='card'>
|
||||||
|
<div className='table-responsive'>
|
||||||
|
<table className='table table-hover mb-0'>
|
||||||
|
<thead className='table-light'>
|
||||||
|
<tr>
|
||||||
|
<th scope='col'>用户</th>
|
||||||
|
<th scope='col'>部门</th>
|
||||||
|
<th scope='col'>职位</th>
|
||||||
|
<th scope='col'>数据集权限</th>
|
||||||
|
<th scope='col' className='text-end'>
|
||||||
|
操作
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{currentPageData.map((user) => (
|
||||||
|
<tr key={user.id}>
|
||||||
|
<td>
|
||||||
|
<div className='d-flex align-items-center'>
|
||||||
|
<div
|
||||||
|
className='bg-dark rounded-circle text-white d-flex align-items-center justify-content-center me-2'
|
||||||
|
style={{ width: '36px', height: '36px' }}
|
||||||
|
>
|
||||||
|
{user.name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='fw-medium'>{user.name}</div>
|
||||||
|
<div className='text-muted small'>{user.username}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className='align-middle'>{user.department}</td>
|
||||||
|
<td className='align-middle'>{user.position}</td>
|
||||||
|
<td className='align-middle'>
|
||||||
|
{user.permissions_count && (
|
||||||
|
<div className='d-flex flex-wrap gap-1'>
|
||||||
|
{user.permissions_count.read > 0 && (
|
||||||
|
<span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'>
|
||||||
|
完全访问: {user.permissions_count.read}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{user.permissions_count.edit > 0 && (
|
||||||
|
<span className='badge bg-warning-subtle text-warning d-flex align-items-center gap-1'>
|
||||||
|
只读访问: {user.permissions_count.edit}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{user.permissions_count.admin > 0 && (
|
||||||
|
<span className='badge bg-danger d-flex align-items-center gap-1'>
|
||||||
|
无访问权限: {user.permissions_count.admin}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className='text-end align-middle'>
|
||||||
|
<button
|
||||||
|
className='btn btn-outline-dark btn-sm'
|
||||||
|
onClick={() => handleOpenDetailsModal(user)}
|
||||||
|
>
|
||||||
|
修改权限
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 分页控件 */}
|
||||||
|
{renderPagination()}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className='alert alert-info' role='alert'>
|
||||||
|
没有找到匹配的用户
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 用户权限详情弹窗 */}
|
{/* 用户权限详情弹窗 */}
|
||||||
{showDetailsModal && selectedUser && (
|
{showDetailsModal && selectedUser && (
|
||||||
<UserPermissionDetails
|
<UserPermissionDetails
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import CryptoJS from 'crypto-js';
|
import CryptoJS from 'crypto-js';
|
||||||
|
import { mockGet, mockPost, mockPut, mockDelete } from './mockApi';
|
||||||
|
|
||||||
const secretKey = import.meta.env.VITE_SECRETKEY;
|
const secretKey = import.meta.env.VITE_SECRETKEY;
|
||||||
|
|
||||||
|
// API连接状态
|
||||||
|
let isServerDown = false;
|
||||||
|
let hasCheckedServer = false;
|
||||||
|
|
||||||
// Create Axios instance with base URL
|
// Create Axios instance with base URL
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/api',
|
||||||
@ -28,9 +33,22 @@ api.interceptors.request.use(
|
|||||||
// Response Interceptor
|
// Response Interceptor
|
||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
|
// 如果成功收到响应,表示服务器正常工作
|
||||||
|
if (!hasCheckedServer) {
|
||||||
|
console.log('Server is up and running');
|
||||||
|
isServerDown = false;
|
||||||
|
hasCheckedServer = true;
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
// 处理服务器无法连接的情况
|
||||||
|
if (!error.response || error.code === 'ECONNABORTED' || error.message.includes('Network Error')) {
|
||||||
|
console.error('Server appears to be down. Switching to mock data.');
|
||||||
|
isServerDown = true;
|
||||||
|
hasCheckedServer = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle errors in the response
|
// Handle errors in the response
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
// monitor /verify
|
// monitor /verify
|
||||||
@ -56,45 +74,144 @@ api.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define common HTTP methods
|
// 检查服务器状态
|
||||||
|
export const checkServerStatus = async () => {
|
||||||
|
try {
|
||||||
|
await api.get('/health-check', { timeout: 3000 });
|
||||||
|
isServerDown = false;
|
||||||
|
hasCheckedServer = true;
|
||||||
|
console.log('Server connection established');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
isServerDown = true;
|
||||||
|
hasCheckedServer = true;
|
||||||
|
console.error('Server connection failed, using mock data');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始检查服务器状态
|
||||||
|
checkServerStatus();
|
||||||
|
|
||||||
|
// Define common HTTP methods with fallback to mock API
|
||||||
const get = async (url, params = {}) => {
|
const get = async (url, params = {}) => {
|
||||||
const res = await api.get(url, { params });
|
try {
|
||||||
return res.data;
|
if (isServerDown) {
|
||||||
|
console.log(`[MOCK MODE] GET ${url}`);
|
||||||
|
return await mockGet(url, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.get(url, { params });
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (!hasCheckedServer || (error.request && !error.response)) {
|
||||||
|
console.log(`Failed to connect to server. Falling back to mock API for GET ${url}`);
|
||||||
|
return await mockGet(url, params);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle POST requests for JSON data
|
// Handle POST requests for JSON data with fallback to mock API
|
||||||
const post = async (url, data, isMultipart = false) => {
|
const post = async (url, data, isMultipart = false) => {
|
||||||
const headers = isMultipart
|
try {
|
||||||
? { 'Content-Type': 'multipart/form-data' } // For file uploads
|
if (isServerDown) {
|
||||||
: { 'Content-Type': 'application/json' }; // For JSON data
|
console.log(`[MOCK MODE] POST ${url}`);
|
||||||
|
return await mockPost(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
const res = await api.post(url, data, { headers });
|
const headers = isMultipart
|
||||||
return res.data;
|
? { 'Content-Type': 'multipart/form-data' } // For file uploads
|
||||||
|
: { 'Content-Type': 'application/json' }; // For JSON data
|
||||||
|
|
||||||
|
const res = await api.post(url, data, { headers });
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (!hasCheckedServer || (error.request && !error.response)) {
|
||||||
|
console.log(`Failed to connect to server. Falling back to mock API for POST ${url}`);
|
||||||
|
return await mockPost(url, data);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle PUT requests
|
// Handle PUT requests with fallback to mock API
|
||||||
const put = async (url, data) => {
|
const put = async (url, data) => {
|
||||||
const res = await api.put(url, data, {
|
try {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
if (isServerDown) {
|
||||||
});
|
console.log(`[MOCK MODE] PUT ${url}`);
|
||||||
return res.data;
|
return await mockPut(url, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.put(url, data, {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (!hasCheckedServer || (error.request && !error.response)) {
|
||||||
|
console.log(`Failed to connect to server. Falling back to mock API for PUT ${url}`);
|
||||||
|
return await mockPut(url, data);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle DELETE requests
|
// Handle DELETE requests with fallback to mock API
|
||||||
const del = async (url) => {
|
const del = async (url) => {
|
||||||
const res = await api.delete(url);
|
try {
|
||||||
return res.data;
|
if (isServerDown) {
|
||||||
|
console.log(`[MOCK MODE] DELETE ${url}`);
|
||||||
|
return await mockDelete(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.delete(url);
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (!hasCheckedServer || (error.request && !error.response)) {
|
||||||
|
console.log(`Failed to connect to server. Falling back to mock API for DELETE ${url}`);
|
||||||
|
return await mockDelete(url);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const upload = async (url, data) => {
|
const upload = async (url, data) => {
|
||||||
const axiosInstance = await axios.create({
|
try {
|
||||||
baseURL: '/api',
|
if (isServerDown) {
|
||||||
headers: {
|
console.log(`[MOCK MODE] Upload ${url}`);
|
||||||
'Content-Type': 'multipart/form-data',
|
return await mockPost(url, data, true);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
const res = await axiosInstance.post(url, data);
|
const axiosInstance = await axios.create({
|
||||||
return res.data;
|
baseURL: '/api',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const res = await axiosInstance.post(url, data);
|
||||||
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (!hasCheckedServer || (error.request && !error.response)) {
|
||||||
|
console.log(`Failed to connect to server. Falling back to mock API for Upload ${url}`);
|
||||||
|
return await mockPost(url, data, true);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 手动切换到模拟API(为调试目的)
|
||||||
|
export const switchToMockApi = () => {
|
||||||
|
isServerDown = true;
|
||||||
|
hasCheckedServer = true;
|
||||||
|
console.log('Manually switched to mock API');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 手动切换回真实API
|
||||||
|
export const switchToRealApi = async () => {
|
||||||
|
// 重新检查服务器状态
|
||||||
|
const isServerUp = await checkServerStatus();
|
||||||
|
console.log(isServerUp ? 'Switched back to real API' : 'Server still down, continuing with mock API');
|
||||||
|
return isServerUp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { get, post, put, del, upload };
|
export { get, post, put, del, upload };
|
||||||
|
@ -4,75 +4,43 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
// Mock data for knowledge bases
|
// Mock data for knowledge bases
|
||||||
const mockKnowledgeBases = [
|
const mockKnowledgeBases = [
|
||||||
{
|
{
|
||||||
id: 'kb-001',
|
id: uuidv4(),
|
||||||
|
user_id: 'user-001',
|
||||||
name: 'Frontend Development',
|
name: 'Frontend Development',
|
||||||
description: 'Resources and guides for frontend development including React, Vue, and Angular',
|
desc: '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',
|
type: 'private',
|
||||||
department: '研发部',
|
department: '研发部',
|
||||||
group: '前端开发组',
|
group: '前端开发组',
|
||||||
owner: {
|
documents: [],
|
||||||
id: 'user-001',
|
char_length: 0,
|
||||||
username: 'johndoe',
|
document_count: 0,
|
||||||
email: 'john@example.com',
|
external_id: uuidv4(),
|
||||||
department: '研发部',
|
create_time: '2024-02-26T08:30:00Z',
|
||||||
group: '前端开发组',
|
update_time: '2024-02-26T14:45:00Z',
|
||||||
},
|
|
||||||
document_count: 15,
|
|
||||||
tags: ['react', 'javascript', 'frontend'],
|
|
||||||
permissions: {
|
permissions: {
|
||||||
can_edit: true,
|
|
||||||
can_read: true,
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: false,
|
||||||
},
|
},
|
||||||
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',
|
id: uuidv4(),
|
||||||
|
user_id: 'user-001',
|
||||||
name: 'Backend Technologies',
|
name: 'Backend Technologies',
|
||||||
description: 'Information about backend frameworks, databases, and server configurations',
|
desc: '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',
|
type: 'private',
|
||||||
owner: {
|
department: '研发部',
|
||||||
id: 'user-001',
|
group: '后端开发组',
|
||||||
username: 'johndoe',
|
documents: [],
|
||||||
email: 'john@example.com',
|
char_length: 0,
|
||||||
},
|
document_count: 0,
|
||||||
document_count: 23,
|
external_id: uuidv4(),
|
||||||
tags: ['nodejs', 'python', 'databases'],
|
create_time: '2024-02-25T10:15:00Z',
|
||||||
|
update_time: '2024-02-26T09:20:00Z',
|
||||||
permissions: {
|
permissions: {
|
||||||
can_edit: true,
|
|
||||||
can_read: true,
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -206,6 +174,36 @@ const mockKnowledgeBases = [
|
|||||||
// In-memory store for CRUD operations
|
// In-memory store for CRUD operations
|
||||||
let knowledgeBases = [...mockKnowledgeBases];
|
let knowledgeBases = [...mockKnowledgeBases];
|
||||||
|
|
||||||
|
// Mock user data for authentication
|
||||||
|
const mockUsers = [
|
||||||
|
{
|
||||||
|
id: 'user-001',
|
||||||
|
username: 'leader2',
|
||||||
|
password: 'leader123', // 在实际应用中不应该存储明文密码
|
||||||
|
email: 'admin@example.com',
|
||||||
|
name: '管理员',
|
||||||
|
department: '研发部',
|
||||||
|
group: '前端开发组',
|
||||||
|
role: 'admin',
|
||||||
|
avatar: null,
|
||||||
|
created_at: '2024-01-01T00:00:00Z',
|
||||||
|
updated_at: '2024-01-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user-002',
|
||||||
|
username: 'user',
|
||||||
|
password: 'user123', // 在实际应用中不应该存储明文密码
|
||||||
|
email: 'user@example.com',
|
||||||
|
name: '普通用户',
|
||||||
|
department: '市场部',
|
||||||
|
group: '市场组',
|
||||||
|
role: 'user',
|
||||||
|
avatar: null,
|
||||||
|
created_at: '2024-01-02T00:00:00Z',
|
||||||
|
updated_at: '2024-01-02T00:00:00Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Helper function for pagination
|
// Helper function for pagination
|
||||||
const paginate = (array, page_size, page) => {
|
const paginate = (array, page_size, page) => {
|
||||||
const startIndex = (page - 1) * page_size;
|
const startIndex = (page - 1) * page_size;
|
||||||
@ -220,18 +218,314 @@ const paginate = (array, page_size, page) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 导入聊天历史模拟数据和方法
|
// Mock chat history data
|
||||||
import {
|
const mockChatHistory = [
|
||||||
mockChatHistory,
|
{
|
||||||
mockGetChatHistory,
|
id: 'chat-001',
|
||||||
mockCreateChat,
|
title: '关于React组件开发的问题',
|
||||||
mockUpdateChat,
|
knowledge_base_id: 'kb-001',
|
||||||
mockDeleteChat,
|
knowledge_base_name: 'Frontend Development',
|
||||||
} from '../store/chatHistory/chatHistory.mock';
|
message_count: 5,
|
||||||
|
created_at: '2024-03-15T10:30:00Z',
|
||||||
|
updated_at: '2024-03-15T11:45:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chat-002',
|
||||||
|
title: 'Vue.js性能优化讨论',
|
||||||
|
knowledge_base_id: 'kb-001',
|
||||||
|
knowledge_base_name: 'Frontend Development',
|
||||||
|
message_count: 3,
|
||||||
|
created_at: '2024-03-14T15:20:00Z',
|
||||||
|
updated_at: '2024-03-14T16:10:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chat-003',
|
||||||
|
title: '后端API集成问题',
|
||||||
|
knowledge_base_id: 'kb-002',
|
||||||
|
knowledge_base_name: 'Backend Technologies',
|
||||||
|
message_count: 4,
|
||||||
|
created_at: '2024-03-13T09:15:00Z',
|
||||||
|
updated_at: '2024-03-13T10:30:00Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock chat history functions
|
||||||
|
const mockGetChatHistory = (params) => {
|
||||||
|
const { page = 1, page_size = 10 } = params;
|
||||||
|
return paginate(mockChatHistory, page_size, page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCreateChat = (data) => {
|
||||||
|
const newChat = {
|
||||||
|
id: `chat-${uuidv4().slice(0, 8)}`,
|
||||||
|
title: data.title || '新对话',
|
||||||
|
knowledge_base_id: data.knowledge_base_id,
|
||||||
|
knowledge_base_name: data.knowledge_base_name,
|
||||||
|
message_count: 0,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
mockChatHistory.unshift(newChat);
|
||||||
|
return newChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockUpdateChat = (id, data) => {
|
||||||
|
const index = mockChatHistory.findIndex((chat) => chat.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error('Chat not found');
|
||||||
|
}
|
||||||
|
const updatedChat = {
|
||||||
|
...mockChatHistory[index],
|
||||||
|
...data,
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
mockChatHistory[index] = updatedChat;
|
||||||
|
return updatedChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDeleteChat = (id) => {
|
||||||
|
const index = mockChatHistory.findIndex((chat) => chat.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new Error('Chat not found');
|
||||||
|
}
|
||||||
|
mockChatHistory.splice(index, 1);
|
||||||
|
return { success: true };
|
||||||
|
};
|
||||||
|
|
||||||
// 模拟聊天消息数据
|
// 模拟聊天消息数据
|
||||||
const chatMessages = {};
|
const chatMessages = {};
|
||||||
|
|
||||||
|
// 模拟待处理权限申请
|
||||||
|
const mockPendingRequests = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
applicant: {
|
||||||
|
name: '王五',
|
||||||
|
department: '达人组',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '达人直播数据报告',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要查看和编辑直播数据报告',
|
||||||
|
created_at: '2024-01-07T10:30:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
applicant: {
|
||||||
|
name: '赵六',
|
||||||
|
department: '直播组',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '人力资源政策文件',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要了解最新的人力资源政策',
|
||||||
|
created_at: '2024-01-06T14:20:00Z',
|
||||||
|
expires_at: '2025-01-06T14:20:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
applicant: {
|
||||||
|
name: '钱七',
|
||||||
|
department: '市场部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '市场分析报告',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要了解市场趋势',
|
||||||
|
created_at: '2024-01-05T09:15:00Z',
|
||||||
|
expires_at: '2024-07-05T09:15:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
applicant: {
|
||||||
|
name: '孙八',
|
||||||
|
department: '技术部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '技术架构文档',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: true,
|
||||||
|
},
|
||||||
|
reason: '需要进行技术架构更新',
|
||||||
|
created_at: '2024-01-04T16:45:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
applicant: {
|
||||||
|
name: '周九',
|
||||||
|
department: '产品部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '产品规划文档',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要参与产品规划讨论',
|
||||||
|
created_at: '2024-01-03T11:30:00Z',
|
||||||
|
expires_at: '2024-12-31T23:59:59Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
applicant: {
|
||||||
|
name: '吴十',
|
||||||
|
department: '设计部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '设计规范文档',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要参考设计规范',
|
||||||
|
created_at: '2024-01-02T14:20:00Z',
|
||||||
|
expires_at: '2024-06-30T23:59:59Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
applicant: {
|
||||||
|
name: '郑十一',
|
||||||
|
department: '财务部',
|
||||||
|
},
|
||||||
|
knowledge_base: {
|
||||||
|
name: '财务报表',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_delete: false,
|
||||||
|
},
|
||||||
|
reason: '需要查看财务数据',
|
||||||
|
created_at: '2024-01-01T09:00:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟用户权限详情
|
||||||
|
const mockUserPermissions = {
|
||||||
|
'user-001': [
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-001',
|
||||||
|
name: '达人直播数据报告',
|
||||||
|
department: '达人组',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-10T14:30:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-002',
|
||||||
|
name: '人力资源政策文件',
|
||||||
|
department: '人力资源组',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-08T09:15:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-003',
|
||||||
|
name: '市场分析报告',
|
||||||
|
department: '市场部',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'user-002': [
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-001',
|
||||||
|
name: '达人直播数据报告',
|
||||||
|
department: '达人组',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-05T10:20:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-004',
|
||||||
|
name: '产品规划文档',
|
||||||
|
department: '产品部',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_admin: true,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-15T11:20:00Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'user-003': [
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-003',
|
||||||
|
name: '市场分析报告',
|
||||||
|
department: '市场部',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: true,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-12T15:40:00Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
knowledge_base: {
|
||||||
|
id: 'kb-005',
|
||||||
|
name: 'UI/UX设计指南',
|
||||||
|
department: '设计部',
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
can_read: true,
|
||||||
|
can_edit: false,
|
||||||
|
can_admin: false,
|
||||||
|
},
|
||||||
|
last_access_time: '2024-03-01T09:10:00Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
// Mock API functions
|
// Mock API functions
|
||||||
export const mockGet = async (url, config = {}) => {
|
export const mockGet = async (url, config = {}) => {
|
||||||
console.log(`[MOCK API] GET ${url}`, config);
|
console.log(`[MOCK API] GET ${url}`, config);
|
||||||
@ -239,12 +533,34 @@ export const mockGet = async (url, config = {}) => {
|
|||||||
// Simulate network delay
|
// Simulate network delay
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
// Get knowledge bases
|
// Get current user
|
||||||
if (url === '/knowledge-bases/') {
|
if (url === '/users/me/') {
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
items: knowledgeBases,
|
code: 200,
|
||||||
total: knowledgeBases.length,
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
user: mockUsers[0], // 默认返回第一个用户
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get knowledge bases
|
||||||
|
if (url === '/knowledge-bases/') {
|
||||||
|
const params = config.params || { page: 1, page_size: 10 };
|
||||||
|
const result = paginate(knowledgeBases, params.page_size, params.page);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: '获取知识库列表成功',
|
||||||
|
data: {
|
||||||
|
total: result.total,
|
||||||
|
page: result.page,
|
||||||
|
page_size: result.page_size,
|
||||||
|
items: result.items,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -258,13 +574,28 @@ export const mockGet = async (url, config = {}) => {
|
|||||||
throw { response: { status: 404, data: { message: 'Knowledge base not found' } } };
|
throw { response: { status: 404, data: { message: 'Knowledge base not found' } } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: knowledgeBase };
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
knowledge_base: knowledgeBase,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chat history
|
// Get chat history
|
||||||
if (url === '/chat-history/') {
|
if (url === '/chat-history/') {
|
||||||
const params = config.params || { page: 1, page_size: 10 };
|
const params = config.params || { page: 1, page_size: 10 };
|
||||||
return { data: mockGetChatHistory(params) };
|
const result = mockGetChatHistory(params);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: result,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chat messages
|
// Get chat messages
|
||||||
@ -309,7 +640,55 @@ export const mockGet = async (url, config = {}) => {
|
|||||||
kb.description.toLowerCase().includes(keyword.toLowerCase()) ||
|
kb.description.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||||
kb.tags.some((tag) => tag.toLowerCase().includes(keyword.toLowerCase()))
|
kb.tags.some((tag) => tag.toLowerCase().includes(keyword.toLowerCase()))
|
||||||
);
|
);
|
||||||
return paginate(filtered, page_size, page);
|
const result = paginate(filtered, page_size, page);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: result,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户权限管理 - 获取用户列表
|
||||||
|
if (url === '/users/permissions/') {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
users: mockUsers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户权限管理 - 获取待处理申请
|
||||||
|
if (url === '/permissions/pending/') {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
pending_requests: mockPendingRequests,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户权限管理 - 获取用户权限详情
|
||||||
|
if (url.match(/\/users\/(.+)\/permissions\//)) {
|
||||||
|
const userId = url.match(/\/users\/(.+)\/permissions\//)[1];
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
permissions: mockUserPermissions[userId] || [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||||
@ -321,6 +700,47 @@ export const mockPost = async (url, data) => {
|
|||||||
// Simulate network delay
|
// Simulate network delay
|
||||||
await new Promise((resolve) => setTimeout(resolve, 800));
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||||
|
|
||||||
|
// Login
|
||||||
|
if (url === '/auth/login/') {
|
||||||
|
const { username, password } = data;
|
||||||
|
const user = mockUsers.find((u) => u.username === username && u.password === password);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw {
|
||||||
|
response: {
|
||||||
|
status: 401,
|
||||||
|
data: {
|
||||||
|
code: 401,
|
||||||
|
message: '用户名或密码错误',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在实际应用中,这里应该生成 JWT token
|
||||||
|
const token = `mock-jwt-token-${uuidv4()}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: '登录成功',
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
department: user.department,
|
||||||
|
group: user.group,
|
||||||
|
role: user.role,
|
||||||
|
avatar: user.avatar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Create knowledge base
|
// Create knowledge base
|
||||||
if (url === '/knowledge-bases/') {
|
if (url === '/knowledge-bases/') {
|
||||||
const newKnowledgeBase = {
|
const newKnowledgeBase = {
|
||||||
@ -351,20 +771,30 @@ export const mockPost = async (url, data) => {
|
|||||||
|
|
||||||
knowledgeBases.push(newKnowledgeBase);
|
knowledgeBases.push(newKnowledgeBase);
|
||||||
|
|
||||||
// 模拟后端返回格式
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
|
||||||
message: '知识库创建成功',
|
|
||||||
data: {
|
data: {
|
||||||
knowledge_base: newKnowledgeBase,
|
code: 200,
|
||||||
external_id: uuidv4(),
|
message: '知识库创建成功',
|
||||||
|
data: {
|
||||||
|
knowledge_base: newKnowledgeBase,
|
||||||
|
external_id: uuidv4(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new chat
|
// Create new chat
|
||||||
if (url === '/chat-history/') {
|
if (url === '/chat-history/') {
|
||||||
return { data: mockCreateChat(data) };
|
const newChat = mockCreateChat(data);
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
code: 200,
|
||||||
|
message: 'success',
|
||||||
|
data: {
|
||||||
|
chat: newChat,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send chat message
|
// Send chat message
|
||||||
@ -421,6 +851,42 @@ export const mockPost = async (url, data) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批准权限申请
|
||||||
|
if (url === '/permissions/approve/') {
|
||||||
|
const { id, responseMessage } = data;
|
||||||
|
|
||||||
|
// 从待处理列表中移除该申请
|
||||||
|
mockPendingRequests = mockPendingRequests.filter((request) => request.id !== id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Permission approved successfully',
|
||||||
|
data: {
|
||||||
|
id: id,
|
||||||
|
status: 'approved',
|
||||||
|
response_message: responseMessage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拒绝权限申请
|
||||||
|
if (url === '/permissions/reject/') {
|
||||||
|
const { id, responseMessage } = data;
|
||||||
|
|
||||||
|
// 从待处理列表中移除该申请
|
||||||
|
mockPendingRequests = mockPendingRequests.filter((request) => request.id !== id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Permission rejected successfully',
|
||||||
|
data: {
|
||||||
|
id: id,
|
||||||
|
status: 'rejected',
|
||||||
|
response_message: responseMessage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -465,10 +931,47 @@ export const mockPut = async (url, data) => {
|
|||||||
return { data: mockUpdateChat(id, data) };
|
return { data: mockUpdateChat(id, data) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新用户权限
|
||||||
|
if (url.match(/\/users\/(.+)\/permissions\//)) {
|
||||||
|
const userId = url.match(/\/users\/(.+)\/permissions\//)[1];
|
||||||
|
const { permissions } = data;
|
||||||
|
|
||||||
|
// 将权限更新应用到模拟数据
|
||||||
|
if (mockUserPermissions[userId]) {
|
||||||
|
// 遍历permissions对象,更新对应知识库的权限
|
||||||
|
Object.entries(permissions).forEach(([knowledgeBaseId, permissionType]) => {
|
||||||
|
// 查找该用户的该知识库权限
|
||||||
|
const permissionIndex = mockUserPermissions[userId].findIndex(
|
||||||
|
(p) => p.knowledge_base.id === knowledgeBaseId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (permissionIndex !== -1) {
|
||||||
|
// 根据权限类型设置具体权限
|
||||||
|
const permission = {
|
||||||
|
can_read: permissionType === 'read' || permissionType === 'edit' || permissionType === 'admin',
|
||||||
|
can_edit: permissionType === 'edit' || permissionType === 'admin',
|
||||||
|
can_admin: permissionType === 'admin',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新权限
|
||||||
|
mockUserPermissions[userId][permissionIndex].permission = permission;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
message: 'Permissions updated successfully',
|
||||||
|
data: {
|
||||||
|
permissions: permissions,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
throw { response: { status: 404, data: { message: 'Not found' } } };
|
throw { response: { status: 404, data: { message: 'Not found' } } };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockDel = async (url) => {
|
export const mockDelete = async (url) => {
|
||||||
console.log(`[MOCK API] DELETE ${url}`);
|
console.log(`[MOCK API] DELETE ${url}`);
|
||||||
|
|
||||||
// Simulate network delay
|
// Simulate network delay
|
||||||
|
@ -80,10 +80,7 @@ const knowledgeBaseSlice = createSlice({
|
|||||||
})
|
})
|
||||||
.addCase(fetchKnowledgeBases.fulfilled, (state, action) => {
|
.addCase(fetchKnowledgeBases.fulfilled, (state, action) => {
|
||||||
state.list.status = 'succeeded';
|
state.list.status = 'succeeded';
|
||||||
state.list.items = action.payload.items;
|
state.list.data = action.payload;
|
||||||
state.list.total = action.payload.total;
|
|
||||||
state.list.page = action.payload.page;
|
|
||||||
state.list.page_size = action.payload.page_size;
|
|
||||||
state.list.error = null;
|
state.list.error = null;
|
||||||
})
|
})
|
||||||
.addCase(fetchKnowledgeBases.rejected, (state, action) => {
|
.addCase(fetchKnowledgeBases.rejected, (state, action) => {
|
||||||
|
@ -11,7 +11,11 @@ export const fetchKnowledgeBases = createAsyncThunk(
|
|||||||
'knowledgeBase/fetchKnowledgeBases',
|
'knowledgeBase/fetchKnowledgeBases',
|
||||||
async ({ page = 1, page_size = 10 } = {}, { rejectWithValue }) => {
|
async ({ page = 1, page_size = 10 } = {}, { rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const response = await get('/knowledge-bases/', { page, page_size });
|
const response = await get('/knowledge-bases/', { params: { page, page_size } });
|
||||||
|
// 处理新的返回格式
|
||||||
|
if (response.data && response.data.code === 200) {
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return rejectWithValue(error.response?.data || 'Failed to fetch knowledge bases');
|
return rejectWithValue(error.response?.data || 'Failed to fetch knowledge bases');
|
||||||
|
Loading…
Reference in New Issue
Block a user