- Compress insertSmartField HTML to single-line, remove trailing - Compress smartField helper in defaultContent.ts to single-line - Add white-space: nowrap to .smart-field-wrapper (CSS + inline) - Add keydown interceptor in TemplateManage to prevent Backspace/Delete from removing whole <p> when adjacent to smart-field-wrapper - Update experience record (#14)
163 lines
7.5 KiB
Markdown
163 lines
7.5 KiB
Markdown
# 实现方案 — 2026-04-17-09-36-07
|
||
|
||
## 根因分析
|
||
|
||
1. **多余空格**:`TemplateManage.tsx` 的 `insertSmartField` 函数在 HTML 字符串末尾追加了 ` `,这是导致字段后跟随大量空白的主要原因。
|
||
2. **异常换行**:`inline-block` 元素默认会在边界处根据容器宽度自动换行;`contenteditable="false"` 节点在行尾时,浏览器可能将其视为独立的渲染单元进行换行。
|
||
3. **Backspace 误删整行**:当光标位于 `contenteditable="false"` 的内联元素之后时,Webkit/Blink 内核的默认行为无法正确删除该节点,而是向上寻找到父级 `<p>` 并将其删除。这是 `contentEditable` 的经典 Bug。
|
||
4. **默认模板未预置**:`defaultContent.ts` 中的第一行仍使用红色纯文本占位符,没有使用 `smartField()` 函数生成智能控件。
|
||
|
||
## 修改文件清单
|
||
|
||
| 文件 | 修改类型 | 说明 |
|
||
|------|---------|------|
|
||
| `src/pages/TemplateManage.tsx` | 修改 | 优化 `insertSmartField` HTML(移除 ` `、压缩为一行);增加 `keydown` 事件拦截,保护 `.smart-field-wrapper` 不被 Backspace 误删 |
|
||
| `src/utils/defaultContent.ts` | 修改 | 将默认模板第一行的红色占位符替换为预置的智能字段控件 |
|
||
| `src/index.css` | 修改(可选) | 给 `.smart-field-wrapper` 增加 `white-space: nowrap` |
|
||
|
||
---
|
||
|
||
## 具体代码变更
|
||
|
||
### 变更 1:`src/pages/TemplateManage.tsx` — 优化插入 HTML
|
||
|
||
**当前代码(约第 159-173 行):**
|
||
```tsx
|
||
const insertSmartField = (field: FormField) => {
|
||
editorRef.current?.focus();
|
||
const html = `
|
||
<span class="smart-field-wrapper" contenteditable="false">
|
||
<span class="field-value"
|
||
data-bind="${field.key}"
|
||
contenteditable="true"
|
||
style="min-width: 32px; padding: 0 4px; margin: 0 2px; border: 1px solid #cbd5e1; border-radius: 2px; display: inline-block; background: #f8fafc; color: #0f172a; line-height: 1.2; font-size: inherit; vertical-align: text-bottom; box-sizing: border-box; min-height: 1.2em;">
|
||
</span>
|
||
</span>
|
||
`;
|
||
document.execCommand('insertHTML', false, html);
|
||
editorRef.current?.focus();
|
||
};
|
||
```
|
||
|
||
**修改为:**
|
||
```tsx
|
||
const insertSmartField = (field: FormField) => {
|
||
editorRef.current?.focus();
|
||
const html = `<span class="smart-field-wrapper" contenteditable="false"><span class="field-value" data-bind="${field.key}" contenteditable="true" style="min-width: 32px; padding: 0 4px; margin: 0 2px; border: 1px solid #cbd5e1; border-radius: 2px; display: inline-block; background: #f8fafc; color: #0f172a; line-height: 1.2; font-size: inherit; vertical-align: text-bottom; box-sizing: border-box; min-height: 1.2em; white-space: nowrap;"></span></span>`;
|
||
document.execCommand('insertHTML', false, html);
|
||
editorRef.current?.focus();
|
||
};
|
||
```
|
||
|
||
**改动点**:
|
||
- 移除末尾的 ` `。
|
||
- 将多行模板字符串压缩为一行,消除源码换行被渲染为空格的问题。
|
||
- 在 `field-value` 的内联样式中增加 `white-space: nowrap;`。
|
||
|
||
### 变更 2:`src/pages/TemplateManage.tsx` — 拦截 Backspace/Delete 防止误删整行
|
||
|
||
在现有的 `useEffect`(用于监听编辑器 click 事件)附近,新增一个 `useEffect` 监听 `keydown`:
|
||
|
||
```tsx
|
||
useEffect(() => {
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key !== 'Backspace' && e.key !== 'Delete') return;
|
||
const sel = window.getSelection();
|
||
if (!sel || sel.rangeCount === 0) return;
|
||
const range = sel.getRangeAt(0);
|
||
if (!range.collapsed) return;
|
||
|
||
const container = range.startContainer;
|
||
const offset = range.startOffset;
|
||
|
||
// Find the node immediately before the cursor
|
||
let prevNode: Node | null = null;
|
||
if (container.nodeType === Node.TEXT_NODE) {
|
||
if (offset === 0) {
|
||
prevNode = container.previousSibling;
|
||
}
|
||
} else if (container.nodeType === Node.ELEMENT_NODE) {
|
||
prevNode = (container as Element).childNodes[offset - 1] || null;
|
||
}
|
||
|
||
if (!prevNode) return;
|
||
|
||
// If the previous node is our smart field wrapper, remove it manually
|
||
const fieldWrapper = prevNode.nodeType === Node.ELEMENT_NODE
|
||
? (prevNode as Element).closest('.smart-field-wrapper')
|
||
: prevNode.parentElement?.closest('.smart-field-wrapper');
|
||
|
||
if (fieldWrapper && editorRef.current?.contains(fieldWrapper)) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
fieldWrapper.remove();
|
||
}
|
||
};
|
||
|
||
const editor = editorRef.current;
|
||
if (editor) {
|
||
editor.addEventListener('keydown', handleKeyDown, true);
|
||
}
|
||
return () => {
|
||
if (editor) {
|
||
editor.removeEventListener('keydown', handleKeyDown, true);
|
||
}
|
||
};
|
||
}, [currentTemplateId]);
|
||
```
|
||
|
||
> 注意:此逻辑与 `ReportEditor.tsx` 中保护 `.image-placeholder` 不被误删的 `handleKeyDown` 思路一致。
|
||
|
||
### 变更 3:`src/utils/defaultContent.ts` — 默认模板预置字段控件
|
||
|
||
**当前第一行(约第 15-23 行):**
|
||
```html
|
||
<div class="template-info-section">
|
||
<p style="font-family: SimSun;">
|
||
姓名:${smartField('patientName')}
|
||
性别:${smartField('patientGender')}
|
||
年龄:${smartField('patientAge')}
|
||
科别:${smartField('department')}
|
||
床号:${smartField('bedNumber')}
|
||
住院号:${smartField('hospitalId')}
|
||
</p>
|
||
```
|
||
|
||
这部分已经在上一版中被替换为 `smartField()`,但需要确认是否末尾有空格问题。由于 `smartField()` 函数本身返回的 HTML 不带 ` `(且是压缩的一行),`defaultContent.ts` 中的这段代码本身没有问题。
|
||
|
||
**但**:如果之前的 `smartField()` 定义末尾带有 ` `,则需要一并修正。当前 `defaultContent.ts` 中的 `smartField` 定义已经在上一版中被修正为压缩的一行且不带 ` `,所以默认模板本身已经符合要求。
|
||
|
||
**确认结果**:`defaultContent.ts` 中的第一行在上一版(`2026-04-17-00-13-09`)中已经替换为 `smartField('patientName')` 等智能控件。**本次只需确保 `smartField` 辅助函数的定义与变更 1 保持一致(移除 ` `、压缩为一行、增加 `white-space: nowrap`)即可。**
|
||
|
||
### 变更 4:`src/index.css` — 增加 `white-space: nowrap`
|
||
|
||
在 `.smart-field-wrapper` 的样式中增加:
|
||
|
||
```css
|
||
.smart-field-wrapper {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin: 0 2px;
|
||
vertical-align: text-bottom;
|
||
white-space: nowrap;
|
||
}
|
||
```
|
||
|
||
> 由于 `field-value` 已经通过内联样式设置了 `white-space: nowrap`,给外层 `.smart-field-wrapper` 增加此属性可作为双重保险。
|
||
|
||
## 风险点
|
||
|
||
| 风险 | 级别 | 应对措施 |
|
||
|------|------|---------|
|
||
| 移除 ` ` 后,字段与前/后文本之间没有间隔,显得拥挤 | 低 | `margin: 0 2px` 已经提供了 2px 的左右间距,视觉上足够紧凑 |
|
||
| `keydown` 拦截可能影响编辑器其他正常删除操作 | 低 | 拦截逻辑严格限定在光标前一个节点为 `.smart-field-wrapper` 时才生效,其他情况正常放行 |
|
||
| 老模板中已插入的字段仍带有 ` ` | 低 | 老模板中的字段只是带有一个额外的空格,不影响功能;用户可手动删除重插 |
|
||
|
||
## 回滚策略
|
||
|
||
本次修改范围极小,仅调整 `insertSmartField` 的 HTML 输出和增加一个 `keydown` 事件监听。如出现异常,可直接 `git revert` 回滚。
|
||
|
||
---
|
||
|
||
**⚠️ 请审核以上方案,确认无误后回复「确认」或提出修改意见,我将继续编写测试方案。**
|