- Intercept Ctrl+Z/Y keyboard shortcuts in keydown listener and route
to custom undoStack/redoStack to fix undo inconsistency.
- Replace execCommand('insertHTML') with precise Range.insertNode()
in insertSmartField to prevent <span> escaping out of <p> when
preceded by <br>.
3.6 KiB
3.6 KiB
需求分析 — 2026-04-17-13-32-07
用户反馈
- Ctrl+Z 快捷键无法撤销对
smart-field-wrapper的删除,但点击工具栏的撤销按钮可以正常撤销。 - 插入
smart-field-wrapper时仍会分成两行。用户怀疑是原本文本结构的问题,并提供了出现问题的 HTML 片段:用户补充:把字段插到下面的内容中(即上述 HTML 中已有字段的位置)则没有问题。<p style="font-family: SimSun;"> <strong>手术日期:</strong><br> </p> <span class="smart-field-wrapper" ...>...</span>
问题 1:Ctrl+Z 快捷键撤销失效
现状
TemplateManage.tsx已实现了自定义的undoStack/redoStack以及handleUndo()/handleRedo()。- 工具栏的撤销/重做按钮调用的是自定义函数,能够正确恢复
innerHTML历史快照。 - 键盘按下
Ctrl+Z时,浏览器会触发原生undo(document.execCommand('undo')),它与自定义历史栈完全脱节,因此:- 若原生栈为空(或已耗尽),则没有任何反应;
- 若原生栈有记录,可能恢复出意料之外的状态。
根因
- 当前只在编辑器上拦截了
Backspace/Delete键(用于防止误删整段),没有拦截Ctrl+Z/Ctrl+Y快捷键。
需求
- 在
TemplateManage.tsx的编辑器keydown事件中,拦截以下快捷键并路由到自定义 Undo/Redo 逻辑:Ctrl+Z/Cmd+Z→handleUndo()Ctrl+Shift+Z/Cmd+Shift+Z→handleRedo()Ctrl+Y/Cmd+Y→handleRedo()
- 拦截后调用
e.preventDefault()阻止浏览器原生行为。
问题 2:插入 smart-field-wrapper 分成两行
现状
insertSmartField()使用document.execCommand('insertHTML', false, html)插入字段。- 当光标位于一个以
<br>结尾的<p>标签末尾时,WebKit/Blink 内核的insertHTML会把<span>插到<p>外部,导致字段独自占据新行。
根因
- 用户提供的 HTML 明确显示了这一现象:
<span>跑到了</p>之后。 execCommand('insertHTML')对块级元素边界(尤其是末尾存在<br>时)的自动修正行为不可控。
需求
- 将
insertSmartField()的插入方式从execCommand('insertHTML')替换为精确的Range.insertNode()手动插入:restoreSelection()恢复光标;- 获取当前
Selection的Range; range.deleteContents()(对 collapsed 光标无实际删除);- 将 HTML 字符串转为
DocumentFragment; range.insertNode(fragment)精确插入到 Range 位置;- 把光标移动到插入内容的末尾(最后一个节点之后),保持编辑连贯性。
- 该方式不依赖浏览器的
execCommand自动修正,可避免<span>被抛到<p>外部。
影响范围
- 仅
src/pages/TemplateManage.tsx需要修改:insertSmartField()函数(替换插入逻辑)。keydown事件监听useEffect(增加快捷键拦截)。
- 其他页面(
ReportEditor.tsx、ReportManage.tsx等)不受影响。
验收标准
- 在
TemplateManage编辑器中删除一个smart-field-wrapper(通过点击 × 或 Backspace/Delete)后,立即按Ctrl+Z,字段能够完整恢复;按Ctrl+Y(或Ctrl+Shift+Z)能够重做删除。 - 在
<p>标签末尾(尤其是存在<br>的情况下)插入smart-field-wrapper,字段与段落保持在同一行,不再被拆成两行。 npm run lint通过,无 TypeScript 编译错误。