feat: 初始化 ChatOA 企业协作 OA 系统

This commit is contained in:
2026-08-20 19:36:07 +08:00
commit 6d7a1f6e63
90 changed files with 20067 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
import { useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { useLocation } from 'react-router-dom';
import ChatPage from '../pages/Chat';
import ContactsPage from '../pages/Contacts';
import SchedulePage from '../pages/Schedule';
import FormsPage from '../pages/Forms';
import KnowledgePage from '../pages/Knowledge';
import AppsPage from '../pages/Apps';
import DrivePage from '../pages/Drive';
import AiChatPage from '../pages/AiChat';
type PageKey =
| 'chat'
| 'contacts'
| 'schedule'
| 'forms'
| 'knowledge'
| 'apps'
| 'drive'
| 'ai-chat';
function resolveKey(pathname: string): PageKey {
if (pathname.startsWith('/chat')) return 'chat';
if (pathname.startsWith('/contacts')) return 'contacts';
if (pathname.startsWith('/schedule')) return 'schedule';
if (pathname.startsWith('/forms')) return 'forms';
if (pathname.startsWith('/knowledge')) return 'knowledge';
if (pathname.startsWith('/apps')) return 'apps';
if (pathname.startsWith('/drive')) return 'drive';
if (pathname.startsWith('/ai-chat')) return 'ai-chat';
return 'chat';
}
/**
* Keep-Alive 出口:1-7 模块页面(聊天/联系人/日程/表单/笔记/应用/网盘 + AI 对话)
* 切换时保持组件挂载,非当前页面用 display:none 隐藏,避免路由切换导致卸载、编辑内容丢失。
* 懒挂载:首次访问某页面时才创建实例,之后一直保留;滚动位置、输入内容等状态均不丢失。
*/
export default function KeepAliveOutlet() {
const location = useLocation();
const activeKey = resolveKey(location.pathname);
const [mountedKeys, setMountedKeys] = useState<PageKey[]>(() => [activeKey]);
// 懒挂载:路由变化时把当前页加入已挂载列表。
// 注意:不能在 render 期间直接 setStateReact 18 并发渲染会丢弃 render-phase update
// 导致从 /chat 切换其他页面时停留在旧渲染、内容空白),必须通过 effect 更新。
useEffect(() => {
setMountedKeys((prev) => (prev.includes(activeKey) ? prev : [...prev, activeKey]));
}, [activeKey]);
// 渲染时始终包含当前页(首次切换时立即显示,effect 提交后再持久化)
const renderKeys = mountedKeys.includes(activeKey) ? mountedKeys : [...mountedKeys, activeKey];
// AI 对话页的助手 id 从路径解析(常驻后不再依赖 Route 的 useParams
const assistantId = activeKey === 'ai-chat' ? location.pathname.split('/')[2] : undefined;
const lastAssistantIdRef = useRef<string | undefined>(undefined);
if (assistantId) lastAssistantIdRef.current = assistantId;
return (
<>
{renderKeys.map((key) => {
const active = key === activeKey;
let page: ReactNode;
switch (key) {
case 'chat':
page = <ChatPage />;
break;
case 'contacts':
page = <ContactsPage />;
break;
case 'schedule':
page = <SchedulePage />;
break;
case 'forms':
page = <FormsPage />;
break;
case 'knowledge':
page = <KnowledgePage />;
break;
case 'apps':
page = <AppsPage />;
break;
case 'drive':
page = <DrivePage />;
break;
default:
page = <AiChatPage assistantId={assistantId ?? lastAssistantIdRef.current} />;
}
return (
<div
key={key}
style={{ flex: 1, minWidth: 0, height: '100%', display: active ? 'flex' : 'none' }}
>
{page}
</div>
);
})}
</>
);
}