5.5 KiB
实现方案 — 2026-04-16-19-06-18
根因分析
问题 1:路由切换后所有内容丢失
ReportEditor.tsx 的初始化 useEffect 在从 draft 恢复数据时,将 stateRef.current 的同步代码放在了 if (editorRef.current && draft.content.trim().length > 0) 条件块的内部。
这导致当以下任一情况发生时,stateRef.current 不会被同步:
editorRef.current在 useEffect 执行时尚未挂载(组件首次渲染时常见);draft.content为空字符串或仅包含空白(新建报告时常见)。
当 stateRef.current 保持为初始值(空的 reportData、空的 videos、空的 capturedFrames)时,用户离开页面触发的 saveDraftToStorage() 会用这些空值覆盖 localStorage 中的 draft。再次返回 /report-editor 时,系统读取了这个被清空的 draft,导致所有内容全部丢失。
问题 2:自动帧插入 UI 批量刷新
autoCaptureFrames 是一个 async 函数,内部通过 for 循环逐帧处理。循环中每次摘取到新帧后都会调用 setCapturedFrames(accumulatedFrames),但由于 React 18 的自动批处理机制,在异步函数中连续调用的状态更新会被合并,DOM 重渲染被推迟到整个循环结束后才执行一次。因此用户看不到逐帧实时摘取的过程,只有等全部帧处理完后,关键帧列表和 placeholder 中的图片才会一次性批量出现。
修改文件清单
| 文件 | 修改类型 | 说明 |
|---|---|---|
src/pages/ReportEditor.tsx |
修改 | 移动 stateRef 同步位置 + 引入 flushSync |
具体代码变更
变更 1:useEffect — draft 恢复已有报告(约第 123 行区域)
当前代码(有问题的结构):
if (editorRef.current && typeof draft.content === 'string' && draft.content.trim().length > 0) {
editorRef.current.innerHTML = draft.content;
// ...
stateRef.current = { ...stateRef.current, reportData: draft.reportData, ... };
}
修改为:
将 stateRef.current 的赋值提取到 if (editorRef.current && ...) 的外部,紧接在 setCapturedFrames 之后:
if (draft.activeTab) setActiveTab(draft.activeTab);
stateRef.current = {
...stateRef.current,
reportData: draft.reportData,
videos: draft.videos,
capturedFrames: draft.capturedFrames,
loadedTemplateId: draft.loadedTemplateId || ''
};
if (editorRef.current && typeof draft.content === 'string' && draft.content.trim().length > 0) {
editorRef.current.innerHTML = draft.content;
contentRef.current = draft.content;
contentLoadedRef.current = true;
setLoadedTemplateId(draft.loadedTemplateId || '');
setTimeout(() => updatePageHeight(), 0);
}
变更 2:useEffect — found 恢复已有报告(约第 141 行区域)
修改为:
将 stateRef.current 的赋值提取到 if (editorRef.current) 的外部,紧接在 setReportData(found) 之后:
setReportData(found);
stateRef.current = {
...stateRef.current,
reportData: found,
videos: found.videos || [],
capturedFrames: found.capturedFrames || []
};
if (editorRef.current) {
// ... 恢复 editor content ...
}
同时顺手清理重复赋值 contentRef.current = found.content;(第 149-150 行重复)。
变更 3:useEffect — draft 恢复新建报告(约第 184 行区域)
修改为:
与变更 1 类似,将 stateRef.current 的赋值提取到 if (editorRef.current && ...) 的外部:
if (draft.activeTab) setActiveTab(draft.activeTab);
stateRef.current = {
...stateRef.current,
reportData: draft.reportData,
videos: draft.videos,
capturedFrames: draft.capturedFrames,
loadedTemplateId: draft.loadedTemplateId || ''
};
if (editorRef.current && typeof draft.content === 'string' && draft.content.trim().length > 0) {
// ... 恢复 editor content ...
}
变更 4:autoCaptureFrames 引入 flushSync(约第 519-521 行区域)
在文件顶部增加导入:
import { flushSync } from 'react-dom';
在 autoCaptureFrames 的 for 循环中,每次更新 capturedFrames 时使用 flushSync 强制同步渲染:
当前代码:
setCapturedFrames(accumulatedFrames);
stateRef.current = { ...stateRef.current, capturedFrames: accumulatedFrames };
修改为:
flushSync(() => {
setCapturedFrames(accumulatedFrames);
});
stateRef.current = { ...stateRef.current, capturedFrames: accumulatedFrames };
这样每一帧被摘取后都会立即触发 DOM 更新,用户可以在右侧「视频分析」面板中实时看到关键帧逐张出现,同时自动插入逻辑也能按配置延迟后逐张填充 placeholder。
风险点
| 风险 | 级别 | 应对措施 |
|---|---|---|
flushSync 在循环中可能导致轻微渲染卡顿 |
低 | 关键帧数量通常仅 10~20 张,现代浏览器完全可以承受;已在 async 函数中使用,不会阻塞视频 seek 事件 |
移动 stateRef 同步位置可能与其他逻辑产生时序影响 |
低 | 仅将同步从 if 内部移到外部,逻辑语义完全一致,只是确保在所有路径下都被执行 |
found 分支中 contentRef.current 重复赋值 |
极低 | 顺手清理,不影响行为 |
回滚策略
本次修改仅调整代码执行顺序并引入一个 React 标准 API(flushSync),未改变数据结构或接口。如出现异常,可直接 git revert 回滚。
⚠️ 请审核以上方案,确认无误后回复「确认」或提出修改意见,我将进入测试方案编写阶段。