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 的 `