- Add signature?: string to User type and 'signature' to FieldType - Add surgeonSignature field to DEFAULT_FORM_FIELDS (category: 图片) - UserManage: add canvas-based image compression (max 500px) and signature upload UI - TemplateManage: add hover highlight on field buttons via direct DOM style manipulation - TemplateManage: add '图片' category to field library for surgeonSignature insertion - ReportEditor: auto-fill surgeonSignature with currentUser.signature image or placeholder text - index.css & print.ts: add .report-signature-img styling (height 2.4em, vertical-align middle) - Update experience record (#18)
162 lines
5.4 KiB
Markdown
162 lines
5.4 KiB
Markdown
# 实现方案 — 字段悬浮高亮、电子签上传与手术者签名联动(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<string> => {
|
||
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"`),触发隐藏的 `<input type="file" accept="image/*">`。
|
||
- "清除签名" 按钮(有值时显示)。
|
||
- `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 = `<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 {
|
||
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 的 `<style>` 标签内,`.smart-field-wrapper` 规则之后追加:
|
||
```css
|
||
.report-signature-img {
|
||
height: 2.4em;
|
||
width: auto;
|
||
vertical-align: middle;
|
||
display: inline-block;
|
||
margin: -0.3em 0;
|
||
}
|
||
```
|
||
|
||
## 三、风险与回滚
|
||
|
||
- **风险**:`localStorage` 容量有限,压缩后的签名图片通常在 10~50KB,单用户存储安全。
|
||
- **风险**:旧用户的 `User` 对象没有 `signature` 字段,读取时为 `undefined`,代码中已通过可选链和默认值处理。
|
||
- **回滚**:如出现问题,可回退 6 个文件的修改。
|