Files
Mdeical_Sur_Report/过往经验/实现方案-2026-04-16-20-24-11.md

117 lines
4.8 KiB
Markdown
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.
# 实现方案 — 2026-04-16-20-24-11
## 根因分析
当前问题由两个核心缺陷共同导致:
### 1. `saveDraftToStorage` 的闭包陷阱 + DOM 引用失效
此前为修复 `stateRef` 不同步的问题,将 `saveDraftToStorage` 重构为直接从 React state 读取:
```tsx
const saveDraftToStorage = React.useCallback(() => {
storage.set(key, {
content: editorRef.current?.innerHTML || '',
reportData,
videos,
capturedFrames,
...
});
}, [reportData, videos, capturedFrames, ...]);
```
这引入了新的问题:
- **闭包陷阱**:用户操作后常见写法是 `setCapturedFrames(nextFrames); saveDraftToStorage();`。由于 `setState` 异步,`saveDraftToStorage` 闭包中读取到的 `capturedFrames` 仍是旧值(空数组),导致旧值覆盖 localStorage。
- **卸载时 DOM 失效**:组件卸载时 React 开始销毁 DOM`editorRef.current` 可能已经变为 `null` 或其 `innerHTML` 已为空,导致 `content: editorRef.current?.innerHTML || ''` 保存了空字符串,覆盖了已有的报告内容。
### 2. `contentRef` 更新遗漏
代码中部分修改编辑器 DOM 的路径没有同步更新 `contentRef.current`。例如 `handleEditorClick` 中通过 `document.execCommand('delete')` 删除 placeholder 后,直接调用了 `saveDraftToStorage()`,但没有先更新 `contentRef`
## 修改文件清单
| 文件 | 修改类型 | 说明 |
|------|---------|------|
| `src/pages/ReportEditor.tsx` | 修改 | 重构 `saveDraftToStorage` + 补齐 `contentRef` 遗漏点 |
## 具体代码变更
### 变更 1重构 `saveDraftToStorage` 从 Ref 读取
**修改为:**
```tsx
const saveDraftToStorage = React.useCallback(() => {
const user = storage.get<User | null>('currentUser', null);
const key = user ? `reportEditorDraft_${user.username}` : '';
if (key) {
const currentContent = contentRef.current || editorRef.current?.innerHTML || '';
storage.set(key, {
content: currentContent,
draftReportId: reportId || null,
reportData: stateRef.current.reportData,
videos: stateRef.current.videos,
capturedFrames: stateRef.current.capturedFrames,
activeTab: stateRef.current.activeTab,
loadedTemplateId: stateRef.current.loadedTemplateId
});
}
}, [reportId]);
```
**效果**
- 数据源回归 `stateRef``contentRef`,彻底摆脱 React state 的闭包陷阱。
- `content` 优先读取 `contentRef.current`(在内存中稳定存在),回退到 `editorRef.current?.innerHTML`(兼容某些未更新 contentRef 的旧路径),兜底为空字符串。即使组件卸载时 DOM 已被销毁,`contentRef.current` 仍保存着最新的编辑器 HTML。
### 变更 2补齐 `contentRef` 更新遗漏
`handleEditorClick``document.execCommand('delete')` 分支中,增加 `contentRef.current` 的同步:
**当前代码(约第 296-304 行):**
```tsx
} else {
const range = document.createRange();
range.selectNode(placeholder);
const sel = window.getSelection();
sel?.removeAllRanges();
sel?.addRange(range);
document.execCommand('delete');
saveDraftToStorage();
}
```
**修改为:**
```tsx
} else {
const range = document.createRange();
range.selectNode(placeholder);
const sel = window.getSelection();
sel?.removeAllRanges();
sel?.addRange(range);
document.execCommand('delete');
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
}
```
### 变更 3确保自动保存 effect 绑定最新 `saveDraftToStorage`
当前自动保存 effect 已经绑定 `[saveDraftToStorage]`,由于 `saveDraftToStorage` 的 dependency 现在只有 `[reportId]`effect 不会因为 state 变化而频繁重新注册,但 cleanup 中仍然指向最新的保存函数。
### 变更 4初始化恢复时 `contentRef` 同步(已有,确认无误)
`useEffect``useLayoutEffect` 的各恢复分支中,设置 `editorRef.current.innerHTML = draft.content`(或 `found.content`)时,代码已经同步设置了 `contentRef.current = ...`,这部分无需修改。
## 风险点
| 风险 | 级别 | 应对措施 |
|------|------|---------|
| 仍有未被发现的 `contentRef` 更新遗漏点 | 低 | 已全面搜索 `innerHTML` 修改点和 `saveDraftToStorage` 调用点,仅发现 1 处遗漏 |
| `stateRef` 在某些新功能中再次不同步 | 低 | `saveDraftToStorage``stateRef` 读取后续新增功能只要保持「setState 后立即同步 stateRef」的习惯即可 |
## 回滚策略
本次修改仅调整 `saveDraftToStorage` 的数据源和补齐一处 `contentRef` 更新,不改变数据结构和接口。如出现异常,可直接 `git revert` 回滚。
---
**⚠️ 请审核以上方案,确认无误后回复「确认」或提出修改意见,我将进入测试方案编写阶段。**