backup before modification at 2026-04-16-17-07-04
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
69
工程分析/实现方案-2026-04-16-16-51-00.md
Normal file
69
工程分析/实现方案-2026-04-16-16-51-00.md
Normal 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`,不触及路由、存储封装或其他页面逻辑。
|
||||
- 不引入新依赖。
|
||||
76
工程分析/测试方案-2026-04-16-16-51-00.md
Normal file
76
工程分析/测试方案-2026-04-16-16-51-00.md
Normal 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 读写保持正常。
|
||||
- [ ] 报告保存(草稿/完成)功能未被破坏。
|
||||
- [ ] 视频分析面板与编辑器交互保持正常。
|
||||
24
工程分析/经验记录.md
Normal file
24
工程分析/经验记录.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 经验记录
|
||||
|
||||
---
|
||||
|
||||
## 记录 1:report-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)强关联,应确保两者一并持久化和恢复,避免状态不一致。
|
||||
- 对兜底初始化逻辑(如默认模板加载)增加更严格的防护,防止被无效中间状态提前截断。
|
||||
36
工程分析/需求分析-2026-04-16-16-51-00.md
Normal file
36
工程分析/需求分析-2026-04-16-16-51-00.md
Normal 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` 构建通过。
|
||||
Reference in New Issue
Block a user