diff --git a/src/App.jsx b/src/App.jsx
index bdcd35e..0c41c6e 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -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 ;
diff --git a/src/components/ApiModeSwitch.jsx b/src/components/ApiModeSwitch.jsx
new file mode 100644
index 0000000..1dbc4a6
--- /dev/null
+++ b/src/components/ApiModeSwitch.jsx
@@ -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 (
+
-
-
- -
-
-
- -
-
-
-
-
-
{activeTab === 'pending' ?
:
}
+
);
}
diff --git a/src/pages/Permissions/components/PendingRequests.css b/src/pages/Permissions/components/PendingRequests.css
index 976cfb7..9ddbd81 100644
--- a/src/pages/Permissions/components/PendingRequests.css
+++ b/src/pages/Permissions/components/PendingRequests.css
@@ -34,4 +34,218 @@
.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;
}
\ No newline at end of file
diff --git a/src/pages/Permissions/components/PendingRequests.jsx b/src/pages/Permissions/components/PendingRequests.jsx
index f222d62..5025dd2 100644
--- a/src/pages/Permissions/components/PendingRequests.jsx
+++ b/src/pages/Permissions/components/PendingRequests.jsx
@@ -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 (
+
+
+
+
+ {currentPage} / {totalPages}
+
+
+
+
+ );
+ };
+
// 渲染加载状态
- if (fetchStatus === 'loading') {
+ if (fetchStatus === 'loading' && pendingRequests.length === 0) {
return (
@@ -114,7 +368,7 @@ export default function PendingRequests() {
}
// 渲染错误状态
- if (fetchStatus === 'failed') {
+ if (fetchStatus === 'failed' && pendingRequests.length === 0) {
return (
{fetchError || '获取待处理申请失败'}
@@ -131,95 +385,196 @@ export default function PendingRequests() {
);
}
+ // 获取当前页的数据
+ const currentPageData = getCurrentPageData();
+
// 渲染申请列表
return (
<>
-
- {/*
待处理申请
*/}
-
{pendingRequests.length}个待处理
+
+
待处理申请
+
{pendingRequests.length}个待处理
-
- {pendingRequests.map((request) => (
-
-
-
-
-
{request.applicant.name || request.applicant}
-
{request.applicant.department || '无归属部门'}
-
-
-
- {new Date(request.created_at).toLocaleDateString()}
+
+ {currentPageData.map((request) => (
+
handleRowClick(request)}>
+
+
+
{request.applicant.name}
+
{request.applicant.department}
+
+
{new Date(request.created_at).toLocaleDateString()}
+
+
+
+
申请访问:{request.knowledge_base.name}
+
+ {request.permissions.can_edit ? (
+
+
+ 完全访问
+
+ ) : (
+ request.permissions.can_read && (
+
+
+ 只读访问
+
+ )
+ )}
+
+
+
+
+
+
+ ))}
+
+
+ {/* 分页控件 */}
+ {renderPagination()}
+
+ {/* 滑动面板 */}
+
+
+ {selectedRequest && (
+
+
+
申请详情
+
+
+
+
+
申请人信息
+
+
+ {(selectedRequest.applicant.name || selectedRequest.applicant).charAt(0)}
+
+
+
+ {selectedRequest.applicant.name || selectedRequest.applicant}
+
+
+ {selectedRequest.applicant.department || '无归属部门'}
+
-
-
申请访问:{request.knowledge_base.name || request.knowledge_base}
-
- {request.permissions.can_read && (
-
只读
+
+
知识库信息
+
+ 名称:{' '}
+ {selectedRequest.knowledge_base.name || selectedRequest.knowledge_base}
+
+
+
+
+
申请权限
+
+ {selectedRequest.permissions.can_read && !selectedRequest.permissions.can_edit && (
+
+ 只读
+
)}
- {request.permissions.can_edit && (
- 编辑
- )}
- {request.permissions.can_delete && (
- 删除
- )}
- {request.expires_at && (
-
- 至 {new Date(request.expires_at).toLocaleDateString()}
+ {selectedRequest.permissions.can_edit && selectedRequest.permissions.can_delete && (
+
+ 完全访问
)}
-
- 申请理由: {request.reason}
-
-
-
-
+
+
+
申请时间
+
{new Date(selectedRequest.created_at).toLocaleString()}
+
+
+ {selectedRequest.expires_at && (
+
+
到期时间
+
{new Date(selectedRequest.expires_at).toLocaleString()}
+
+ )}
+
+
+
申请理由
+
{selectedRequest.reason || '无申请理由'}
+
+
+
+
- ))}
+ )}
{/* 回复输入弹窗 */}
diff --git a/src/pages/Permissions/components/UserPermissionDetails.jsx b/src/pages/Permissions/components/UserPermissionDetails.jsx
index cd57416..6015a0b 100644
--- a/src/pages/Permissions/components/UserPermissionDetails.jsx
+++ b/src/pages/Permissions/components/UserPermissionDetails.jsx
@@ -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 {
+ // API返回的数据为空,使用模拟数据
+ console.log('API返回的用户权限数据为空,使用模拟数据');
+ setUserPermissions(mockUserPermissions);
+ }
} else {
- setError('获取用户权限详情失败');
+ // 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 (
-
+
{user.name} 的权限详情
@@ -87,8 +159,76 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
加载权限详情...
) : error ? (
-
- {error}
+
+
+ {error}
+
+ {userPermissions.length > 0 && (
+
+
+
+
+ 知识库名称 |
+ 所属部门 |
+ 当前权限 |
+ 最后访问时间 |
+ 操作 |
+
+
+
+ {userPermissions.map((item) => {
+ const currentPermissionType = getPermissionType(item.permission);
+ const updatedPermissionType =
+ updatedPermissions[item.knowledge_base.id] || currentPermissionType;
+
+ return (
+
+ {item.knowledge_base.name} |
+ {item.knowledge_base.department || '未指定'} |
+
+
+ {getPermissionTypeText(currentPermissionType)}
+
+ |
+
+ {item.last_access_time
+ ? new Date(item.last_access_time).toLocaleString()
+ : '从未访问'}
+ |
+
+
+ |
+
+ );
+ })}
+
+
+
+ )}
) : userPermissions.length === 0 ? (
@@ -167,7 +307,7 @@ export default function UserPermissionDetails({ user, onClose, onSave }) {
-
+
);
}
diff --git a/src/pages/Permissions/components/UserPermissions.css b/src/pages/Permissions/components/UserPermissions.css
new file mode 100644
index 0000000..1e1dd18
--- /dev/null
+++ b/src/pages/Permissions/components/UserPermissions.css
@@ -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;
+}
\ No newline at end of file
diff --git a/src/pages/Permissions/components/UserPermissions.jsx b/src/pages/Permissions/components/UserPermissions.jsx
index dc6d8d5..75b9ad0 100644
--- a/src/pages/Permissions/components/UserPermissions.jsx
+++ b/src/pages/Permissions/components/UserPermissions.jsx
@@ -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 {
+ console.log('API返回的用户列表为空,使用模拟数据');
+ setUsers(mockUsers);
+ }
} else {
- setError('获取用户列表失败');
+ // 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 (
+
+
+
+
+
+
+
+ );
+ };
+
// 渲染加载状态
- if (loading) {
+ if (loading && users.length === 0) {
return (
@@ -112,7 +336,7 @@ export default function UserPermissions() {
}
// 渲染错误状态
- if (error) {
+ if (error && users.length === 0) {
return (
{error}
@@ -129,70 +353,111 @@ export default function UserPermissions() {
);
}
+ // 获取当前页的数据
+ const currentPageData = getCurrentPageData();
+
+ // 获取过滤后的总用户数
+ const filteredUsersCount = getFilteredUsers().length;
+
// 渲染用户列表
return (
<>
-
-
-
用户权限管理
-
-
-
-
-
-
- 用户名 |
- 姓名 |
- 部门 |
- 职位 |
- 权限 |
- 操作 |
-
-
-
- {users.map((user) => (
-
- {user.username} |
- {user.name} |
- {user.department} |
- {user.position} |
-
- {user.permissions_count && (
-
- {user.permissions_count.read > 0 && (
-
- 只读: {user.permissions_count.read}
-
- )}
- {user.permissions_count.edit > 0 && (
-
- 编辑: {user.permissions_count.edit}
-
- )}
- {user.permissions_count.admin > 0 && (
-
- 管理: {user.permissions_count.admin}
-
- )}
-
- )}
- |
-
-
- |
-
- ))}
-
-
-
+
+ {filteredUsersCount > 0 ? (
+ <>
+
+
+
+
+
+ 用户 |
+ 部门 |
+ 职位 |
+ 数据集权限 |
+
+ 操作
+ |
+
+
+
+ {currentPageData.map((user) => (
+
+
+
+
+ {user.name.charAt(0)}
+
+
+ {user.name}
+ {user.username}
+
+
+ |
+ {user.department} |
+ {user.position} |
+
+ {user.permissions_count && (
+
+ {user.permissions_count.read > 0 && (
+
+ 完全访问: {user.permissions_count.read}
+
+ )}
+ {user.permissions_count.edit > 0 && (
+
+ 只读访问: {user.permissions_count.edit}
+
+ )}
+ {user.permissions_count.admin > 0 && (
+
+ 无访问权限: {user.permissions_count.admin}
+
+ )}
+
+ )}
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+
+ {/* 分页控件 */}
+ {renderPagination()}
+ >
+ ) : (
+
+ 没有找到匹配的用户
+
+ )}
+
{/* 用户权限详情弹窗 */}
{showDetailsModal && selectedUser && (
{
+ // 如果成功收到响应,表示服务器正常工作
+ 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,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 res = await api.get(url, { params });
- return res.data;
+ 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
+// Handle POST requests for JSON data with fallback to mock API
const post = async (url, data, isMultipart = false) => {
- const headers = isMultipart
- ? { 'Content-Type': 'multipart/form-data' } // For file uploads
- : { 'Content-Type': 'application/json' }; // For JSON data
+ try {
+ if (isServerDown) {
+ console.log(`[MOCK MODE] POST ${url}`);
+ return await mockPost(url, data);
+ }
- const res = await api.post(url, data, { headers });
- return res.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) => {
- const res = await api.put(url, data, {
- headers: { 'Content-Type': 'application/json' },
- });
- return res.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) => {
- const res = await api.delete(url);
- return res.data;
+ 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) => {
- const axiosInstance = await axios.create({
- baseURL: '/api',
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
- });
- const res = await axiosInstance.post(url, data);
- return res.data;
+ try {
+ if (isServerDown) {
+ console.log(`[MOCK MODE] Upload ${url}`);
+ return await mockPost(url, data, true);
+ }
+
+ const axiosInstance = await axios.create({
+ 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 };
diff --git a/src/services/mockApi.js b/src/services/mockApi.js
index 38f358f..c2afe2f 100644
--- a/src/services/mockApi.js
+++ b/src/services/mockApi.js
@@ -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 {
- code: 200,
- message: '知识库创建成功',
data: {
- knowledge_base: newKnowledgeBase,
- external_id: uuidv4(),
+ 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
diff --git a/src/store/knowledgeBase/knowledgeBase.slice.js b/src/store/knowledgeBase/knowledgeBase.slice.js
index d37445a..9b7ed7c 100644
--- a/src/store/knowledgeBase/knowledgeBase.slice.js
+++ b/src/store/knowledgeBase/knowledgeBase.slice.js
@@ -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) => {
diff --git a/src/store/knowledgeBase/knowledgeBase.thunks.js b/src/store/knowledgeBase/knowledgeBase.thunks.js
index c48f17a..757ba04 100644
--- a/src/store/knowledgeBase/knowledgeBase.thunks.js
+++ b/src/store/knowledgeBase/knowledgeBase.thunks.js
@@ -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');