Files
ChatOA/client/src/layout/KeepAliveOutlet.tsx
T

105 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
// /ai-chat(无 id)时兜底默认助手 'assistant',与原 Navigate 重定向行为一致
const assistantId =
activeKey === 'ai-chat' ? location.pathname.split('/')[2] || 'assistant' : 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>
);
})}
</>
);
}