Files
Mdeical_Sur_Report/工程分析/实现方案-2026-04-16-19-06-18.md

5.5 KiB
Raw Blame History

实现方案 — 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

具体代码变更

变更 1useEffect — 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);
}

变更 2useEffect — 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 行重复)。

变更 3useEffect — 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 ...
}

变更 4autoCaptureFrames 引入 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 标准 APIflushSync),未改变数据结构或接口。如出现异常,可直接 git revert 回滚。


⚠️ 请审核以上方案,确认无误后回复「确认」或提出修改意见,我将进入测试方案编写阶段。