# 实现方案 — 2026-04-17-13-32-07 ## 目标 修复 `TemplateManage.tsx` 中两个编辑器交互问题: 1. `Ctrl+Z` 快捷键无法撤销自定义删除行为。 2. 在特定 HTML 结构(段落以 `
` 结尾)下插入 `smart-field-wrapper` 会导致换行错位。 --- ## 变更文件 - `src/pages/TemplateManage.tsx` --- ## 具体改动 ### 改动 A:拦截键盘 Undo/Redo 快捷键 **位置**:`TemplateManage.tsx` 中现有的 `keydown` 事件监听 `useEffect`(第 184~243 行附近)。 **做法**: 在 `handleKeyDown` 函数的最开头,增加对 `Ctrl+Z` / `Cmd+Z` / `Ctrl+Shift+Z` / `Ctrl+Y` / `Cmd+Y` 的拦截: ```typescript if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'z') { e.preventDefault(); if (e.shiftKey) { handleRedo(); } else { handleUndo(); } return; } if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'y') { e.preventDefault(); handleRedo(); return; } ``` **注意**:`handleUndo` / `handleRedo` 在该组件内是稳定函数(通过 ref 访问 DOM 状态),因此可直接在原生事件监听器闭包中调用,无需额外依赖。 --- ### 改动 B:替换 `insertSmartField` 的插入方式 **位置**:`insertSmartField` 函数(第 304~315 行附近)。 **原逻辑**: ```typescript document.execCommand('insertHTML', false, html); ``` **新逻辑**: 使用 `Range.insertNode()` 精确插入,避免 `execCommand` 在 `
` 边界处的标签逃逸。 ```typescript const insertSmartField = (field: FormField) => { editorRef.current?.focus(); restoreSelection(); if (editorRef.current?.querySelector(`[data-bind="${field.key}"]`)) { alert(`字段 "${field.label}" 已存在,请勿重复插入。`); return; } pushHistory(); const html = ` ×​`; const sel = window.getSelection(); if (sel && sel.rangeCount > 0) { const range = sel.getRangeAt(0); range.deleteContents(); const wrapper = document.createElement('div'); wrapper.innerHTML = html; const fragment = document.createDocumentFragment(); while (wrapper.firstChild) { fragment.appendChild(wrapper.firstChild); } range.insertNode(fragment); // 将光标移动到插入内容末尾 const lastNode = fragment.lastChild; if (lastNode) { const newRange = document.createRange(); newRange.setStartAfter(lastNode); newRange.collapse(true); sel.removeAllRanges(); sel.addRange(newRange); } } editorRef.current?.focus(); saveTemplateContent(); }; ``` **说明**: - `range.deleteContents()` 对 collapsed 的光标无实际影响,安全。 - `fragment.lastChild` 引用的是已被移入文档的具体节点,`setStartAfter` 可正确定位光标。 - 末尾的 `​`(零宽空格)作为 `TextNode` 被一同插入,依然起到防止字段被意外吞并的作用。 --- ## 回滚策略 - 修改前 `git` 仓库已处于干净状态(最新提交 `b822bb1`)。 - 若验证失败,可直接 `git checkout -- src/pages/TemplateManage.tsx` 回滚到上一版本,重新分析。 --- ## 无其他依赖变更 - 不新增 npm 依赖。 - 不修改 `defaultContent.ts` 或 `index.css`。