[dev]add mock data

This commit is contained in:
susie-laptop 2025-03-19 20:22:02 -04:00
parent b4a0874a4d
commit 523c474001
12 changed files with 363 additions and 462 deletions

View File

@ -21,6 +21,7 @@ export default function HeaderWithNav() {
const isActive = (path) => { const isActive = (path) => {
return location.pathname.startsWith(path); return location.pathname.startsWith(path);
}; };
console.log('user', user);
// leader admin // leader admin
const hasManagePermission = user && (user.role === 'leader' || user.role === 'admin'); const hasManagePermission = user && (user.role === 'leader' || user.role === 'admin');

View File

@ -4,6 +4,7 @@ import { fetchMessages, sendMessage } from '../../store/chat/chat.messages.thunk
import { resetMessages, resetSendMessageStatus } from '../../store/chat/chat.slice'; import { resetMessages, resetSendMessageStatus } from '../../store/chat/chat.slice';
import { showNotification } from '../../store/notification.slice'; import { showNotification } from '../../store/notification.slice';
import SvgIcon from '../../components/SvgIcon'; import SvgIcon from '../../components/SvgIcon';
import { fetchKnowledgeBases } from '../../store/knowledgeBase/knowledgeBase.thunks';
export default function ChatWindow({ chatId, knowledgeBaseId }) { export default function ChatWindow({ chatId, knowledgeBaseId }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -18,8 +19,9 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
} = useSelector((state) => state.chat.messages); } = useSelector((state) => state.chat.messages);
const { status: sendStatus, error: sendError } = useSelector((state) => state.chat.sendMessage); const { status: sendStatus, error: sendError } = useSelector((state) => state.chat.sendMessage);
const knowledgeBase = useSelector((state) => const knowledgeBase = useSelector((state) =>
state.knowledgeBase.list.items.find((kb) => kb.id === knowledgeBaseId) state.knowledgeBase.list.data?.items?.find((kb) => kb.id === knowledgeBaseId)
); );
const isLoadingKnowledgeBases = useSelector((state) => state.knowledgeBase.list.isLoading);
// //
useEffect(() => { useEffect(() => {
@ -51,6 +53,13 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]); }, [messages]);
// Redux store
useEffect(() => {
if (!knowledgeBase && !isLoadingKnowledgeBases) {
dispatch(fetchKnowledgeBases());
}
}, [dispatch, knowledgeBase, isLoadingKnowledgeBases]);
const handleSendMessage = (e) => { const handleSendMessage = (e) => {
e.preventDefault(); e.preventDefault();

View File

@ -1,87 +1,92 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { showNotification } from '../../store/notification.slice'; import { showNotification } from '../../store/notification.slice';
import { get } from '../../services/api'; import { fetchKnowledgeBases } from '../../store/knowledgeBase/knowledgeBase.thunks';
import SvgIcon from '../../components/SvgIcon'; import SvgIcon from '../../components/SvgIcon';
export default function NewChat() { export default function NewChat() {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [knowledgeBases, setKnowledgeBases] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
// Redux store
const { data, status, error } = useSelector((state) => state.knowledgeBase.list);
const knowledgeBases = data?.items || [];
const isLoading = status === 'loading';
// //
useEffect(() => { useEffect(() => {
const fetchKnowledgeBases = async () => { if (!data?.items?.length && status !== 'loading') {
try { dispatch(fetchKnowledgeBases());
setLoading(true); }
const response = await get('/knowledge-bases/'); }, [dispatch, data, status]);
// can_read //
const readableKnowledgeBases = response.data.items.filter( useEffect(() => {
(kb) => kb.permissions && kb.permissions.can_read === true if (status === 'failed' && error) {
); dispatch(
showNotification({
message: `获取知识库列表失败: ${error.message || error}`,
type: 'danger',
})
);
}
}, [status, error, dispatch]);
setKnowledgeBases(readableKnowledgeBases); // can_read
} catch (error) { const readableKnowledgeBases = knowledgeBases.filter((kb) => kb.permissions && kb.permissions.can_read === true);
console.error('获取知识库列表失败:', error);
dispatch(
showNotification({
message: '获取知识库列表失败,请稍后重试',
type: 'danger',
})
);
} finally {
setLoading(false);
}
};
fetchKnowledgeBases();
}, [dispatch]);
const handleSelectKnowledgeBase = (knowledgeBaseId) => { const handleSelectKnowledgeBase = (knowledgeBaseId) => {
// //
navigate(`/chat/${knowledgeBaseId}`); navigate(`/chat/${knowledgeBaseId}`);
}; };
return ( //
<div className='new-chat container py-4'> if (isLoading) {
<div className='text-center mb-5'> return (
<h2 className='mb-4'>选择知识库开始聊天</h2> <div className='container-fluid px-4 py-5 text-center'>
<div className='spinner-border' role='status'>
<span className='visually-hidden'>加载中...</span>
</div>
</div> </div>
);
}
{loading ? ( return (
<div className='d-flex justify-content-center my-5'> <div className='container-fluid px-4 py-5'>
<div className='spinner-border' role='status'> <h4 className='mb-4'>选择知识库开始聊天</h4>
<span className='visually-hidden'>加载中...</span> <div className='row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4'>
</div> {readableKnowledgeBases.length > 0 ? (
</div> readableKnowledgeBases.map((kb) => (
) : knowledgeBases.length === 0 ? (
<div className='text-center my-5'>
<p className='text-muted'>没有可用的知识库请联系管理员获取权限</p>
</div>
) : (
<div className='row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 justify-content-center'>
{knowledgeBases.map((kb) => (
<div key={kb.id} className='col'> <div key={kb.id} className='col'>
<div <div
className='card h-100 bg-light border-0 cursor-pointer' className='card h-100 shadow-sm border-0 cursor-pointer'
onClick={() => handleSelectKnowledgeBase(kb.id)} onClick={() => handleSelectKnowledgeBase(kb.id)}
style={{ cursor: 'pointer' }}
> >
<div className='card-body py-4'> <div className='card-body'>
<p className='card-title h5'>{kb.name}</p> <h5 className='card-title'>{kb.name}</h5>
<p className='card-text text-muted'>{kb.description}</p> <p className='card-text text-muted'>{kb.desc || kb.description || ''}</p>
<div className='d-flex justify-content-between align-items-center mt-3'> <div className='text-muted small d-flex align-items-center gap-2'>
<small className='text-muted'>文档数: {kb.document_count || 0}</small> <span className='d-flex align-items-center gap-1'>
<SvgIcon className='file' />
{kb.document_count} 文档
</span>
<span className='d-flex align-items-center gap-1'>
<SvgIcon className='clock' />
{new Date(kb.create_time).toLocaleDateString()}
</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
))} ))
</div> ) : (
)} <div className='col-12'>
<div className='alert alert-warning'>暂无可访问的知识库请先申请知识库访问权限</div>
</div>
)}
</div>
</div> </div>
); );
} }

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { showNotification } from '../../../store/notification.slice'; import { showNotification } from '../../../store/notification.slice';
import { fetchKnowledgeBases } from '../../../store/knowledgeBase/knowledgeBase.thunks';
import SvgIcon from '../../../components/SvgIcon'; import SvgIcon from '../../../components/SvgIcon';
import DatasetTab from './DatasetTab'; import DatasetTab from './DatasetTab';
import SettingsTab from './SettingsTab'; import SettingsTab from './SettingsTab';
@ -13,8 +14,16 @@ export default function KnowledgeBaseDetail() {
const [activeTab, setActiveTab] = useState(tab === 'settings' ? 'settings' : 'datasets'); const [activeTab, setActiveTab] = useState(tab === 'settings' ? 'settings' : 'datasets');
// Get knowledge base details from Redux store // Get knowledge base details from Redux store
const { items: knowledgeBases } = useSelector((state) => state.knowledgeBase.list); const { data, status } = useSelector((state) => state.knowledgeBase.list);
const knowledgeBase = knowledgeBases.find((kb) => kb.id === id); const knowledgeBase = data?.items?.find((kb) => kb.id === id);
const isLoading = status === 'loading';
// Fetch knowledge bases if not available
useEffect(() => {
if (!data?.items?.length && status !== 'loading') {
dispatch(fetchKnowledgeBases());
}
}, [dispatch, data, status]);
// Update active tab when URL changes // Update active tab when URL changes
useEffect(() => { useEffect(() => {
@ -25,7 +34,7 @@ export default function KnowledgeBaseDetail() {
// If knowledge base not found in Redux store, show notification and redirect // If knowledge base not found in Redux store, show notification and redirect
useEffect(() => { useEffect(() => {
if (!knowledgeBase && knowledgeBases.length > 0) { if (!knowledgeBase && data?.items?.length > 0 && !isLoading) {
dispatch( dispatch(
showNotification({ showNotification({
message: '未找到知识库,请返回知识库列表', message: '未找到知识库,请返回知识库列表',
@ -34,7 +43,7 @@ export default function KnowledgeBaseDetail() {
); );
navigate('/knowledge-base'); navigate('/knowledge-base');
} }
}, [knowledgeBase, knowledgeBases, dispatch, navigate]); }, [knowledgeBase, data, isLoading, dispatch, navigate]);
// Handle tab change // Handle tab change
const handleTabChange = (tab) => { const handleTabChange = (tab) => {
@ -43,7 +52,7 @@ export default function KnowledgeBaseDetail() {
}; };
// Show loading state if knowledge base not loaded yet // Show loading state if knowledge base not loaded yet
if (!knowledgeBase) { if (isLoading || !knowledgeBase) {
return ( return (
<div className='container-fluid px-4 py-5 text-center'> <div className='container-fluid px-4 py-5 text-center'>
<div className='spinner-border' role='status'> <div className='spinner-border' role='status'>
@ -61,7 +70,7 @@ export default function KnowledgeBaseDetail() {
<div className='py-4'> <div className='py-4'>
<div className='h4 mb-3 text-center'>{knowledgeBase.name}</div> <div className='h4 mb-3 text-center'>{knowledgeBase.name}</div>
<p className='text-center text-muted small mb-4'> <p className='text-center text-muted small mb-4'>
{knowledgeBase.desc || knowledgeBase.description || ''} {knowledgeBase.desc || ''}
</p> </p>
<hr /> <hr />

View File

@ -5,7 +5,7 @@ import KnowledgeCard from './KnowledgeCard';
* 知识库列表组件 * 知识库列表组件
*/ */
const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequestAccess, onDelete }) => { const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequestAccess, onDelete }) => {
if (knowledgeBases.length === 0) { if (!knowledgeBases?.length) {
return ( return (
<div className='alert alert-warning'> <div className='alert alert-warning'>
{isSearching ? '没有找到匹配的知识库' : '暂无知识库,请创建新的知识库'} {isSearching ? '没有找到匹配的知识库' : '暂无知识库,请创建新的知识库'}
@ -19,15 +19,18 @@ const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequest
<React.Fragment key={item.id}> <React.Fragment key={item.id}>
<KnowledgeCard <KnowledgeCard
id={item.id} id={item.id}
title={item.name} title={item.highlighted_name || item.name}
description={item.description || item.desc || ''} description={item.desc || ''}
documents={item.document_count || 0} documents={item.document_count}
date={new Date(item.create_time || item.created_at).toLocaleDateString()} date={new Date(item.create_time).toLocaleDateString()}
permissions={item.permissions} permissions={item.permissions}
access={item.permissions?.can_edit ? 'full' : item.permissions?.can_read ? 'read' : 'none'} access={item.permissions?.can_edit ? 'full' : item.permissions?.can_read ? 'read' : 'none'}
onClick={() => onCardClick(item.id, item.permissions)} onClick={() => onCardClick(item.id, item.permissions)}
onRequestAccess={onRequestAccess} onRequestAccess={onRequestAccess}
onDelete={(e) => onDelete(e, item.id)} onDelete={(e) => onDelete(e, item.id)}
type={item.type}
department={item.department}
group={item.group}
/> />
</React.Fragment> </React.Fragment>
))} ))}

View File

@ -13,6 +13,9 @@ export default function KnowledgeCard({
onClick, onClick,
onRequestAccess, onRequestAccess,
onDelete, onDelete,
type,
department,
group,
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
@ -41,7 +44,7 @@ export default function KnowledgeCard({
return ( return (
<div className='knowledge-card card shadow border-0 p-0 col' onClick={onClick}> <div className='knowledge-card card shadow border-0 p-0 col' onClick={onClick}>
<div className='card-body'> <div className='card-body'>
<h5 className='card-title'>{title}</h5> <h5 className='card-title' dangerouslySetInnerHTML={{ __html: title }} />
{permissions && permissions.can_delete && ( {permissions && permissions.can_delete && (
<div className='hoverdown position-absolute end-0 top-0'> <div className='hoverdown position-absolute end-0 top-0'>
<button type='button' className='detail-btn btn'> <button type='button' className='detail-btn btn'>
@ -66,6 +69,13 @@ export default function KnowledgeCard({
{date} {date}
</span> </span>
</div> </div>
{/* <div className='mt-2 d-flex flex-wrap gap-2'>
<span className='badge bg-secondary-subtle text-secondary'>
{type === 'private' ? '私有' : '公开'}
</span>
{department && <span className='badge bg-info-subtle text-info'>{department}</span>}
{group && <span className='badge bg-primary-subtle text-primary'>{group}</span>}
</div> */}
<div className='mt-3 d-flex justify-content-between align-items-end'> <div className='mt-3 d-flex justify-content-between align-items-end'>
{access === 'full' ? ( {access === 'full' ? (
<span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'> <span className='badge bg-success-subtle text-success d-flex align-items-center gap-1'>

View File

@ -10,136 +10,6 @@ import { resetApproveRejectStatus } from '../../../store/permissions/permissions
import './PendingRequests.css'; // CSS import './PendingRequests.css'; // CSS
import SvgIcon from '../../../components/SvgIcon'; 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; const PAGE_SIZE = 5;
@ -173,31 +43,16 @@ export default function PendingRequests() {
const fetchData = async () => { const fetchData = async () => {
try { try {
setFetchStatus('loading'); setFetchStatus('loading');
// API
const result = await dispatch(fetchPermissionsThunk()); const result = await dispatch(fetchPermissionsThunk());
if (result.payload) {
// API
if (result && result.payload && result.payload.length > 0) {
// 使API
setPendingRequests(result.payload); setPendingRequests(result.payload);
setTotalPages(Math.ceil(result.payload.length / PAGE_SIZE)); 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'); setFetchStatus('succeeded');
} catch (error) { } catch (error) {
console.error('获取待处理申请失败:', error); console.error('获取待处理申请列表失败:', error);
setFetchError('获取待处理申请失败'); setFetchError(error.message || '获取待处理申请列表失败');
setFetchStatus('failed'); setFetchStatus('failed');
// API使
console.log('API请求失败使用模拟数据作为后备');
setPendingRequests(mockPendingRequests);
setTotalPages(Math.ceil(mockPendingRequests.length / PAGE_SIZE));
} }
}; };
@ -318,7 +173,7 @@ export default function PendingRequests() {
const getCurrentPageData = () => { const getCurrentPageData = () => {
const startIndex = (currentPage - 1) * PAGE_SIZE; const startIndex = (currentPage - 1) * PAGE_SIZE;
const endIndex = startIndex + PAGE_SIZE; const endIndex = startIndex + PAGE_SIZE;
return pendingRequests.slice(startIndex, endIndex); return Array.isArray(pendingRequests) ? pendingRequests.slice(startIndex, endIndex) : [];
}; };
// //
@ -401,14 +256,13 @@ export default function PendingRequests() {
<div key={request.id} className='pending-request-item' onClick={() => handleRowClick(request)}> <div key={request.id} className='pending-request-item' onClick={() => handleRowClick(request)}>
<div className='request-header'> <div className='request-header'>
<div className='user-info'> <div className='user-info'>
<h6 className='mb-0'>{request.applicant.name}</h6> <h6 className='mb-0'>{request.applicant}</h6>
<p className='department'>{request.applicant.department}</p>
</div> </div>
<div className='request-date'>{new Date(request.created_at).toLocaleDateString()}</div> <div className='request-date'>{new Date(request.created_at).toLocaleDateString()}</div>
</div> </div>
<div className='request-content'> <div className='request-content'>
<p className='mb-2'>申请访问{request.knowledge_base.name}</p> <p className='mb-2'>申请访问{request.knowledge_base}</p>
{request.permissions.can_edit ? ( {request.permissions.can_edit ? (
<span <span
@ -477,15 +331,10 @@ export default function PendingRequests() {
<h6 className='text-muted mb-2'>申请人信息</h6> <h6 className='text-muted mb-2'>申请人信息</h6>
<div className='d-flex align-items-center mb-3'> <div className='d-flex align-items-center mb-3'>
<div className='avatar-placeholder me-3 bg-dark'> <div className='avatar-placeholder me-3 bg-dark'>
{(selectedRequest.applicant.name || selectedRequest.applicant).charAt(0)} {selectedRequest.applicant.charAt(0)}
</div> </div>
<div> <div>
<h5 className='mb-1'> <h5 className='mb-1'>{selectedRequest.applicant}</h5>
{selectedRequest.applicant.name || selectedRequest.applicant}
</h5>
<p className='text-muted mb-0'>
{selectedRequest.applicant.department || '无归属部门'}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -493,8 +342,7 @@ export default function PendingRequests() {
<div className='mb-4'> <div className='mb-4'>
<h6 className='text-muted mb-2'>知识库信息</h6> <h6 className='text-muted mb-2'>知识库信息</h6>
<p className='mb-1'> <p className='mb-1'>
<strong>名称</strong>{' '} <strong>ID</strong> {selectedRequest.knowledge_base}
{selectedRequest.knowledge_base.name || selectedRequest.knowledge_base}
</p> </p>
</div> </div>

View File

@ -295,235 +295,230 @@ const mockDeleteChat = (id) => {
// 模拟聊天消息数据 // 模拟聊天消息数据
const chatMessages = {}; const chatMessages = {};
// 模拟待处理权限申请 // 权限申请列表的 mock 数据
const mockPendingRequests = [ const mockPendingRequests = [
{ {
id: 1, id: 1,
applicant: { knowledge_base: 'f13c4bdb-eb03-4ce2-b83c-30917351fb72',
name: '王五', applicant: 'f2799611-7a3d-436d-b3fa-3789bdd877e2',
department: '达人组',
},
knowledge_base: {
name: '达人直播数据报告',
},
permissions: { permissions: {
can_edit: false,
can_read: true, can_read: true,
can_edit: true,
can_delete: false, can_delete: false,
}, },
reason: '需要查看和编辑直播数据报告', status: 'pending',
created_at: '2024-01-07T10:30:00Z', reason: '需要访问知识库进行学习',
expires_at: null, response_message: null,
expires_at: '2025-03-19T00:17:43.781000Z',
created_at: '2025-03-12T00:17:44.044351Z',
updated_at: '2025-03-12T00:17:44.044369Z',
}, },
{ {
id: 2, id: 2,
applicant: { knowledge_base: 'f13c4bdb-eb03-4ce2-b83c-30917351fb73',
name: '赵六', applicant: 'f2799611-7a3d-436d-b3fa-3789bdd877e3',
department: '直播组',
},
knowledge_base: {
name: '人力资源政策文件',
},
permissions: { permissions: {
can_edit: true,
can_read: true, can_read: true,
can_edit: false,
can_delete: false, can_delete: false,
}, },
reason: '需要了解最新的人力资源政策', status: 'pending',
created_at: '2024-01-06T14:20:00Z', reason: '需要编辑和更新文档',
expires_at: '2025-01-06T14:20:00Z', response_message: null,
expires_at: '2025-03-20T00:17:43.781000Z',
created_at: '2025-03-12T00:17:44.044351Z',
updated_at: '2025-03-12T00:17:44.044369Z',
}, },
];
// 用户权限列表的 mock 数据
const mockUserPermissions = [
{ {
id: 3, id: 'perm-001',
applicant: { user: {
name: '钱七', id: 'user-001',
department: '市场部', username: 'johndoe',
name: 'John Doe',
email: 'john@example.com',
department: '研发部',
group: '前端开发组',
}, },
knowledge_base: { knowledge_base: {
name: '市场分析报告', id: 'kb-001',
}, name: 'Frontend Development Guide',
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: { permissions: {
can_read: true, can_read: true,
can_edit: true, can_edit: true,
can_delete: true, can_delete: true,
can_manage: true,
},
granted_at: '2024-01-15T10:00:00Z',
granted_by: {
id: 'user-admin',
username: 'admin',
name: 'System Admin',
}, },
reason: '需要进行技术架构更新',
created_at: '2024-01-04T16:45:00Z',
expires_at: null,
}, },
{ {
id: 5, id: 'perm-002',
applicant: { user: {
name: '周九', id: 'user-002',
department: '产品部', username: 'janedoe',
name: 'Jane Doe',
email: 'jane@example.com',
department: '研发部',
group: '前端开发组',
}, },
knowledge_base: { knowledge_base: {
name: '产品规划文档', id: 'kb-001',
name: 'Frontend Development Guide',
}, },
permissions: { permissions: {
can_read: true, can_read: true,
can_edit: true, can_edit: true,
can_delete: false, can_delete: false,
can_manage: false,
},
granted_at: '2024-01-20T14:30:00Z',
granted_by: {
id: 'user-001',
username: 'johndoe',
name: 'John Doe',
}, },
reason: '需要参与产品规划讨论',
created_at: '2024-01-03T11:30:00Z',
expires_at: '2024-12-31T23:59:59Z',
}, },
{ {
id: 6, id: 'perm-003',
applicant: { user: {
name: '吴十', id: 'user-003',
department: '设计部', username: 'alexsmith',
name: 'Alex Smith',
email: 'alex@example.com',
department: '研发部',
group: '后端开发组',
}, },
knowledge_base: { knowledge_base: {
name: '设计规范文档', id: 'kb-001',
name: 'Frontend Development Guide',
}, },
permissions: { permissions: {
can_read: true, can_read: true,
can_edit: false, can_edit: false,
can_delete: false, can_delete: false,
can_manage: false,
}, },
reason: '需要参考设计规范', granted_at: '2024-02-01T09:15:00Z',
created_at: '2024-01-02T14:20:00Z', granted_by: {
expires_at: '2024-06-30T23:59:59Z', id: 'user-001',
}, username: 'johndoe',
{ name: 'John Doe',
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,
}, },
]; ];
// 模拟用户权限详情 // Mock API handlers for permissions
const mockUserPermissions = { const mockPermissionApi = {
'user-001': [ // 获取待处理的权限申请列表
{ getPendingRequests: () => {
knowledge_base: { return {
id: 'kb-001', code: 200,
name: '达人直播数据报告', message: 'success',
department: '达人组', data: {
items: mockPendingRequests,
total: mockPendingRequests.length,
}, },
permission: { };
can_read: true, },
can_edit: true,
can_admin: false, // 获取用户权限列表
getUserPermissions: (knowledgeBaseId) => {
const permissions = mockUserPermissions.filter((perm) => perm.knowledge_base.id === knowledgeBaseId);
return {
code: 200,
message: 'success',
data: {
items: permissions,
total: permissions.length,
}, },
last_access_time: '2024-03-10T14:30:00Z', };
}, },
{
knowledge_base: { // 处理权限申请
id: 'kb-002', handlePermissionRequest: (requestId, action) => {
name: '人力资源政策文件', const request = mockPendingRequests.find((req) => req.id === requestId);
department: '人力资源组', if (!request) {
}, return {
permission: { code: 404,
can_read: true, message: 'Permission request not found',
can_edit: false, };
can_admin: false, }
},
last_access_time: '2024-03-08T09:15:00Z', request.status = action === 'approve' ? 'approved' : 'rejected';
},
{ if (action === 'approve') {
knowledge_base: { // 如果批准,添加新的权限记录
id: 'kb-003', const newPermission = {
name: '市场分析报告', id: `perm-${Date.now()}`,
department: '市场部', user: request.user,
}, knowledge_base: request.knowledge_base,
permission: { permissions: {
can_read: true, can_read: true,
can_edit: false, can_edit: request.request_type === 'edit',
can_admin: false, can_delete: false,
}, can_manage: false,
last_access_time: null, },
}, granted_at: new Date().toISOString(),
], granted_by: mockCurrentUser,
'user-002': [ };
{ mockUserPermissions.push(newPermission);
knowledge_base: { }
id: 'kb-001',
name: '达人直播数据报告', return {
department: '达人组', code: 200,
}, message: 'success',
permission: { data: request,
can_read: true, };
can_edit: false, },
can_admin: false,
}, // 更新用户权限
last_access_time: '2024-03-05T10:20:00Z', updateUserPermission: (permissionId, permissions) => {
}, const permission = mockUserPermissions.find((perm) => perm.id === permissionId);
{ if (!permission) {
knowledge_base: { return {
id: 'kb-004', code: 404,
name: '产品规划文档', message: 'Permission not found',
department: '产品部', };
}, }
permission: {
can_read: true, permission.permissions = {
can_edit: true, ...permission.permissions,
can_admin: true, ...permissions,
}, };
last_access_time: '2024-03-15T11:20:00Z',
}, return {
], code: 200,
'user-003': [ message: 'success',
{ data: permission,
knowledge_base: { };
id: 'kb-003', },
name: '市场分析报告',
department: '市场部', // 删除用户权限
}, deleteUserPermission: (permissionId) => {
permission: { const index = mockUserPermissions.findIndex((perm) => perm.id === permissionId);
can_read: true, if (index === -1) {
can_edit: true, return {
can_admin: false, code: 404,
}, message: 'Permission not found',
last_access_time: '2024-03-12T15:40:00Z', };
}, }
{
knowledge_base: { mockUserPermissions.splice(index, 1);
id: 'kb-005',
name: 'UI/UX设计指南', return {
department: '设计部', code: 200,
}, message: 'success',
permission: { };
can_read: true, },
can_edit: false,
can_admin: false,
},
last_access_time: '2024-03-01T09:10:00Z',
},
],
}; };
// Mock API functions // Mock API functions
@ -536,13 +531,7 @@ export const mockGet = async (url, config = {}) => {
// Get current user // Get current user
if (url === '/users/me/') { if (url === '/users/me/') {
return { return {
data: { user: mockUsers[0], // 默认返回第一个用户
code: 200,
message: 'success',
data: {
user: mockUsers[0], // 默认返回第一个用户
},
},
}; };
} }
@ -670,7 +659,8 @@ export const mockGet = async (url, config = {}) => {
code: 200, code: 200,
message: 'success', message: 'success',
data: { data: {
pending_requests: mockPendingRequests, items: mockPendingRequests,
total: mockPendingRequests.length,
}, },
}, },
}; };
@ -721,22 +711,18 @@ export const mockPost = async (url, data) => {
const token = `mock-jwt-token-${uuidv4()}`; const token = `mock-jwt-token-${uuidv4()}`;
return { return {
code: 200,
message: '登录成功',
data: { data: {
code: 200, token,
message: '登录成功', id: user.id,
data: { username: user.username,
token, email: user.email,
user: { name: user.name,
id: user.id, department: user.department,
username: user.username, group: user.group,
email: user.email, role: user.role,
name: user.name, avatar: user.avatar,
department: user.department,
group: user.group,
role: user.role,
avatar: user.avatar,
},
},
}, },
}; };
} }
@ -1003,3 +989,18 @@ export const mockDelete = async (url) => {
export const resetMockData = () => { export const resetMockData = () => {
knowledgeBases = [...mockKnowledgeBases]; knowledgeBases = [...mockKnowledgeBases];
}; };
// 添加权限相关的 API 处理
export const mockApi = {
// ... existing api handlers ...
// 权限管理相关的 API
'GET /api/permissions/pending': () => mockPermissionApi.getPendingRequests(),
'GET /api/permissions/users/:knowledgeBaseId': (params) =>
mockPermissionApi.getUserPermissions(params.knowledgeBaseId),
'POST /api/permissions/handle/:requestId': (params, body) =>
mockPermissionApi.handlePermissionRequest(params.requestId, body.action),
'PUT /api/permissions/:permissionId': (params, body) =>
mockPermissionApi.updateUserPermission(params.permissionId, body.permissions),
'DELETE /api/permissions/:permissionId': (params) => mockPermissionApi.deleteUserPermission(params.permissionId),
};

View File

@ -11,6 +11,8 @@ export const loginThunk = createAsyncThunk(
async ({ username, password }, { rejectWithValue, dispatch }) => { async ({ username, password }, { rejectWithValue, dispatch }) => {
try { try {
const { message, data } = await post('/auth/login/', { username, password }); const { message, data } = await post('/auth/login/', { username, password });
console.log('data', data);
if (!data) { if (!data) {
throw new Error(message || 'Something went wrong'); throw new Error(message || 'Something went wrong');
} }

View File

@ -11,20 +11,24 @@ import {
const initialState = { const initialState = {
// List state // List state
list: { list: {
items: [], data: {
total: 0, items: [],
page: 1, total: 0,
page_size: 10, page: 1,
page_size: 10,
},
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null, error: null,
}, },
// Search state // Search state
search: { search: {
items: [], data: {
total: 0, items: [],
page: 1, total: 0,
page_size: 10, page: 1,
keyword: '', page_size: 10,
keyword: '',
},
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null, error: null,
}, },
@ -62,11 +66,13 @@ const knowledgeBaseSlice = createSlice({
}, },
resetSearchState: (state) => { resetSearchState: (state) => {
state.search = { state.search = {
items: [], data: {
total: 0, items: [],
page: 1, total: 0,
page_size: 10, page: 1,
keyword: '', page_size: 10,
keyword: '',
},
status: 'idle', status: 'idle',
error: null, error: null,
}; };
@ -93,15 +99,12 @@ const knowledgeBaseSlice = createSlice({
state.search.status = 'loading'; state.search.status = 'loading';
// Store the keyword for reference // Store the keyword for reference
if (action.meta.arg.keyword) { if (action.meta.arg.keyword) {
state.search.keyword = action.meta.arg.keyword; state.search.data.keyword = action.meta.arg.keyword;
} }
}) })
.addCase(searchKnowledgeBases.fulfilled, (state, action) => { .addCase(searchKnowledgeBases.fulfilled, (state, action) => {
state.search.status = 'succeeded'; state.search.status = 'succeeded';
state.search.items = action.payload.items; state.search.data = action.payload;
state.search.total = action.payload.total;
state.search.page = action.payload.page;
state.search.page_size = action.payload.page_size;
state.search.error = null; state.search.error = null;
}) })
.addCase(searchKnowledgeBases.rejected, (state, action) => { .addCase(searchKnowledgeBases.rejected, (state, action) => {
@ -146,14 +149,14 @@ const knowledgeBaseSlice = createSlice({
.addCase(updateKnowledgeBase.fulfilled, (state, action) => { .addCase(updateKnowledgeBase.fulfilled, (state, action) => {
state.operations.status = 'succeeded'; state.operations.status = 'succeeded';
// Update in list if present // Update in list if present
const index = state.list.items.findIndex((item) => item.id === action.payload.id); const index = state.list.data.items.findIndex((item) => item.id === action.payload.id);
if (index !== -1) { if (index !== -1) {
state.list.items[index] = action.payload; state.list.data.items[index] = action.payload;
} }
// Update in search results if present // Update in search results if present
const searchIndex = state.search.items.findIndex((item) => item.id === action.payload.id); const searchIndex = state.search.data.items.findIndex((item) => item.id === action.payload.id);
if (searchIndex !== -1) { if (searchIndex !== -1) {
state.search.items[searchIndex] = action.payload; state.search.data.items[searchIndex] = action.payload;
} }
// Update current if it's the same knowledge base // Update current if it's the same knowledge base
if (state.current.data && state.current.data.id === action.payload.id) { if (state.current.data && state.current.data.id === action.payload.id) {
@ -174,9 +177,9 @@ const knowledgeBaseSlice = createSlice({
.addCase(deleteKnowledgeBase.fulfilled, (state, action) => { .addCase(deleteKnowledgeBase.fulfilled, (state, action) => {
state.operations.status = 'succeeded'; state.operations.status = 'succeeded';
// Remove from list if present // Remove from list if present
state.list.items = state.list.items.filter((item) => item.id !== action.payload); state.list.data.items = state.list.data.items.filter((item) => item.id !== action.payload);
// Remove from search results if present // Remove from search results if present
state.search.items = state.search.items.filter((item) => item.id !== action.payload); state.search.data.items = state.search.data.items.filter((item) => item.id !== action.payload);
// Reset current if it's the same knowledge base // Reset current if it's the same knowledge base
if (state.current.data && state.current.data.id === action.payload) { if (state.current.data && state.current.data.id === action.payload) {
state.current.data = null; state.current.data = null;

View File

@ -35,10 +35,13 @@ export const searchKnowledgeBases = createAsyncThunk(
async ({ keyword, page = 1, page_size = 10 }, { rejectWithValue }) => { async ({ keyword, page = 1, page_size = 10 }, { rejectWithValue }) => {
try { try {
const response = await get('/knowledge-bases/search/', { const response = await get('/knowledge-bases/search/', {
keyword, params: { keyword, page, page_size },
page,
page_size,
}); });
// 处理新的返回格式
if (response.data && response.data.code === 200) {
return response.data.data;
}
return response.data; return response.data;
} catch (error) { } catch (error) {
return rejectWithValue(error.response?.data || 'Failed to search knowledge bases'); return rejectWithValue(error.response?.data || 'Failed to search knowledge bases');
@ -75,6 +78,10 @@ export const getKnowledgeBaseById = createAsyncThunk(
async (id, { rejectWithValue }) => { async (id, { rejectWithValue }) => {
try { try {
const response = await get(`/knowledge-bases/${id}/`); const response = await get(`/knowledge-bases/${id}/`);
// 处理新的返回格式
if (response.data && response.data.code === 200) {
return response.data.data.knowledge_base;
}
return response.data; return response.data;
} catch (error) { } catch (error) {
return rejectWithValue(error.response?.data || 'Failed to get knowledge base details'); return rejectWithValue(error.response?.data || 'Failed to get knowledge base details');

View File

@ -6,8 +6,11 @@ export const fetchPermissionsThunk = createAsyncThunk(
'permissions/fetchPermissions', 'permissions/fetchPermissions',
async (_, { rejectWithValue }) => { async (_, { rejectWithValue }) => {
try { try {
const response = await get('/permissions/'); const response = await get('/permissions/pending/');
return response || []; if (response?.data?.code === 200) {
return response.data.data.items || [];
}
return rejectWithValue('获取权限申请列表失败');
} catch (error) { } catch (error) {
console.error('获取权限申请列表失败:', error); console.error('获取权限申请列表失败:', error);
return rejectWithValue('获取权限申请列表失败'); return rejectWithValue('获取权限申请列表失败');