2026-04-18-17-27-51 - 修复TemplateManage静态占位符插入、重构默认模板排版、修复Logo删除按钮交互

This commit is contained in:
Administrator
2026-04-18 17:33:07 +08:00
parent 67fb2c9080
commit e1dc961ecf
5 changed files with 340 additions and 55 deletions

View File

@@ -1266,7 +1266,29 @@ export default function TemplateManage() {
const text = showShortText ? '插图' : hintText;
html = `<span id="${id}" class="image-placeholder" data-placeholder="true" contenteditable="false"${modeAttr} style="${styleStr}"><span class="delete-btn" contenteditable="false">×</span><span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">${text}</span></span>&#8203;`;
}
execCmd('insertHTML', html);
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
const fragment = document.createDocumentFragment();
while (wrapper.firstChild) {
fragment.appendChild(wrapper.firstChild);
}
const sel2 = window.getSelection();
if (sel2 && sel2.rangeCount > 0) {
const range = sel2.getRangeAt(0);
range.deleteContents();
range.insertNode(fragment);
const lastNode = fragment.lastChild;
if (lastNode) {
range.setStartAfter(lastNode);
range.collapse(true);
sel2.removeAllRanges();
sel2.addRange(range);
}
} else if (editorRef.current) {
editorRef.current.appendChild(fragment);
}
editorRef.current?.focus();
saveTemplateContent();
setPlaceholderModal({...placeholderModal, isOpen: false});
}} className="px-4 py-2 bg-accent text-white rounded text-sm"></button>
</div>

View File

@@ -1,65 +1,62 @@
const smartField = (key: string) => `<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;position:relative;"><span class="field-value" data-bind="${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;outline:none;"> </span><span class="delete-btn" contenteditable="false">×</span></span>&#8203;`;
export const defaultReportContent = `
<!-- 医院Logo -->
<p style="text-align: center; margin-bottom: 16px;" contenteditable="false">
<span class="image-placeholder" data-placeholder="true" contenteditable="false" data-mode="manual" style="display:inline-flex;align-items:center;justify-content:center;width:65px;height:65px;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 auto;cursor:pointer;">
<table style="width: 100%; border: none; margin-bottom: 16px;">
<tr>
<td style="width: 20%; vertical-align: bottom; border: none; text-align: left;">
<span class="image-placeholder" data-placeholder="true" contenteditable="false" data-mode="manual" style="position:relative;display:inline-flex;align-items:center;justify-content:center;width:65px;height:65px;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;cursor:pointer;">
<span class="delete-btn" contenteditable="false">×</span>
<span class="placeholder-text" style="color:#94a3b8;font-size:11px;pointer-events:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;">插入图片</span>
</span>
</p>
</td>
<td style="width: 60%; text-align: center; vertical-align: middle; border: none;">
<div style="font-size: 14pt; font-family: SimSun; border-bottom: 1px solid #000; padding-bottom: 4px; margin-bottom: 8px; display: inline-block;">西 安 交 通 大 学 第 一 附 属 医 院</div>
<div style="font-size: 16pt; font-family: SimSun;">手术记录</div>
</td>
<td style="width: 20%; border: none;"></td>
</tr>
</table>
<!-- 医院名称 -->
<p style="text-align: center; font-family: SimSun; margin-bottom: 8px;" contenteditable="false">
<strong><u>西 安 交 通 大 学 第 一 附 属 医 院</u></strong>
</p>
<!-- 报告标题 -->
<h1 style="font-family: SimSun; font-size: 20px; margin: 16px 0; text-align: center;" contenteditable="false">手术记录</h1>
<div class="template-info-section">
<p style="font-family: SimSun;">
姓名:${smartField('patientName')}
性别:${smartField('patientGender')}
年龄:${smartField('patientAge')}
科别:${smartField('department')}
床号:${smartField('bedNumber')}
<div style="border-bottom: 1px solid #000; padding-bottom: 4px; margin-bottom: 12px;">
<p style="font-family: SimSun; font-size: 11pt; font-weight: normal; margin: 0; line-height: 1.8;">
姓名:${smartField('patientName')}&nbsp;&nbsp;&nbsp;
性别:${smartField('patientGender')}&nbsp;&nbsp;&nbsp;
年龄:${smartField('patientAge')}&nbsp;&nbsp;&nbsp;
科别:${smartField('department')}&nbsp;&nbsp;&nbsp;
床号:${smartField('bedNumber')}&nbsp;&nbsp;&nbsp;
住院号:${smartField('hospitalId')}
</p>
<p style="font-family: SimSun;">
<strong>手术日期:</strong>${smartField('surgeryDate')}
</p>
<p style="font-family: SimSun;">
<strong>术前诊断:</strong>${smartField('preoperativeDiagnosis')}
</p>
<p style="font-family: SimSun;">
<strong>术后诊断:</strong>${smartField('postoperativeDiagnosis')}
</p>
<p style="font-family: SimSun;">
<strong>手术名称:</strong>${smartField('title')}
</p>
<p style="font-family: SimSun;">
手术开始时间:${smartField('startTime')}
手术终止时间:${smartField('endTime')}
</p>
<p style="font-family: SimSun;">
手术者:${smartField('surgeon')}
助手:${smartField('assistant')}
</p>
<p style="font-family: SimSun;">
麻醉师:${smartField('anesthesiologist')}
麻醉方式:${smartField('anesthesiaType')}
</p>
</div>
<p style="font-family: SimSun;">
<p style="font-family: SimSun; font-size: 12pt; line-height: 1.8;">
<strong>手术日期:</strong>${smartField('surgeryDate')}
</p>
<p style="font-family: SimSun; font-size: 12pt; line-height: 1.8;">
<strong>术前诊断:</strong>${smartField('preoperativeDiagnosis')}
</p>
<p style="font-family: SimSun; font-size: 12pt; line-height: 1.8;">
<strong>术中诊断:</strong>${smartField('postoperativeDiagnosis')}
</p>
<p style="font-family: SimSun; font-size: 12pt; line-height: 1.8;">
<strong>手术名称:</strong>${smartField('title')}
</p>
<table style="width: 100%; border: none; font-family: SimSun; font-size: 12pt; margin-bottom: 12pt;">
<tr>
<td style="border: none; padding: 4px 0; width: 50%;">手术开始时间:${smartField('startTime')}</td>
<td style="border: none; padding: 4px 0; width: 50%;">手术终止时间:${smartField('endTime')}</td>
</tr>
<tr>
<td style="border: none; padding: 4px 0;">手术者:${smartField('surgeon')}</td>
<td style="border: none; padding: 4px 0;">助手:${smartField('assistant')}</td>
</tr>
<tr>
<td style="border: none; padding: 4px 0;">麻醉师:${smartField('anesthesiologist')}</td>
<td style="border: none; padding: 4px 0;">麻醉方式:${smartField('anesthesiaType')}</td>
</tr>
</table>
<p style="font-family: SimSun; font-size: 12pt;">
<strong>手术步骤、术中出现的情况及处理:</strong>
</p>

View File

@@ -0,0 +1,82 @@
# 实现方案 —— 2026-04-18-17-27-51
## 方案目标
修复 TemplateManage 静态占位符插入 Bug重构默认报告模板顶部排版修复 Logo 删除按钮交互。
## 需求 1修复静态图片占位符插入不显示
### 问题根因
`TemplateManage.tsx``insertImage()` 使用 `document.execCommand('insertHTML', false, html)`。现代浏览器对含 `contenteditable="false"` 的复杂嵌套标签会自动修正/拍平,导致外层 `.image-placeholder` 容器丢失DOM 仅剩零散子元素,视觉上不可见。
### 解决步骤
1. **定位 `insertImage` 函数**:找到 `TemplateManage.tsx` 中通过 `document.execCommand('insertHTML')` 插入占位符的逻辑。
2. **替换为 `Range.insertNode`**
- 创建临时 `div`,将 HTML 字符串写入 `innerHTML`
- 将子节点逐个移入 `DocumentFragment`
- 获取当前 `Selection``RangeAt(0)`
- 调用 `range.deleteContents()` 清空当前选区。
- 调用 `range.insertNode(fragment)` 精确插入。
- 将光标移动到插入内容之后。
3. **保持原有弹窗逻辑不变**Modal 中的模式选择frame/manual、宽高输入等逻辑不受影响。
## 需求 2重构默认报告模板排版
### 排版设计
#### 页眉Logo + 医院名 + 标题)
使用 3 列 `<table>`(左 20%、中 60%、右 20%),中间列绝对居中:
- 左列Logo 占位符65×65`data-mode="manual"``position:relative`
- 中列:
- 第一行14pt SimSun「西 安 交 通 大 学 第 一 附 属 医 院」(带 `border-bottom: 1px solid #000` 下划线,使用 `display: inline-block`
- 第二行16pt SimSun「手术记录」
- 右列:留空
#### 基本信息栏(下划线贯穿)
使用 `<div style="border-bottom: 1px solid #000; padding-bottom: 4px; margin-bottom: 12px;">` 包裹一行:
- 11pt SimSun不加粗
- 姓名、性别、年龄、科别、床号、住院号,用 `&nbsp;` 间隔
#### 诊断/手术信息(单行加粗)
每项独立 `<p>`
- 12pt SimSun`font-weight: bold`
- 手术日期、术前诊断、术中诊断、手术名称
#### 双列信息(两项一行,不加粗)
使用 `<table style="width: 100%; border: none;">`
- 三行两列,每列 50%
- 12pt SimSun不加粗
- 手术开始/终止时间、手术者/助手、麻醉师/麻醉方式
#### 手术步骤标题
- 12pt SimSun`font-weight: bold`
- 「手术步骤、术中出现的情况及处理:」
#### 保留内容
- 5 条手术步骤段落文字(不变)
- 手术图片说明表格(需求 3 中已替换的最新 6 图格表格)
- 手术后情况段落(术后诊断、标本描述、病理检查、冰冻病理)
- 手术者签名占位符 + 撰写时间字段
### 涉及文件
`src/utils/defaultContent.ts` —— 完全重写 `defaultReportContent` 变量。
## 需求 3修复顶部 Logo 删除按钮
### 解决步骤
`defaultContent.ts` 中 Logo 占位符的 `style` 属性中增加 `position: relative;`,使绝对定位的 `.delete-btn` 相对于占位符自身定位,而非向外层逃逸。
```html
<span class="image-placeholder" data-placeholder="true" contenteditable="false" data-mode="manual" style="position:relative;display:inline-flex;...">
```
## 涉及文件及修改点
| 文件 | 修改点 |
|------|--------|
| `src/pages/TemplateManage.tsx` | `insertImage``execCommand('insertHTML')``Range.insertNode` |
| `src/utils/defaultContent.ts` | 完全重写顶部排版Logo 增加 `position:relative`;保留手术步骤/表格/底部段落 |
## 风险与注意事项
1. `Range.insertNode` 要求编辑器有有效光标/选区。若编辑器未聚焦或选区不在编辑器内需增加保护逻辑fallback 到 `editorRef.current.appendChild`)。
2. 默认模板重写后,需验证 `smartField()` 生成的所有字段占位符在新排版中是否正确渲染。
3. 打印时需确认新排版的下划线、表格边框在 `@media print` 中正常显示。
4. `&nbsp;` 分隔的基本信息栏在打印时可能换行,需测试实际打印效果。

View File

@@ -0,0 +1,155 @@
# 测试方案 —— 2026-04-18-17-27-51
## 测试目标
验证 TemplateManage 静态占位符插入修复、默认模板排版重构、Logo 删除按钮修复。
## 测试用例
### TC-01TemplateManage 插入静态图片占位符
**前置条件**:进入 /template-manage编辑器有焦点
**操作步骤**
1. 点击工具栏「插入图片占位符」
2. 在弹窗中选择「静态图片占位」
3. 输入宽度 200高度 200
4. 点击「确认插入」
**预期结果**
- 编辑器中出现虚线边框的占位符框
- 占位符带有 `class="image-placeholder"``data-mode="manual"`
- 占位符内部显示「插入/点击放置图片」文字
- 占位符右上角显示红色「×」删除按钮
---
### TC-02TemplateManage 插入手术影像占位符
**前置条件**:进入 /template-manage
**操作步骤**
1. 点击工具栏「插入图片占位符」
2. 选择「手术影像占位」
3. 点击「确认插入」
**预期结果**
- 占位符正常显示
- 带有 `data-mode="frame"`
- 可接受关键帧拖拽填充
---
### TC-03TemplateManage 占位符删除按钮
**前置条件**:已插入占位符
**操作步骤**
1. 鼠标悬浮在占位符上
2. 点击右上角的红色「×」
**预期结果**
- 占位符被删除
- 撤销按钮可恢复该占位符
---
### TC-04新建报告默认模板排版——抬头
**前置条件**:退出重新登录,进入 /report-editor新建报告
**操作步骤**
1. 查看报告顶部
**预期结果**
- 左侧有 65×65 的 Logo 占位符(虚线框)
- 中间偏右有 14pt 下划线文字「西 安 交 通 大 学 第 一 附 属 医 院」
- 下方有 16pt 文字「手术记录」
- 整体居中对齐
---
### TC-05新建报告默认模板排版——基本信息栏
**前置条件**:新建报告已加载默认模板
**操作步骤**
1. 查看抬头下方的基本信息行
**预期结果**
- 一行显示:姓名、性别、年龄、科别、床号、住院号
- 字体 11pt不加粗
- 整行下方有一条黑色贯穿下划线
---
### TC-06新建报告默认模板排版——诊断信息
**前置条件**:新建报告已加载默认模板
**操作步骤**
1. 查看手术日期、术前诊断、术中诊断、手术名称
**预期结果**
- 每项独立一行
- 12pt 字体,加粗
- 格式为:「手术日期:」+ smartField 占位符
---
### TC-07新建报告默认模板排版——双列信息
**前置条件**:新建报告已加载默认模板
**操作步骤**
1. 查看时间、人员、麻醉信息
**预期结果**
- 手术开始/终止时间在同一行,左右各 50%
- 手术者/助手在同一行
- 麻醉师/麻醉方式在同一行
- 12pt 字体,不加粗
---
### TC-08新建报告默认模板排版——手术步骤标题
**前置条件**:新建报告已加载默认模板
**操作步骤**
1. 查看「手术步骤、术中出现的情况及处理:」
**预期结果**
- 12pt 字体,加粗
- 位于双列信息下方
---
### TC-09Logo 占位符删除按钮可点击
**前置条件**:新建报告已加载默认模板
**操作步骤**
1. 鼠标悬浮在顶部 Logo 占位符上
2. 点击右上角的红色「×」
**预期结果**
- Logo 占位符被删除
- 可撤销恢复
---
### TC-10Logo 占位符图片上传
**前置条件**:新建报告已加载默认模板
**操作步骤**
1. 点击顶部 Logo 占位符
2. 选择本地上传一张图片
**预期结果**
- 图片正确显示在 65×65 区域内
- 图片不溢出占位符
---
### TC-11打印效果验证
**前置条件**:新建报告,填写部分内容
**操作步骤**
1. 点击打印按钮
2. 检查打印预览
**预期结果**
- 抬头排版正确Logo + 医院名 + 标题)
- 基本信息下划线可见
- 双列信息左右对齐
- 无多余虚线边框placeholder 填充后 border 应消失)
---
## 回归测试范围
- 验证 `ReportEditor` 中已有的 `image-placeholder` 点击上传、拖拽填充功能不受影响
- 验证 `TemplateManage` 中智能字段插入、删除、撤销/重做功能正常
- 验证 `smart-field-wrapper` 双向绑定正常工作
## 测试结论
TC-01~TC-11 全部通过,即可确认三项需求均正确实现。

View File

@@ -0,0 +1,29 @@
# 需求分析 —— 2026-04-18-17-27-51
## 需求来源
用户提出 TemplateManage 功能修复与默认报告模板排版重构需求。
## 需求概述
### 需求 1修复 TemplateManage 静态图片占位符插入不显示
`template-manage` 中通过弹窗选择「静态图片占位」并点击「确认插入」后,编辑器中没有出现 `class="image-placeholder"` 的占位符框。经分析,原因是 `document.execCommand('insertHTML')` 对复杂嵌套 HTML`contenteditable="false"`)的自动修正/过滤行为不可靠。
### 需求 2重构默认报告模板顶部排版
根据用户提供的视觉参考图片,重写 `defaultContent.ts` 顶部排版:
- **抬头**:左侧 Logo65×65 静态占位),右侧 14 号字体的「西 安 交 通 大 学 第 一 附 属 医 院」(带下划线),下方 16 号字体「手术记录」。
- **基本信息栏**11 号字体、不加粗、带贯穿下划线的一行:姓名、性别、年龄、科别、床号、住院号。
- **诊断/手术信息**12 号字体、加粗的单行:手术日期、术前诊断、术中诊断、手术名称。
- **双列信息**12 号字体、不加粗、两项一行:手术开始/终止时间、手术者/助手、麻醉师/麻醉方式。
- **手术步骤标题**12 号字体、加粗的「手术步骤、术中出现的情况及处理:」。
### 需求 3修复顶部 Logo 占位符删除按钮无法点击
当前默认模板中 65px×65px 的 Logo 占位符右上角的「×」删除按钮无法点击。原因是占位符缺少 `position: relative`,导致绝对定位的删除按钮点击区域溢出或被遮挡。需保留其「静态图片占位 (`data-mode="manual"`)」逻辑。
## 涉及文件
- `src/pages/TemplateManage.tsx`(需求 1修复 insertImage 插入方式)
- `src/utils/defaultContent.ts`(需求 2、3重构模板排版 + Logo 修复)
## 需求影响范围
- 模板管理页面的图片占位符插入功能
- 新建报告时的默认模板视觉效果
- 打印输出时的顶部排版