fix(editor): contentEditable回车导致段落溢出.ai-content

- handleAIGenerate中获取currentHtml前增加溢出段落合并逻辑
- 遍历.ai-content之后的兄弟<p>节点,移回.ai-content内
- 合并后同步更新contentRef和saveDraftToStorage
- 确保diff弹窗左侧能显示AI可编辑区域内的全部段落
This commit is contained in:
2026-04-19 18:10:40 +08:00
parent a3cafcb672
commit 6abd7d1e3a
5 changed files with 191 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
# 实现方案
## 修改文件
- `src/pages/ReportEditor.tsx`
## 修改位置:`handleAIGenerate` 函数内,获取 `currentHtml` 之前(约 line 885
**原代码**
```tsx
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"] .ai-content`) as HTMLElement | null;
const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/&#8203;/g, '').trim() : '';
```
**新代码**
```tsx
const targetRegionEl = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"] .ai-content`) as HTMLElement | null;
// 合并溢出的段落:浏览器 contentEditable 可能在回车时把 <p> 生成到 .ai-content 之外
const aiRegion = editorRef.current?.querySelector(`.ai-region[data-ai-id="${actualTargetId}"]`);
if (aiRegion && targetRegionEl) {
let nextSibling = targetRegionEl.nextElementSibling;
while (nextSibling) {
const toMove = nextSibling;
nextSibling = nextSibling.nextElementSibling;
if (toMove.tagName === 'P') {
targetRegionEl.appendChild(toMove);
}
}
// 同步更新 contentRef 和草稿
if (editorRef.current) {
contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
}
}
const currentHtml = targetRegionEl ? targetRegionEl.innerHTML.replace(/&#8203;/g, '').trim() : '';
```
**变更点**
1. 新增 `aiRegion` 查询,获取 `.ai-region` 容器
2. 遍历 `targetRegionEl.nextElementSibling`,把所有 `<p>` 标签移回 `targetRegionEl`
3. 移动完成后同步更新 `contentRef.current` 和调用 `saveDraftToStorage()`
4. 然后才获取 `currentHtml`
**为什么这样可行**
- `confirmAiInjection` 只替换 `.ai-content` 的 innerHTML
- 修复后 `.ai-content` 已包含所有段落,注入时自然替换全部
- 溢出的段落不会再留在 `.ai-region` 内造成重复

View File

@@ -0,0 +1,45 @@
# 测试方案
## 测试环境
- 浏览器访问 `http://localhost:4173/`
- 进入「图文报告生成」→ 新建报告
## 测试用例 1溢出段落自动合并
**前置条件**
手动构造一个 DOM 结构被「破坏」的 AI 区域(模拟用户回车导致段落溢出):
1. 插入 AI 可编辑区域「手术步骤」
2. 在区域内输入第 2 段内容
3. 在区域末尾按回车,输入第 3、4、5 段(观察 DOM确认 `<p>` 标签变成了 `.ai-content` 的兄弟节点)
**步骤**
1. 勾选「允许修改正文」→ 选中「手术步骤」区域
2. 发送「请完善手术步骤描述」
**预期结果**
- diff 弹窗左侧「原始版本」应显示全部 2-5 段内容,而不只是第 2 段
- diff 弹窗右侧「AI 提议版本」也显示完整的多段落内容
- 确认注入后,编辑器中 AI 区域的全部内容被替换,没有重复段落
## 测试用例 2正常结构不受影响
**前置条件**
AI 可编辑区域内的所有段落都在 `.ai-content` 内部(正常结构)。
**步骤**
1. 发送修改指令
**预期结果**
- diff 弹窗左侧正常显示所有段落
- 合并逻辑不会误删或误移任何内容
## 测试用例 3编译与部署
**步骤**
1. 执行 `npm run build`
2. 确认无 TypeScript 编译错误
3. 预览服务正常启动并返回 200
**预期结果**
- `vite build` 成功完成
- 预览页面可正常访问

View File

@@ -0,0 +1,40 @@
# 需求分析
## 时间戳
2026-04-19 18:05
## 需求来源
用户发现 AI 修改确认弹窗的「原始版本」左侧只显示了一段内容,而编辑器中的 AI 可编辑区域实际上有 2-5 段内容。
## 现象
从用户提供的 DOM 源码可以清楚看到:
```html
<div class="ai-content">
<p><span>2腹腔镜探查...</span></p>
</div>
<p>3.切除胆囊:...</p>
<p>4.检查腹腔内无活动性出血及漏胆后:...</p>
<p>5.手术顺利,麻醉满意:...</p>
```
- `.ai-content` 内只有第 2 段
- 第 3、4、5 段变成了 `.ai-content` 的**兄弟节点**(在 `.ai-region` 内但在 `.ai-content` 外)
- 左侧 diff 弹窗只显示 `.ai-content.innerHTML`,所以只有第 2 段
## 根因分析
**浏览器 contentEditable 机制的锅**
当用户在 `.ai-content`(一个 contentEditable 的 div内按回车换行或粘贴包含多个段落的外部文本时浏览器的默认行为会截断当前的 `div`,在同级生成新的 `<p>` 标签。这导致后续的段落脱离了 `.ai-content` 父容器,变成了 `.ai-region` 的直接子节点。
## 解决方向
**代码层面修复**:在 `handleAIGenerate` 获取 `currentHtml` 之前,自动把 `.ai-region``.ai-content` 之外的 `<p>` 节点移回 `.ai-content`。这样:
1. `currentHtml` 就能包含所有段落
2. 左侧 diff 弹窗显示全部内容
3. `confirmAiInjection` 注入时替换 `.ai-content`,此时 `.ai-content` 已包含所有段落
## 约束条件
- 只移动 `.ai-content` 之后的 `<p>` 节点,不移动 `.ai-region-label` 或其他元素
- 移动后要同步更新 `contentRef.current``saveDraftToStorage()`
- 不改变现有 diff 弹窗和注入逻辑