UX 缺陷修复: - 校验失败不再卡死弹窗按钮(Users/Roles/Bills) - 押金收取/批量收取/添加分期防重复提交;切换房型重置勾选 - AI 表单/批量确认不再出现"假成功" - Dashboard 各数据模块独立加载,单接口失败不再整页清零 - 房间可视化加载失败显示错误态而非永久转圈 - 学生编辑表单回填前重置,避免字段残留污染 - 覆盖式导入增加二次确认;恢复默认考勤时段确认并同步表单 - 金数据匹配关闭前确认,同步中禁止误关 体验提升: - 新增统一 QueryErrorState/QueryEmpty,20+ 页面加载失败显示错误态与重试 - 全局 ErrorBoundary + RouteKeeper 逐页兜底 - 新增 usePageVisible/useVisibleRefetch,保活页面切回自动刷新数据 - 新增首次登录角色引导 RoleTour 与业务闭环 NextStepHint 引导卡 - 重构 A2UI:useSubmissionState/useXCardSurface 收敛状态与命令生命周期, ArtifactErrorBoundary 渲染降级,图表空数据占位 - AI 助手欢迎语与建议话术按角色定制,会话列表空态引导 - 更新 a2ui-contract.md 契约文档说明实现现状
49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import React, { useRef } from 'react';
|
||
import { useLocation, useOutlet } from 'react-router';
|
||
import AppErrorBoundary from './AppErrorBoundary';
|
||
import { ActivePageContext } from './routeKeeperContext';
|
||
|
||
const MAX_CACHED_PAGES = 30;
|
||
|
||
/**
|
||
* 路由保活:切换页面时保留已访问页面的组件实例(输入、滚动、弹窗状态不丢失)。
|
||
* 隐藏页面仍挂载在 DOM 中,仅通过 display:none 隐藏。
|
||
*
|
||
* - 每个缓存页外层包裹 AppErrorBoundary:单页渲染异常不影响其他缓存页。
|
||
* - 通过 ActivePageContext 向页面暴露「当前激活页路径」,供 usePageVisible 使用。
|
||
*/
|
||
export const RouteKeeper: React.FC = () => {
|
||
const location = useLocation();
|
||
const outlet = useOutlet();
|
||
const cacheRef = useRef<Map<string, React.ReactNode>>(new Map());
|
||
const orderRef = useRef<string[]>([]);
|
||
// 仅以 pathname 作为缓存键:页面内部通过 URL 参数同步状态时不会
|
||
// 产生第二个实例,切回时也不会因此重挂载。
|
||
const pageKey = location.pathname;
|
||
|
||
if (outlet && !cacheRef.current.has(pageKey)) {
|
||
cacheRef.current.set(pageKey, outlet);
|
||
orderRef.current.push(pageKey);
|
||
if (orderRef.current.length > MAX_CACHED_PAGES) {
|
||
const oldest = orderRef.current.shift();
|
||
if (oldest && oldest !== pageKey) cacheRef.current.delete(oldest);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<ActivePageContext.Provider value={pageKey}>
|
||
{Array.from(cacheRef.current.entries()).map(([key, node]) => (
|
||
<div
|
||
key={key}
|
||
className="route-keeper-page"
|
||
style={{ display: key === pageKey ? undefined : 'none' }}
|
||
>
|
||
<AppErrorBoundary>{node}</AppErrorBoundary>
|
||
</div>
|
||
))}
|
||
</ActivePageContext.Provider>
|
||
);
|
||
};
|
||
|
||
export default RouteKeeper;
|