mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 04:58:13 +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');
|
||||
try {
|
||||
await dispatch(checkAuthThunk()).unwrap();
|
||||
if (user) navigate('/');
|
||||
} catch (error) {}
|
||||
console.log('user', user);
|
||||
if (!user) navigate('/login');
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
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 location = useLocation();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
console.log('user', user);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
|
@ -10,7 +10,7 @@ import { PersistGate } from 'redux-persist/integration/react';
|
||||
import Loading from './components/Loading.jsx';
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
// <StrictMode>
|
||||
<PersistGate loading={<Loading />} persistor={persistor}>
|
||||
<BrowserRouter>
|
||||
<Provider store={store}>
|
||||
@ -18,5 +18,5 @@ createRoot(document.getElementById('root')).render(
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</PersistGate>
|
||||
</StrictMode>
|
||||
// </StrictMode>
|
||||
);
|
||||
|
@ -52,10 +52,9 @@ export default function KnowledgeBase() {
|
||||
});
|
||||
|
||||
// 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 {
|
||||
items: searchResults,
|
||||
total: searchTotal,
|
||||
data: searchData,
|
||||
status: searchStatus,
|
||||
error: searchError,
|
||||
keyword: storeKeyword,
|
||||
@ -63,8 +62,8 @@ export default function KnowledgeBase() {
|
||||
const { status: operationStatus, error: operationError } = useSelector((state) => state.knowledgeBase.operations);
|
||||
|
||||
// Determine which data to display based on search state
|
||||
const displayData = isSearching ? searchResults : knowledgeBases;
|
||||
const displayTotal = isSearching ? searchTotal : total;
|
||||
const displayData = isSearching ? searchData?.items : data?.items;
|
||||
const displayTotal = isSearching ? searchData?.total : data?.total;
|
||||
const displayStatus = isSearching ? searchStatus : status;
|
||||
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 { useNavigate } from 'react-router-dom';
|
||||
import PendingRequests from './components/PendingRequests';
|
||||
import UserPermissions from './components/UserPermissions';
|
||||
import ApiModeSwitch from '../../components/ApiModeSwitch';
|
||||
import './Permissions.css';
|
||||
|
||||
export default function PermissionsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const [activeTab, setActiveTab] = useState('pending');
|
||||
|
||||
// 检查用户是否有管理权限(leader 或 admin)
|
||||
useEffect(() => {
|
||||
@ -17,28 +18,18 @@ export default function PermissionsPage() {
|
||||
}, [user, navigate]);
|
||||
|
||||
return (
|
||||
<div className='permissions-container container py-4'>
|
||||
<div className='permissions-container'>
|
||||
<div className='api-mode-control mb-3'>
|
||||
<ApiModeSwitch />
|
||||
</div>
|
||||
|
||||
<ul className='nav nav-tabs mb-4'>
|
||||
<li className='nav-item'>
|
||||
<button
|
||||
className={`nav-link ${activeTab === 'pending' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('pending')}
|
||||
>
|
||||
待处理申请
|
||||
</button>
|
||||
</li>
|
||||
<li className='nav-item'>
|
||||
<button
|
||||
className={`nav-link ${activeTab === 'users' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('users')}
|
||||
>
|
||||
用户权限管理
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div className='permissions-section mb-4'>
|
||||
<PendingRequests />
|
||||
</div>
|
||||
|
||||
<div className='tab-content'>{activeTab === 'pending' ? <PendingRequests /> : <UserPermissions />}</div>
|
||||
<div className='permissions-section'>
|
||||
<UserPermissions />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -35,3 +35,217 @@
|
||||
.badge.bg-secondary {
|
||||
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';
|
||||
import { resetApproveRejectStatus } from '../../../store/permissions/permissions.slice';
|
||||
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() {
|
||||
const dispatch = useDispatch();
|
||||
@ -15,14 +149,19 @@ export default function PendingRequests() {
|
||||
const [showResponseInput, setShowResponseInput] = useState(false);
|
||||
const [currentRequestId, setCurrentRequestId] = useState(null);
|
||||
const [isApproving, setIsApproving] = useState(false);
|
||||
const [selectedRequest, setSelectedRequest] = useState(null);
|
||||
const [showSlideOver, setShowSlideOver] = useState(false);
|
||||
|
||||
// 从Redux store获取权限申请列表和状态
|
||||
const {
|
||||
items: pendingRequests,
|
||||
status: fetchStatus,
|
||||
error: fetchError,
|
||||
} = useSelector((state) => state.permissions.permissions);
|
||||
// 使用本地状态管理待处理申请列表
|
||||
const [pendingRequests, setPendingRequests] = useState([]);
|
||||
const [fetchStatus, setFetchStatus] = useState('idle');
|
||||
const [fetchError, setFetchError] = useState(null);
|
||||
|
||||
// 分页状态
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
|
||||
// 从Redux store获取批准/拒绝操作的状态
|
||||
const {
|
||||
status: approveRejectStatus,
|
||||
error: approveRejectError,
|
||||
@ -31,7 +170,38 @@ export default function PendingRequests() {
|
||||
|
||||
// 获取待处理申请列表
|
||||
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]);
|
||||
|
||||
// 监听批准/拒绝操作的状态变化
|
||||
@ -46,6 +216,21 @@ export default function PendingRequests() {
|
||||
setShowResponseInput(false);
|
||||
setCurrentRequestId(null);
|
||||
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());
|
||||
} else if (approveRejectStatus === 'failed') {
|
||||
@ -58,7 +243,15 @@ export default function PendingRequests() {
|
||||
// 重置状态
|
||||
dispatch(resetApproveRejectStatus());
|
||||
}
|
||||
}, [approveRejectStatus, approveRejectError, dispatch, isApproving]);
|
||||
}, [
|
||||
approveRejectStatus,
|
||||
approveRejectError,
|
||||
dispatch,
|
||||
isApproving,
|
||||
currentRequestId,
|
||||
pendingRequests,
|
||||
currentPage,
|
||||
]);
|
||||
|
||||
// 打开回复输入框
|
||||
const handleOpenResponseInput = (requestId, approving) => {
|
||||
@ -90,19 +283,80 @@ export default function PendingRequests() {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取权限类型文本和样式
|
||||
const getPermissionTypeText = (permissions) => {
|
||||
if (permissions.can_read && !permissions.can_edit) {
|
||||
return '只读访问';
|
||||
} else if (permissions.can_edit) {
|
||||
return '完全访问';
|
||||
// 处理行点击,显示滑动面板
|
||||
const handleRowClick = (request) => {
|
||||
setSelectedRequest(request);
|
||||
setShowSlideOver(true);
|
||||
};
|
||||
|
||||
// 关闭滑动面板
|
||||
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 {
|
||||
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 (
|
||||
<div className='text-center py-5'>
|
||||
<div className='spinner-border' role='status'>
|
||||
@ -114,7 +368,7 @@ export default function PendingRequests() {
|
||||
}
|
||||
|
||||
// 渲染错误状态
|
||||
if (fetchStatus === 'failed') {
|
||||
if (fetchStatus === 'failed' && pendingRequests.length === 0) {
|
||||
return (
|
||||
<div className='alert alert-danger' role='alert'>
|
||||
{fetchError || '获取待处理申请失败'}
|
||||
@ -131,59 +385,159 @@ export default function PendingRequests() {
|
||||
);
|
||||
}
|
||||
|
||||
// 获取当前页的数据
|
||||
const currentPageData = getCurrentPageData();
|
||||
|
||||
// 渲染申请列表
|
||||
return (
|
||||
<>
|
||||
<div className='d-flex justify-content-between align-items-center mb-4'>
|
||||
{/* <h5 className='mb-0'>待处理申请</h5> */}
|
||||
<div className='bg-danger rounded-pill text-white py-1 px-2'>{pendingRequests.length}个待处理</div>
|
||||
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||
<h5 className='mb-0'>待处理申请</h5>
|
||||
<div className='badge bg-danger'>{pendingRequests.length}个待处理</div>
|
||||
</div>
|
||||
|
||||
<div className='permission-requests'>
|
||||
{pendingRequests.map((request) => (
|
||||
<div key={request.id} className='permission-card card mb-3 border-0 shadow-sm bg-light text-dark'>
|
||||
<div className='card-body p-4 d-flex flex-column gap-3'>
|
||||
<div className='d-flex justify-content-between'>
|
||||
<div className='pending-requests-list'>
|
||||
{currentPageData.map((request) => (
|
||||
<div key={request.id} className='pending-request-item' onClick={() => handleRowClick(request)}>
|
||||
<div className='request-header'>
|
||||
<div className='user-info'>
|
||||
<h6 className='mb-0'>{request.applicant.name}</h6>
|
||||
<p className='department'>{request.applicant.department}</p>
|
||||
</div>
|
||||
<div className='request-date'>{new Date(request.created_at).toLocaleDateString()}</div>
|
||||
</div>
|
||||
|
||||
<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'>{request.applicant.name || request.applicant}</h5>
|
||||
<p className='text-dark-50 mb-0'>{request.applicant.department || '无归属部门'}</p>
|
||||
</div>
|
||||
<div className='text-end'>
|
||||
<div className='text-dark-50 mb-0'>
|
||||
{new Date(request.created_at).toLocaleDateString()}
|
||||
<h5 className='mb-1'>
|
||||
{selectedRequest.applicant.name || selectedRequest.applicant}
|
||||
</h5>
|
||||
<p className='text-muted mb-0'>
|
||||
{selectedRequest.applicant.department || '无归属部门'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='d-flex flex-column gap-3'>
|
||||
<div>申请访问:{request.knowledge_base.name || request.knowledge_base}</div>
|
||||
<div className='d-flex flex-wrap gap-2'>
|
||||
{request.permissions.can_read && (
|
||||
<span className='badge bg-info custom-badge'>只读</span>
|
||||
<div className='mb-4'>
|
||||
<h6 className='text-muted mb-2'>知识库信息</h6>
|
||||
<p className='mb-1'>
|
||||
<strong>名称:</strong>{' '}
|
||||
{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 && (
|
||||
<span className='badge bg-success custom-badge'>编辑</span>
|
||||
)}
|
||||
{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()}
|
||||
{selectedRequest.permissions.can_edit && selectedRequest.permissions.can_delete && (
|
||||
<span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'>
|
||||
完全访问
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='text-dark-50'>
|
||||
<strong>申请理由:</strong> {request.reason}
|
||||
</div>
|
||||
|
||||
<div className='mb-4'>
|
||||
<h6 className='text-muted mb-2'>申请时间</h6>
|
||||
<p className='mb-1'>{new Date(selectedRequest.created_at).toLocaleString()}</p>
|
||||
</div>
|
||||
|
||||
{selectedRequest.expires_at && (
|
||||
<div className='mb-4'>
|
||||
<h6 className='text-muted mb-2'>到期时间</h6>
|
||||
<p className='mb-1'>{new Date(selectedRequest.expires_at).toLocaleString()}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mb-4'>
|
||||
<h6 className='text-muted mb-2'>申请理由</h6>
|
||||
<div className='p-3 bg-light rounded'>{selectedRequest.reason || '无申请理由'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='d-flex justify-content-end'>
|
||||
<div className='slide-over-footer'>
|
||||
<button
|
||||
className='btn btn-outline-danger me-2'
|
||||
onClick={() => handleOpenResponseInput(request.id, false)}
|
||||
disabled={processingId === request.id && approveRejectStatus === 'loading'}
|
||||
onClick={() => handleOpenResponseInput(selectedRequest.id, false)}
|
||||
disabled={processingId === selectedRequest.id && approveRejectStatus === 'loading'}
|
||||
>
|
||||
{processingId === request.id &&
|
||||
{processingId === selectedRequest.id &&
|
||||
approveRejectStatus === 'loading' &&
|
||||
!isApproving ? (
|
||||
<>
|
||||
@ -200,10 +554,12 @@ export default function PendingRequests() {
|
||||
</button>
|
||||
<button
|
||||
className='btn btn-success'
|
||||
onClick={() => handleOpenResponseInput(request.id, true)}
|
||||
disabled={processingId === request.id && approveRejectStatus === 'loading'}
|
||||
onClick={() => handleOpenResponseInput(selectedRequest.id, true)}
|
||||
disabled={processingId === selectedRequest.id && approveRejectStatus === 'loading'}
|
||||
>
|
||||
{processingId === request.id && approveRejectStatus === 'loading' && isApproving ? (
|
||||
{processingId === selectedRequest.id &&
|
||||
approveRejectStatus === 'loading' &&
|
||||
isApproving ? (
|
||||
<>
|
||||
<span
|
||||
className='spinner-border spinner-border-sm me-1'
|
||||
@ -218,8 +574,7 @@ export default function PendingRequests() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 回复输入弹窗 */}
|
||||
|
@ -1,6 +1,62 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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 }) {
|
||||
const [userPermissions, setUserPermissions] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -15,13 +71,29 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||
const response = await get(`/users/${user.id}/permissions/`);
|
||||
|
||||
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 {
|
||||
setError('获取用户权限详情失败');
|
||||
// API返回的数据为空,使用模拟数据
|
||||
console.log('API返回的用户权限数据为空,使用模拟数据');
|
||||
setUserPermissions(mockUserPermissions);
|
||||
}
|
||||
} else {
|
||||
// API请求失败,使用模拟数据
|
||||
console.log('API请求失败,使用模拟数据');
|
||||
setUserPermissions(mockUserPermissions);
|
||||
setError('获取用户权限详情失败,显示模拟数据');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户权限详情失败:', error);
|
||||
setError('获取用户权限详情失败');
|
||||
// API请求出错,使用模拟数据作为后备
|
||||
console.log('API请求出错,使用模拟数据作为后备');
|
||||
setUserPermissions(mockUserPermissions);
|
||||
setError('获取用户权限详情失败,显示模拟数据');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -72,7 +144,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||
|
||||
return (
|
||||
<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-header'>
|
||||
<h5 className='modal-title'>{user.name} 的权限详情</h5>
|
||||
@ -87,9 +159,77 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||
<p className='mt-3'>加载权限详情...</p>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className='alert alert-danger' role='alert'>
|
||||
<div>
|
||||
<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>
|
||||
) : userPermissions.length === 0 ? (
|
||||
<div className='alert alert-info' role='alert'>
|
||||
该用户暂无任何知识库权限
|
||||
@ -167,7 +307,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='btn btn-primary'
|
||||
className='btn btn-dark'
|
||||
onClick={handleSave}
|
||||
disabled={loading || Object.keys(updatedPermissions).length === 0}
|
||||
>
|
||||
@ -176,7 +316,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='modal-backdrop fade show'></div>
|
||||
<div className='modal-backdrop fade show' style={{ zIndex: 1050 }}></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 { showNotification } from '../../../store/notification.slice';
|
||||
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() {
|
||||
const dispatch = useDispatch();
|
||||
@ -11,6 +116,12 @@ export default function UserPermissions() {
|
||||
const [error, setError] = useState(null);
|
||||
const [selectedUser, setSelectedUser] = useState(null);
|
||||
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(() => {
|
||||
@ -20,13 +131,25 @@ export default function UserPermissions() {
|
||||
const response = await get('/users/permissions/');
|
||||
|
||||
if (response && response.code === 200) {
|
||||
setUsers(response.data.users || []);
|
||||
// 只有当API返回的用户列表为空时才使用模拟数据
|
||||
const apiUsers = response.data.users || [];
|
||||
if (apiUsers.length > 0) {
|
||||
setUsers(apiUsers);
|
||||
} else {
|
||||
setError('获取用户列表失败');
|
||||
console.log('API返回的用户列表为空,使用模拟数据');
|
||||
setUsers(mockUsers);
|
||||
}
|
||||
} else {
|
||||
// API请求失败,使用模拟数据
|
||||
console.log('API请求失败,使用模拟数据');
|
||||
setUsers(mockUsers);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error);
|
||||
setError('获取用户列表失败');
|
||||
// API请求出错,使用模拟数据作为后备
|
||||
console.log('API请求出错,使用模拟数据作为后备');
|
||||
setUsers(mockUsers);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -35,6 +158,16 @@ export default function UserPermissions() {
|
||||
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) => {
|
||||
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 (
|
||||
<div className='text-center py-5'>
|
||||
<div className='spinner-border' role='status'>
|
||||
@ -112,7 +336,7 @@ export default function UserPermissions() {
|
||||
}
|
||||
|
||||
// 渲染错误状态
|
||||
if (error) {
|
||||
if (error && users.length === 0) {
|
||||
return (
|
||||
<div className='alert alert-danger' role='alert'>
|
||||
{error}
|
||||
@ -129,60 +353,93 @@ export default function UserPermissions() {
|
||||
);
|
||||
}
|
||||
|
||||
// 获取当前页的数据
|
||||
const currentPageData = getCurrentPageData();
|
||||
|
||||
// 获取过滤后的总用户数
|
||||
const filteredUsersCount = getFilteredUsers().length;
|
||||
|
||||
// 渲染用户列表
|
||||
return (
|
||||
<>
|
||||
<div className='card'>
|
||||
<div className='card-header bg-white'>
|
||||
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||
<h5 className='mb-0'>用户权限管理</h5>
|
||||
<div className='input-group' style={{ maxWidth: '250px' }}>
|
||||
<input
|
||||
type='text'
|
||||
className='form-control'
|
||||
placeholder='搜索用户...'
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<span className='input-group-text'>
|
||||
<i className='bi bi-search'></i>
|
||||
</span>
|
||||
</div>
|
||||
<div className='card-body p-0'>
|
||||
</div>
|
||||
|
||||
{filteredUsersCount > 0 ? (
|
||||
<>
|
||||
<div className='card'>
|
||||
<div className='table-responsive'>
|
||||
<table className='table table-hover mb-0'>
|
||||
<thead className='table-light'>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>姓名</th>
|
||||
<th>部门</th>
|
||||
<th>职位</th>
|
||||
<th>权限</th>
|
||||
<th>操作</th>
|
||||
<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>
|
||||
{users.map((user) => (
|
||||
{currentPageData.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.name}</td>
|
||||
<td>{user.department}</td>
|
||||
<td>{user.position}</td>
|
||||
<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 gap-2'>
|
||||
<div className='d-flex flex-wrap gap-1'>
|
||||
{user.permissions_count.read > 0 && (
|
||||
<span className='badge bg-info'>
|
||||
只读: {user.permissions_count.read}
|
||||
<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-success'>
|
||||
编辑: {user.permissions_count.edit}
|
||||
<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'>
|
||||
管理: {user.permissions_count.admin}
|
||||
<span className='badge bg-danger d-flex align-items-center gap-1'>
|
||||
无访问权限: {user.permissions_count.admin}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<td className='text-end align-middle'>
|
||||
<button
|
||||
className='btn btn-sm btn-primary'
|
||||
className='btn btn-outline-dark btn-sm'
|
||||
onClick={() => handleOpenDetailsModal(user)}
|
||||
>
|
||||
权限详情
|
||||
修改权限
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@ -191,7 +448,15 @@ export default function UserPermissions() {
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 分页控件 */}
|
||||
{renderPagination()}
|
||||
</>
|
||||
) : (
|
||||
<div className='alert alert-info' role='alert'>
|
||||
没有找到匹配的用户
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 用户权限详情弹窗 */}
|
||||
{showDetailsModal && selectedUser && (
|
||||
|
@ -1,8 +1,13 @@
|
||||
import axios from 'axios';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { mockGet, mockPost, mockPut, mockDelete } from './mockApi';
|
||||
|
||||
const secretKey = import.meta.env.VITE_SECRETKEY;
|
||||
|
||||
// API连接状态
|
||||
let isServerDown = false;
|
||||
let hasCheckedServer = false;
|
||||
|
||||
// Create Axios instance with base URL
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
@ -28,9 +33,22 @@ api.interceptors.request.use(
|
||||
// Response Interceptor
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
// 如果成功收到响应,表示服务器正常工作
|
||||
if (!hasCheckedServer) {
|
||||
console.log('Server is up and running');
|
||||
isServerDown = false;
|
||||
hasCheckedServer = true;
|
||||
}
|
||||
return response;
|
||||
},
|
||||
(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
|
||||
if (error.response) {
|
||||
// monitor /verify
|
||||
@ -56,37 +74,114 @@ api.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
// Define common HTTP methods
|
||||
const get = async (url, params = {}) => {
|
||||
const res = await api.get(url, { params });
|
||||
return res.data;
|
||||
// 检查服务器状态
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle POST requests for JSON data
|
||||
// 初始检查服务器状态
|
||||
checkServerStatus();
|
||||
|
||||
// Define common HTTP methods with fallback to mock API
|
||||
const get = async (url, params = {}) => {
|
||||
try {
|
||||
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 with fallback to mock API
|
||||
const post = async (url, data, isMultipart = false) => {
|
||||
try {
|
||||
if (isServerDown) {
|
||||
console.log(`[MOCK MODE] POST ${url}`);
|
||||
return await mockPost(url, data);
|
||||
}
|
||||
|
||||
const headers = isMultipart
|
||||
? { '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) => {
|
||||
try {
|
||||
if (isServerDown) {
|
||||
console.log(`[MOCK MODE] PUT ${url}`);
|
||||
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) => {
|
||||
try {
|
||||
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) => {
|
||||
try {
|
||||
if (isServerDown) {
|
||||
console.log(`[MOCK MODE] Upload ${url}`);
|
||||
return await mockPost(url, data, true);
|
||||
}
|
||||
|
||||
const axiosInstance = await axios.create({
|
||||
baseURL: '/api',
|
||||
headers: {
|
||||
@ -95,6 +190,28 @@ const upload = async (url, 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 };
|
||||
|
@ -4,75 +4,43 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
// Mock data for knowledge bases
|
||||
const mockKnowledgeBases = [
|
||||
{
|
||||
id: 'kb-001',
|
||||
id: uuidv4(),
|
||||
user_id: 'user-001',
|
||||
name: 'Frontend Development',
|
||||
description: 'Resources and guides for frontend development including React, Vue, and Angular',
|
||||
created_at: '2023-10-15T08:30:00Z',
|
||||
updated_at: '2023-12-20T14:45:00Z',
|
||||
create_time: '2023-10-15T08:30:00Z',
|
||||
update_time: '2023-12-20T14:45:00Z',
|
||||
desc: 'Resources and guides for frontend development including React, Vue, and Angular',
|
||||
type: 'private',
|
||||
department: '研发部',
|
||||
group: '前端开发组',
|
||||
owner: {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
department: '研发部',
|
||||
group: '前端开发组',
|
||||
},
|
||||
document_count: 15,
|
||||
tags: ['react', 'javascript', 'frontend'],
|
||||
documents: [],
|
||||
char_length: 0,
|
||||
document_count: 0,
|
||||
external_id: uuidv4(),
|
||||
create_time: '2024-02-26T08:30:00Z',
|
||||
update_time: '2024-02-26T14:45:00Z',
|
||||
permissions: {
|
||||
can_edit: 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',
|
||||
description: 'Information about backend frameworks, databases, and server configurations',
|
||||
created_at: '2023-09-05T10:15:00Z',
|
||||
updated_at: '2024-01-10T09:20:00Z',
|
||||
create_time: '2023-09-05T10:15:00Z',
|
||||
update_time: '2024-01-10T09:20:00Z',
|
||||
desc: 'Information about backend frameworks, databases, and server configurations',
|
||||
type: 'private',
|
||||
owner: {
|
||||
id: 'user-001',
|
||||
username: 'johndoe',
|
||||
email: 'john@example.com',
|
||||
},
|
||||
document_count: 23,
|
||||
tags: ['nodejs', 'python', 'databases'],
|
||||
department: '研发部',
|
||||
group: '后端开发组',
|
||||
documents: [],
|
||||
char_length: 0,
|
||||
document_count: 0,
|
||||
external_id: uuidv4(),
|
||||
create_time: '2024-02-25T10:15:00Z',
|
||||
update_time: '2024-02-26T09:20:00Z',
|
||||
permissions: {
|
||||
can_edit: true,
|
||||
can_read: true,
|
||||
can_edit: true,
|
||||
can_delete: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -206,6 +174,36 @@ const mockKnowledgeBases = [
|
||||
// In-memory store for CRUD operations
|
||||
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
|
||||
const paginate = (array, page_size, page) => {
|
||||
const startIndex = (page - 1) * page_size;
|
||||
@ -220,18 +218,314 @@ const paginate = (array, page_size, page) => {
|
||||
};
|
||||
};
|
||||
|
||||
// 导入聊天历史模拟数据和方法
|
||||
import {
|
||||
mockChatHistory,
|
||||
mockGetChatHistory,
|
||||
mockCreateChat,
|
||||
mockUpdateChat,
|
||||
mockDeleteChat,
|
||||
} from '../store/chatHistory/chatHistory.mock';
|
||||
// Mock chat history data
|
||||
const mockChatHistory = [
|
||||
{
|
||||
id: 'chat-001',
|
||||
title: '关于React组件开发的问题',
|
||||
knowledge_base_id: 'kb-001',
|
||||
knowledge_base_name: 'Frontend Development',
|
||||
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 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
|
||||
export const mockGet = async (url, config = {}) => {
|
||||
console.log(`[MOCK API] GET ${url}`, config);
|
||||
@ -239,12 +533,34 @@ export const mockGet = async (url, config = {}) => {
|
||||
// Simulate network delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
// Get knowledge bases
|
||||
if (url === '/knowledge-bases/') {
|
||||
// Get current user
|
||||
if (url === '/users/me/') {
|
||||
return {
|
||||
data: {
|
||||
items: knowledgeBases,
|
||||
total: knowledgeBases.length,
|
||||
code: 200,
|
||||
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' } } };
|
||||
}
|
||||
|
||||
return { data: knowledgeBase };
|
||||
return {
|
||||
data: {
|
||||
code: 200,
|
||||
message: 'success',
|
||||
data: {
|
||||
knowledge_base: knowledgeBase,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Get chat history
|
||||
if (url === '/chat-history/') {
|
||||
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
|
||||
@ -309,7 +640,55 @@ export const mockGet = async (url, config = {}) => {
|
||||
kb.description.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' } } };
|
||||
@ -321,6 +700,47 @@ export const mockPost = async (url, data) => {
|
||||
// Simulate network delay
|
||||
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
|
||||
if (url === '/knowledge-bases/') {
|
||||
const newKnowledgeBase = {
|
||||
@ -351,20 +771,30 @@ export const mockPost = async (url, data) => {
|
||||
|
||||
knowledgeBases.push(newKnowledgeBase);
|
||||
|
||||
// 模拟后端返回格式
|
||||
return {
|
||||
data: {
|
||||
code: 200,
|
||||
message: '知识库创建成功',
|
||||
data: {
|
||||
knowledge_base: newKnowledgeBase,
|
||||
external_id: uuidv4(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Create new chat
|
||||
if (url === '/chat-history/') {
|
||||
return { data: mockCreateChat(data) };
|
||||
const newChat = mockCreateChat(data);
|
||||
return {
|
||||
data: {
|
||||
code: 200,
|
||||
message: 'success',
|
||||
data: {
|
||||
chat: newChat,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 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' } } };
|
||||
};
|
||||
|
||||
@ -465,10 +931,47 @@ export const mockPut = async (url, 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' } } };
|
||||
};
|
||||
|
||||
export const mockDel = async (url) => {
|
||||
export const mockDelete = async (url) => {
|
||||
console.log(`[MOCK API] DELETE ${url}`);
|
||||
|
||||
// Simulate network delay
|
||||
|
@ -80,10 +80,7 @@ const knowledgeBaseSlice = createSlice({
|
||||
})
|
||||
.addCase(fetchKnowledgeBases.fulfilled, (state, action) => {
|
||||
state.list.status = 'succeeded';
|
||||
state.list.items = action.payload.items;
|
||||
state.list.total = action.payload.total;
|
||||
state.list.page = action.payload.page;
|
||||
state.list.page_size = action.payload.page_size;
|
||||
state.list.data = action.payload;
|
||||
state.list.error = null;
|
||||
})
|
||||
.addCase(fetchKnowledgeBases.rejected, (state, action) => {
|
||||
|
@ -11,7 +11,11 @@ export const fetchKnowledgeBases = createAsyncThunk(
|
||||
'knowledgeBase/fetchKnowledgeBases',
|
||||
async ({ page = 1, page_size = 10 } = {}, { rejectWithValue }) => {
|
||||
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;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data || 'Failed to fetch knowledge bases');
|
||||
|
Loading…
Reference in New Issue
Block a user