# 实现方案 — 字段悬浮高亮、电子签上传与手术者签名联动(2026-04-17-11-34-24) ## 一、修改文件清单 1. `src/types.ts` — 扩展 `User` / `FieldType` / `DEFAULT_FORM_FIELDS` 2. `src/pages/UserManage.tsx` — 电子签上传组件 + 前端压缩逻辑 3. `src/pages/TemplateManage.tsx` — 悬浮高亮 + 图片分类 + 手术者签名插入 4. `src/pages/ReportEditor.tsx` — `surgeonSignature` 特殊同步逻辑 5. `src/index.css` — 签名图片排版样式 + 打印样式 6. `src/utils/print.ts` — 打印样式中增加签名图片规则 ## 二、详细改动 ### 2.1 `src/types.ts` - `User` 接口追加 `signature?: string`。 - `FieldType` 扩展为 `'text' | 'single_select' | 'multi_select' | 'time' | 'date' | 'signature'`。 - `DEFAULT_FORM_FIELDS` 末尾追加: ```ts { key: 'surgeonSignature', label: '手术者签名', category: '图片', type: 'signature', visibleInForm: false, isSystemLocked: true } ``` ### 2.2 `src/pages/UserManage.tsx` #### A. 前端压缩工具函数 ```ts const compressImage = (file: File, maxSize: number = 500): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (e) => { const img = new Image(); img.src = e.target?.result as string; img.onload = () => { const canvas = document.createElement('canvas'); let { width, height } = img; if (width > height && width > maxSize) { height = Math.round((height * maxSize) / width); width = maxSize; } else if (height > maxSize) { width = Math.round((width * maxSize) / height); height = maxSize; } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (ctx) { ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, width, height); ctx.drawImage(img, 0, 0, width, height); } resolve(canvas.toDataURL('image/jpeg', 0.8)); }; img.onerror = reject; }; reader.onerror = reject; }); }; ``` #### B. 上传组件与保存逻辑 - 在模态框表单中("状态"选择器下方或底部按钮上方)增加一个区块: - 标签:"电子签名" - 若 `formData.signature` 有值,显示压缩后的预览图(高度限制 64px)。 - "上传签名" 按钮(`type="button"`),触发隐藏的 ``。 - "清除签名" 按钮(有值时显示)。 - `handleSubmit` 中保存 `signature` 字段到用户对象。 - 编辑当前登录用户时,同步更新 `storage.set('currentUser', currentCached)`,确保 ReportEditor 能立即读取到最新签名。 ### 2.3 `src/pages/TemplateManage.tsx` #### A. 悬浮高亮 在字段库按钮上增加 `onMouseEnter` 和 `onMouseLeave`: ```ts const highlightField = (key: string, active: boolean) => { if (!editorRef.current) return; const el = editorRef.current.querySelector(`[data-bind="${key}"]`) as HTMLElement | null; if (!el) return; if (active) { el.style.transition = 'all 0.2s'; el.style.boxShadow = '0 0 0 2px #3b82f6'; el.style.backgroundColor = '#e0f2fe'; } else { el.style.boxShadow = ''; el.style.backgroundColor = ''; } }; ``` #### B. 图片分类与手术者签名 - 插入字段分类数组从 `['填空', '单选', '多选', '时间']` 改为 `['填空', '单选', '多选', '时间', '图片']`。 - `surgeonSignature` 字段会自动出现在"图片"分类下,按钮点击逻辑复用 `insertSmartField`(已支持唯一性校验)。 ### 2.4 `src/pages/ReportEditor.tsx` 在"Sync form state -> rich text field values"的 `useEffect` 中,对 `fieldKey === 'surgeonSignature'` 做特殊分支: ```ts if (fieldKey === 'surgeonSignature') { const signatureData = currentUser?.signature; if (signatureData) { const imgHtml = `签名`; if (el.innerHTML !== imgHtml) { el.innerHTML = imgHtml; el.style.border = 'none'; el.style.backgroundColor = 'transparent'; } } else { if (el.innerText !== '【请上传电子签】') { el.innerText = '【请上传电子签】'; el.style.border = ''; el.style.backgroundColor = ''; } } return; // 跳过常规文本同步 } ``` ### 2.5 `src/index.css` 增加签名图片样式: ```css .report-signature-img { height: 2.4em; width: auto; vertical-align: middle; display: inline-block; margin: -0.3em 0; } ``` 在 `@media print` 中同步增加: ```css @media print { .report-signature-img { height: 2.4em !important; width: auto !important; vertical-align: middle !important; display: inline-block !important; margin: -0.3em 0 !important; } } ``` ### 2.6 `src/utils/print.ts` 在打印 iframe 的 `