backup before modification at 2026-04-16-17-07-04

This commit is contained in:
2026-04-16 17:07:04 +08:00
parent dcc35870bf
commit 90c9c7f2b0
5 changed files with 215 additions and 5 deletions

View File

@@ -88,11 +88,12 @@ export default function ReportEditor() {
if (key) {
storage.set(key, {
content: contentRef.current,
loadedTemplateId,
draftReportId: reportId || null,
...stateRef.current
});
}
}, [reportId]);
}, [reportId, loadedTemplateId]);
useEffect(() => {
const user = storage.get<User | null>('currentUser', null);
@@ -122,10 +123,11 @@ export default function ReportEditor() {
setCapturedFrames(draft.capturedFrames.sort((a: CapturedFrame, b: CapturedFrame) => a.time - b.time));
}
if (draft.activeTab) setActiveTab(draft.activeTab);
if (editorRef.current && typeof draft.content === 'string') {
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);
}
} else {
@@ -169,10 +171,11 @@ export default function ReportEditor() {
setCapturedFrames(draft.capturedFrames.sort((a: CapturedFrame, b: CapturedFrame) => a.time - b.time));
}
if (draft.activeTab) setActiveTab(draft.activeTab);
if (editorRef.current && typeof draft.content === 'string') {
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);
}
}
@@ -616,10 +619,11 @@ export default function ReportEditor() {
const draft = key ? storage.get<Record<string, any> | null>(key, null) : null;
if (reportId) {
if (draft && draft.draftReportId === reportId && typeof draft.content === 'string') {
if (draft && draft.draftReportId === reportId && 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);
return;
}
@@ -638,10 +642,11 @@ export default function ReportEditor() {
return;
}
} else {
if (draft && !draft.draftReportId && typeof draft.content === 'string') {
if (draft && !draft.draftReportId && 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);
return;
}

View File

@@ -0,0 +1,69 @@
# 实现方案 — 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`,不触及路由、存储封装或其他页面逻辑。
- 不引入新依赖。

View File

@@ -0,0 +1,76 @@
# 测试方案 — 2026-04-16-16-51-00
## 测试环境准备
1. 确保项目依赖已安装:`npm install` 已完成。
2. 使用测试账号 `admin / 123456`(超级管理员)登录系统。
3.**系统设置** 页面中,确认 **图文报告生成默认模板** 已设置为 **"腹腔镜胆囊切除术报告"**。
---
## 测试项清单
### 测试项 1新建报告时正确加载默认模板
**测试步骤**
1. 登录后,点击左侧菜单 **"图文报告生成"**`/report-editor`,无 `id` 参数)。
2. 观察页面顶部 **"当前模板(及重置模板):"** 下拉框显示内容。
3. 观察中间编辑区域(`editor-content-wrapper print-wrapper`)是否有模板内容。
**预期结果**
- 顶部模板选择器显示 **"腹腔镜胆囊切除术报告"**(或用户设置的默认模板名称),而非"无"。
- 编辑区域显示该模板的完整 HTML 内容(包含标题、表格、图片占位符等),不是纯白色空白。
---
### 测试项 2从其他页面返回后未编辑不显示空白
**测试步骤**
1.**工作台**`/dashboard`)页面停留。
2. 通过左侧菜单再次进入 **"图文报告生成"**。
3. 不要输入任何内容,直接再切回 **工作台**,然后再切回 **"图文报告生成"**。
**预期结果**
- 每次进入 `/report-editor`,编辑区域都应正确显示默认模板内容。
- 不会出现白色空白页面。
---
### 测试项 3编辑已有报告时不被空白草稿覆盖
**测试步骤**
1. 进入 **报告管理**打开一份已有内容的报告进行编辑URL 带有 `?id=xxx`)。
2. 不要做任何修改,直接刷新浏览器页面。
**预期结果**
- 页面加载后显示该报告原有的完整内容,不会被空白草稿覆盖为默认模板。
---
### 测试项 4有效草稿恢复后模板选择器显示正确
**测试步骤**
1. 进入 **"图文报告生成"**,确认已加载默认模板。
2. 在编辑器中随意输入几个字(确保内容非空)。
3. 切到 **工作台**,再切回 **"图文报告生成"**。
**预期结果**
- 编辑器恢复刚才输入的内容。
- 顶部模板选择器仍显示 **"腹腔镜胆囊切除术报告"**(因为草稿中已保存 `loadedTemplateId`)。
---
### 测试项 5构建与类型检查回归
**测试步骤**
1. 在项目根目录执行:
```bash
npm run lint
npm run build
```
**预期结果**
- `npm run lint` 无 TypeScript 编译错误。
- `npm run build` 构建成功,生成 `dist/` 目录。
---
## 回归验证范围
- [ ] `SystemSettings.tsx` 未被修改,默认模板设置功能保持正常。
- [ ] `storage.ts` 未被修改localStorage 读写保持正常。
- [ ] 报告保存(草稿/完成)功能未被破坏。
- [ ] 视频分析面板与编辑器交互保持正常。

View File

@@ -0,0 +1,24 @@
# 经验记录
---
## 记录 1report-editor 新建报告时显示空白模板
**A. 具体问题**
超级管理员进入 `/report-editor`(新建报告)时,编辑区域为纯白色空白,顶部模板选择器显示"无",但 system-settings 中已配置了默认模板。
**B. 产生问题原因**
1. `ReportEditor.tsx` 在组件卸载如页面切换时会自动将当前编辑器内容保存为草稿draft。即使用户未输入任何内容保存的 `content` 也是空字符串 `""`
2. 初始化 effect 中判断草稿是否有效的条件仅使用了 `typeof draft.content === 'string'`,空字符串满足该条件,导致编辑器被填充为空白 HTML并将 `contentLoadedRef.current` 设为 `true`
3. 由于 `contentLoadedRef.current` 已被置为 `true`,后续加载 `settings.defaultTemplate` 的默认模板分支被完全跳过,从而永远显示空白。
4. 此外,草稿中未保存 `loadedTemplateId`,即使内容非空时恢复草稿,模板选择器也会因缺少状态而显示"无"。
**C. 解决问题方案**
1.`saveDraftToStorage` 中将当前 `loadedTemplateId` 一并存入 draft。
2. 将四处草稿恢复的判断条件从 `typeof draft.content === 'string'` 收紧为 `typeof draft.content === 'string' && draft.content.trim().length > 0`,使空白草稿不再拦截默认模板加载。
3. 恢复草稿时同步执行 `setLoadedTemplateId(draft.loadedTemplateId || '')`,确保模板选择器名称正确。
**D. 后续如何避免问题**
- 在前端使用 contentEditable 的自动保存机制时,保存和恢复草稿都应增加对空/仅空白内容的过滤。
- 若草稿与某个业务状态(如当前模板 ID强关联应确保两者一并持久化和恢复避免状态不一致。
- 对兜底初始化逻辑(如默认模板加载)增加更严格的防护,防止被无效中间状态提前截断。

View File

@@ -0,0 +1,36 @@
# 需求分析 — 2026-04-16-16-51-00
## 需求背景
用户在进入 **report-editor**(图文报告生成)页面时,期望编辑器能够自动加载在 **system-settings** 中配置的"图文报告生成默认模板"。但目前存在以下异常现象:
1. 超级管理员进入 report-editor 时,顶部模板选择器显示 **"无"**,编辑区域(`editor-content-wrapper print-wrapper`)为纯白色空白,没有加载任何模板内容。
2. 在 system-settings 中已正确设置默认模板为 **"腹腔镜胆囊切除术报告"**,但 report-editor 未按预期加载。
3. 用户从 `/dashboard` 等其他页面返回 `/report-editor` 后,若之前未进行过有效编辑,也可能出现白色空白模板的情况。
## 功能目标
修复 report-editor 页面初始化时的模板加载逻辑,确保:
- 新建报告(无 `reportId`)时,优先加载系统设置中的默认模板。
- 只有当用户确实在编辑器中有过有效编辑内容时才从本地草稿draft恢复。
- 模板选择器中的"当前模板(及重置模板):"应正确反映当前加载的模板名称,而不是显示"无"。
- 从其他页面返回或重新进入 report-editor 时,未编辑状态下不应出现空白模板。
## 涉及页面/模块
- `src/pages/ReportEditor.tsx` —— 核心问题所在,草稿恢复与默认模板加载逻辑
- `src/pages/SystemSettings.tsx` —— 默认模板设置页面(验证配置读取逻辑)
- `src/utils/storage.ts` —— localStorage 读写封装(辅助确认)
## 问题根因(预分析)
通过代码审阅,发现以下导致空白的根因:
1. **空字符串草稿被当作有效内容加载**`saveDraftToStorage` 在组件卸载时自动保存草稿。如果用户未在编辑器中输入任何内容,保存的 `content` 会是空字符串 `""`。在初始化 effect 中,判断条件 `typeof draft.content === 'string'` 对空字符串也返回 `true`,导致编辑器被填充为空白 HTML跳过了后续默认模板加载逻辑。
2. **草稿中未记录模板 ID**`loadedTemplateId` 没有被存入 draft。当从 draft 恢复时,即使内容非空,模板选择器也因缺少 `loadedTemplateId` 而显示"无"。
3. **默认模板加载逻辑被空白草稿截断**:由于空草稿提前将 `contentLoadedRef.current` 设为 `true`,真正的默认模板分支 (`settings.defaultTemplate`) 永远不会执行。
## 验收标准
- [ ] 超级管理员进入 report-editor 时,编辑区域正确显示 system-settings 中设置的默认模板内容。
- [ ] 模板选择器显示当前已加载的默认模板名称,而非"无"。
- [ ] 从 dashboard 等其他页面返回 report-editor未编辑情况下不显示空白模板。
- [ ] `npm run lint` 类型检查零错误。
- [ ] `npm run build` 构建通过。