# 实现方案 — 2026-04-16-16-51-00 ## 技术思路 本次修复聚焦于 `ReportEditor.tsx` 中草稿(draft)恢复与默认模板加载的竞争条件问题。核心思路是: 1. **拒绝加载空白草稿**:将空字符串(或仅空白字符)的 draft 视为无效,不拦截默认模板加载流程。 2. **草稿中携带模板信息**:在自动保存 draft 时追加 `loadedTemplateId`,恢复草稿时同步还原,确保模板选择器显示正确。 3. **统一初始化路径**:`useEffect` 与 `useLayoutEffect` 中的草稿/默认模板判断逻辑保持一致,避免其中一个抢先设置空白内容。 --- ## 修改文件清单 | 文件 | 变更类型 | 说明 | |------|----------|------| | `src/pages/ReportEditor.tsx` | 修改 | 修复草稿恢复条件、保存/恢复 `loadedTemplateId`、统一空白判断逻辑 | --- ## 关键代码变更说明 ### 1. 保存 draft 时追加 `loadedTemplateId` 在 `saveDraftToStorage` 回调中,将当前 `loadedTemplateId` 一并持久化: ```tsx storage.set(key, { content: contentRef.current, loadedTemplateId, // 新增 draftReportId: reportId || null, ...stateRef.current }); ``` ### 2. 恢复 draft 时同步恢复 `loadedTemplateId`(useEffect 初始化逻辑) **新建报告分支(无 `reportId`)** - 将条件 `typeof draft.content === 'string'` 改为 `typeof draft.content === 'string' && draft.content.trim().length > 0`。 - 若草稿有效,除了回填内容,还要执行 `setLoadedTemplateId(draft.loadedTemplateId || '')`。 **编辑报告分支(有 `reportId`)** - 同样使用 `draft.content.trim().length > 0` 判断,避免空白草稿覆盖已有报告内容。 - 恢复 `loadedTemplateId`(虽然在编辑模式下模板选择器通常不显示具体模板名,但保持一致性)。 ### 3. useLayoutEffect 安全网逻辑同步修复 `useLayoutEffect`(第 611 行起)作为 editor ref 就绪后的二次安全网,其草稿判断条件也要同步修改: - `typeof draft.content === 'string'` → `typeof draft.content === 'string' && draft.content.trim().length > 0` - 恢复 `loadedTemplateId` ### 4. 默认模板加载时同步设置 `loadedTemplateId` 在加载 `settings.defaultTemplate` 对应模板内容时,当前代码已经设置了 `setLoadedTemplateId(tpl.id)`,无需改动。 在兜底使用 `defaultReportContent` 时,`loadedTemplateId` 保持为空字符串(显示"无"),这符合语义,因为兜底内容不是用户选中的模板。 --- ## 风险点及应对策略 | 风险 | 影响 | 应对策略 | |------|------|----------| | 修改了多处 draft 判断逻辑,可能漏改 | 某些路由切换场景仍出现空白 | 在 `useEffect` 和 `useLayoutEffect` 两处共 **4 个草稿加载点** 统一替换判断条件 | | `loadedTemplateId` 加入 draft 后,旧 draft 兼容性 | 旧 draft 没有该字段,恢复时值为 `undefined`,模板选择器短暂显示"无" | 使用 `draft.loadedTemplateId || ''` 兜底,不影响功能 | | 用户之前确实清空了内容再离开 | 会被视为无效草稿而丢失空白状态 | 这是预期行为:空白内容等价于未开始编辑,应回退到默认模板 | --- ## 改动范围总结 - 仅修改 `src/pages/ReportEditor.tsx`,不触及路由、存储封装或其他页面逻辑。 - 不引入新依赖。