- Replace direct DOM remove() with Range+execCommand('delete') in TemplateManage click and keydown handlers to restore undo stack
- Append ​ zero-width space to smart-field-wrapper HTML in insertSmartField and defaultContent.ts to prevent unwanted line breaks
- Refactor ReportEditor surgeonSignature rendering to depend on isSigned field
- Add isSigned to DEFAULT_FORM_FIELDS (single_select: 已签字/未签字)
- Change surgeonSignature to visibleInForm=true, isSystemLocked=false
- Constrain signature image with max-width:120px, max-height:40px, object-fit:contain in CSS and print.ts
- Add weak-blocking signature validation prompts in saveReport('completed')
- Update experience record (#19)
158 lines
5.2 KiB
Markdown
158 lines
5.2 KiB
Markdown
# 实现方案 — 撤销栈修复、字段删除交互优化与签名字段闭环(2026-04-17-12-34-56)
|
||
|
||
## 一、修改文件清单
|
||
|
||
1. `src/pages/TemplateManage.tsx` — 删除逻辑改用 `execCommand('delete')`;插入 HTML 增加零宽空格防换行
|
||
2. `src/types.ts` — 修改 `surgeonSignature` 显隐属性;新增 `isSigned` 字段
|
||
3. `src/pages/ReportEditor.tsx` — 初始值增加 `isSigned`;签名同步逻辑重构;完成报告签名校验
|
||
4. `src/index.css` — 签名图片尺寸约束
|
||
5. `src/utils/print.ts` — 打印样式同步签名尺寸约束
|
||
|
||
## 二、详细改动
|
||
|
||
### 2.1 `src/pages/TemplateManage.tsx`
|
||
|
||
#### A. 点击红 X 删除改用 `execCommand('delete')`
|
||
```ts
|
||
if (smartField && targetEl.closest('.delete-btn')) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
const sel = window.getSelection();
|
||
const range = document.createRange();
|
||
range.selectNode(smartField);
|
||
sel?.removeAllRanges();
|
||
sel?.addRange(range);
|
||
document.execCommand('delete');
|
||
saveTemplateContent();
|
||
return;
|
||
}
|
||
```
|
||
|
||
#### B. 键盘 Backspace/Delete 改用 `execCommand('delete')`
|
||
在 `handleKeyDown` 中,当定位到 `smart-field-wrapper` 目标后:
|
||
```ts
|
||
if (target) {
|
||
e.preventDefault();
|
||
const sel = window.getSelection();
|
||
const range = document.createRange();
|
||
range.selectNode(target);
|
||
sel?.removeAllRanges();
|
||
sel?.addRange(range);
|
||
document.execCommand('delete');
|
||
saveTemplateContent();
|
||
}
|
||
```
|
||
|
||
#### C. 插入 HTML 防换行
|
||
在 `insertSmartField` 的 HTML 字符串末尾增加 `​`(零宽空格),作为行内锚点,防止浏览器将字段挤到新行:
|
||
```html
|
||
<span class="smart-field-wrapper" ...>...</span>​
|
||
```
|
||
|
||
### 2.2 `src/types.ts`
|
||
|
||
- 将 `surgeonSignature` 改为:
|
||
```ts
|
||
{ key: 'surgeonSignature', label: '手术者签名', category: '图片', type: 'signature', visibleInForm: true, isSystemLocked: false }
|
||
```
|
||
- 在 `DEFAULT_FORM_FIELDS` 末尾追加(放在 `surgeonSignature` 之前或之后均可):
|
||
```ts
|
||
{ key: 'isSigned', label: '手术者签名确认', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['已签字', '未签字'] },
|
||
```
|
||
|
||
### 2.3 `src/pages/ReportEditor.tsx`
|
||
|
||
#### A. 初始 `reportData` 增加 `isSigned`
|
||
```ts
|
||
const [reportData, setReportData] = useState<Partial<Report>>({
|
||
// ... 其他字段
|
||
isSigned: '未签字',
|
||
// ...
|
||
});
|
||
```
|
||
|
||
#### B. 签名同步逻辑重构
|
||
将 `surgeonSignature` 的特殊处理从 `useEffect` 移到更前面的位置,逻辑改为:
|
||
```ts
|
||
if (fieldKey === 'surgeonSignature') {
|
||
const isSigned = (reportData as any).isSigned === '已签字';
|
||
const signatureData = currentUser?.signature;
|
||
if (isSigned && signatureData) {
|
||
const imgHtml = `<img src="${signatureData}" class="report-signature-img" alt="签名" draggable="false" />`;
|
||
if (el.innerHTML !== imgHtml) {
|
||
el.innerHTML = imgHtml;
|
||
el.style.border = 'none';
|
||
el.style.backgroundColor = 'transparent';
|
||
}
|
||
} else {
|
||
const placeholder = isSigned ? '【请上传电子签】' : '【未签字】';
|
||
if (el.innerText !== placeholder) {
|
||
el.innerText = placeholder;
|
||
el.style.border = '';
|
||
el.style.backgroundColor = '';
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
```
|
||
|
||
#### C. 完成报告签名校验
|
||
在 `saveReport` 的 `status === 'completed'` 分支中,在现有患者信息校验之后追加:
|
||
```ts
|
||
const hasSignatureField = editorRef.current?.querySelector('[data-bind="surgeonSignature"]');
|
||
if (hasSignatureField) {
|
||
const isSigned = reportData.isSigned === '已签字';
|
||
const hasSignatureImage = !!currentUser?.signature;
|
||
if (!isSigned) {
|
||
const proceed = window.confirm('提示:模板中包含【手术者签名】字段,但您在基本信息中未选择"已签字"。是否继续完成报告?');
|
||
if (!proceed) return;
|
||
} else if (!hasSignatureImage) {
|
||
const proceed = window.confirm('提示:您选择了"已签字",但您的账号尚未上传电子签名图片。报告中将不显示签名图片,是否继续完成?');
|
||
if (!proceed) return;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.4 `src/index.css`
|
||
|
||
修改 `.report-signature-img`:
|
||
```css
|
||
.report-signature-img {
|
||
max-width: 120px;
|
||
max-height: 40px;
|
||
width: auto;
|
||
height: auto;
|
||
object-fit: contain;
|
||
vertical-align: middle;
|
||
display: inline-block;
|
||
}
|
||
```
|
||
|
||
在 `@media print` 中同步:
|
||
```css
|
||
@media print {
|
||
.report-signature-img {
|
||
max-width: 120px !important;
|
||
max-height: 40px !important;
|
||
width: auto !important;
|
||
height: auto !important;
|
||
object-fit: contain !important;
|
||
vertical-align: middle !important;
|
||
display: inline-block !important;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.5 `src/utils/print.ts`
|
||
|
||
在 iframe 的 `<style>` 中,`.smart-field-wrapper` 规则之后追加:
|
||
```css
|
||
.report-signature-img { max-width: 120px; max-height: 40px; width: auto; height: auto; object-fit: contain; vertical-align: middle; display: inline-block; }
|
||
```
|
||
|
||
## 三、风险与回滚
|
||
|
||
- **风险**:改用 `execCommand('delete')` 后,少数旧版浏览器可能行为不一致,但现代 Chromium/Edge 支持良好。
|
||
- **风险**:`​` 零宽空格在极少数场景下可能导致光标异常,但其为无形字符,影响极小。
|
||
- **回滚**:如出现问题,可回退上述 5 个文件的修改。
|