From 7275906f3c8ed6d491dd4e71078b330ff3d62e1a Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sun, 19 Apr 2026 20:33:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20AI=E6=B3=A8=E5=85=A5=E5=90=8ECtr?= =?UTF-8?q?l+Z=E5=A4=B1=E6=95=88=20+=20=E5=AD=97=E4=BD=93=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - confirmAiInjection改用Range.selectNodeContents + execCommand('insertHTML')保留浏览器撤销栈 - handleAIGenerate中对cleanHtml增加

标签内联样式注入:padding 0px、font-family SimSun、font-size 12pt、line-height 1.5 - 确保AI替换后的文字字体与原有文字完全一致 --- src/pages/ReportEditor.tsx | 9 ++- 工程分析/20260419_2030/实现方案.md | 94 ++++++++++++++++++++++++++++++ 工程分析/20260419_2030/测试方案.md | 42 +++++++++++++ 工程分析/20260419_2030/需求分析.md | 29 +++++++++ 工程分析/经验记录.md | 35 +++++++++++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 工程分析/20260419_2030/实现方案.md create mode 100644 工程分析/20260419_2030/测试方案.md create mode 100644 工程分析/20260419_2030/需求分析.md diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 7a96e5a..8a0cf7d 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -957,6 +957,7 @@ export default function ReportEditor() { cleanHtml = cleanHtml.replace(//gi, ''); cleanHtml = cleanHtml.replace(/<\/p>\s*

/gi, '

'); cleanHtml = cleanHtml.trim(); + cleanHtml = cleanHtml.replace(/

/gi, '

'); if (targetRegionEl) { setDiffModal({ isOpen: true, @@ -982,7 +983,13 @@ export default function ReportEditor() { if (!editorRef.current) return; const targetContent = editorRef.current.querySelector(`.ai-region[data-ai-id="${regionId}"] .ai-content`) as HTMLElement; if (targetContent) { - targetContent.innerHTML = newHtml; + targetContent.focus(); + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(targetContent); + sel?.removeAllRanges(); + sel?.addRange(range); + document.execCommand('insertHTML', false, newHtml); targetContent.style.transition = 'background-color 0.3s ease'; targetContent.style.backgroundColor = '#bfdbfe'; setTimeout(() => { diff --git a/工程分析/20260419_2030/实现方案.md b/工程分析/20260419_2030/实现方案.md new file mode 100644 index 0000000..3c639b2 --- /dev/null +++ b/工程分析/20260419_2030/实现方案.md @@ -0,0 +1,94 @@ +# 实现方案 + +## 修改文件 +- `src/pages/ReportEditor.tsx` + +## 修改 1:`confirmAiInjection` 保留撤销栈(约 line 981-998) + +**原代码**: +```tsx + const confirmAiInjection = (newHtml: string, regionId: string) => { + if (!editorRef.current) return; + const targetContent = editorRef.current.querySelector(`.ai-region[data-ai-id="${regionId}"] .ai-content`) as HTMLElement; + if (targetContent) { + targetContent.innerHTML = newHtml; + targetContent.style.transition = 'background-color 0.3s ease'; + targetContent.style.backgroundColor = '#bfdbfe'; + setTimeout(() => { + targetContent.style.backgroundColor = '#eff6ff'; + setTimeout(() => { + targetContent.style.backgroundColor = 'transparent'; + }, 800); + }, 400); + contentRef.current = editorRef.current.innerHTML; + saveDraftToStorage(); + } + setDiffModal(null); + }; +``` + +**新代码**: +```tsx + const confirmAiInjection = (newHtml: string, regionId: string) => { + if (!editorRef.current) return; + const targetContent = editorRef.current.querySelector(`.ai-region[data-ai-id="${regionId}"] .ai-content`) as HTMLElement; + if (targetContent) { + targetContent.focus(); + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(targetContent); + sel?.removeAllRanges(); + sel?.addRange(range); + document.execCommand('insertHTML', false, newHtml); + targetContent.style.transition = 'background-color 0.3s ease'; + targetContent.style.backgroundColor = '#bfdbfe'; + setTimeout(() => { + targetContent.style.backgroundColor = '#eff6ff'; + setTimeout(() => { + targetContent.style.backgroundColor = 'transparent'; + }, 800); + }, 400); + contentRef.current = editorRef.current.innerHTML; + saveDraftToStorage(); + } + setDiffModal(null); + }; +``` + +**变更点**: +1. 去掉 `targetContent.innerHTML = newHtml;` +2. 增加 `targetContent.focus()` +3. 使用 `Range.selectNodeContents(targetContent)` 选中区域内所有旧内容 +4. 使用 `document.execCommand('insertHTML', false, newHtml)` 执行替换 +5. 浏览器撤销栈会记录这次替换,Ctrl+Z 可正常撤销 + +## 修改 2:`handleAIGenerate` 中 `

` 标签注入样式(约 line 955-970) + +**原代码**: +```tsx + if (responseJson.updatedHtml && aiModifyEnabled) { + let cleanHtml = responseJson.updatedHtml; + cleanHtml = cleanHtml.replace(//gi, ''); + cleanHtml = cleanHtml.replace(/<\/p>\s*

/gi, '

'); + cleanHtml = cleanHtml.trim(); + if (targetRegionEl) { +``` + +**新代码**: +```tsx + if (responseJson.updatedHtml && aiModifyEnabled) { + let cleanHtml = responseJson.updatedHtml; + cleanHtml = cleanHtml.replace(//gi, ''); + cleanHtml = cleanHtml.replace(/<\/p>\s*

/gi, '

'); + cleanHtml = cleanHtml.trim(); + cleanHtml = cleanHtml.replace(/

/gi, '

'); + if (targetRegionEl) { +``` + +**变更点**:在 `cleanHtml.trim()` 后增加一行正则替换,将所有 `

` 标签替换为带标准内联样式的 `

` 标签。 + +**样式值说明**: +- `padding: 0px`:与原始段落一致 +- `font-family: SimSun`:宋体 +- `font-size: 12pt`:12pt 字号 +- `line-height: 1.5`:1.5 倍行高 diff --git a/工程分析/20260419_2030/测试方案.md b/工程分析/20260419_2030/测试方案.md new file mode 100644 index 0000000..ecaaf73 --- /dev/null +++ b/工程分析/20260419_2030/测试方案.md @@ -0,0 +1,42 @@ +# 测试方案 + +## 测试环境 +- 浏览器访问 `http://localhost:4173/` +- 进入「图文报告生成」→ 新建报告 + +## 测试用例 1:Ctrl+Z 可撤销 AI 修改 + +**步骤**: +1. 编辑器中插入 AI 可编辑区域,写入一些内容 +2. 勾选「允许修改正文」→ 发送修改指令 +3. 在 diff 弹窗中点击「确认并写入报告」 +4. 按 Ctrl+Z(或点击工具栏撤销按钮) + +**预期结果**: +- AI 修改的内容被撤销,恢复到修改前的状态 +- 可连续按 Ctrl+Z 继续撤销更早的操作 +- 撤销后内容完整,无 DOM 结构损坏 + +## 测试用例 2:替换后字体格式一致 + +**步骤**: +1. 编辑器中 AI 可编辑区域内原有内容带宋体 12pt 样式 +2. 发送 AI 修改指令 +3. 观察 diff 弹窗左右两侧 +4. 确认注入后观察编辑器中该区域内容 + +**预期结果**: +- diff 弹窗右侧「AI 提议版本」的字体为宋体 12pt,与左侧一致 +- 确认注入后,编辑器中 AI 区域的文字字体与周边/原有文字一致 +- 无视觉割裂感 + +## 测试用例 3:编译与部署 + +**步骤**: +1. 执行 `npm run build` +2. 确认无 TypeScript 编译错误 +3. 预览服务正常启动并返回 200 + +**预期结果**: +- `vite build` 成功完成 +- 预览页面可正常访问 diff --git a/工程分析/20260419_2030/需求分析.md b/工程分析/20260419_2030/需求分析.md new file mode 100644 index 0000000..5182749 --- /dev/null +++ b/工程分析/20260419_2030/需求分析.md @@ -0,0 +1,29 @@ +# 需求分析 + +## 时间戳 +2026-04-19 20:30 + +## 需求来源 +用户在 AI 修改确认后遇到两个问题: +1. 点击「确认并写入报告」后,Ctrl+Z 撤销按钮无法撤销 AI 的修改 +2. AI 替换后的文字字体格式与原有文字不一致(原有宋体 12pt,替换后变为浏览器默认字体) + +## 问题 1:Ctrl+Z 撤销失效 + +**现象**:AI 修改确认注入后,按 Ctrl+Z 无法撤销。 + +**根因分析**: +`confirmAiInjection` 使用 `targetContent.innerHTML = newHtml;` 直接修改 DOM 属性。这种方式绕过了浏览器 `contentEditable` 的原生撤销/重做历史栈,导致浏览器无法追踪这次更改。 + +## 问题 2:字体格式不一致 + +**现象**: +- 替换前:`

内容

` +- 替换后:`

内容

`(无内联样式,显示为浏览器默认字体) + +**根因分析**: +大模型严格按照指令返回纯净的 `

` 标签,没有内联样式。注入后浏览器使用默认字体渲染,与原有宋体 12pt 不一致。 + +## 解决方向 +1. `confirmAiInjection` 改用 `Range.selectNodeContents` + `document.execCommand('insertHTML')`,让浏览器原生撤销栈记录这次替换 +2. `handleAIGenerate` 中对 `cleanHtml` 进行正则替换,给 `

` 标签注入标准内联样式 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 88087a4..862651d 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -845,3 +845,38 @@ if (aiRegion && targetRegionEl) { **D. 后续如何避免问题** - 任何通过 iframe 或独立文档实现的打印/导出功能,都必须在 iframe 的 `