Files
Mdeical_Sur_Report/工程分析/实现方案-2026-04-17-18-38-47.md
admin 0c57409c59 feat: TemplateManage field system upgrade and bidirectional navigation
- Fix new field type linkage (remove text option under single/multi/image).
- Add system fields: pre/post-op diagnosis, pathology checks, etc.
- Replace placeholder text in default template with smart fields.
- Accordion grouping and inline option editing in field management.
- Add image field type, asset library with logo preloading.
- Image source picker modal in ReportEditor (local/signature/asset).
- Editor-to-sidebar highlight and scroll navigation on smart field click.
2026-04-17 18:54:10 +08:00

239 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实现方案 — 2026-04-17-18-38-47
## 变更文件
1. `src/types.ts`
2. `src/utils/defaultContent.ts`
3. `src/pages/TemplateManage.tsx`
4. `src/pages/ReportEditor.tsx`
5. `src/index.css`
---
## 一、types.ts 修改
### 1.1 扩展 FieldType
```typescript
export type FieldType = 'text' | 'single_select' | 'multi_select' | 'time' | 'date' | 'signature' | 'image';
```
### 1.2 更新 DEFAULT_FORM_FIELDS
在现有字段基础上追加/修改:
- `preoperativeDiagnosis`(术前诊断,单选)
- `postoperativeDiagnosis`(术后诊断,单选)
- `postOpCondition`(手术后情况,单选,默认选项含"患者麻醉恢复后安返病房"
- `pathologyCheck`(是否送病理检查,单选,选项["是","否"]
- `frozenPathology`(冰冻病理结果,单选)
- `specimenDescription`(切除标本描述,单选)
- `hospitalLogo`医院Logo图片type: 'image',对应默认模板顶部 logo
所有新增诊断类字段默认 `visibleInForm: true, isSystemLocked: true`
---
## 二、defaultContent.ts 修改
将模板 HTML 中的静态占位文本替换为 `smartField(...)`
```javascript
// 术前诊断
<strong>术前诊断</strong>${smartField('preoperativeDiagnosis')}
// 术后诊断
<strong>术后诊断</strong>${smartField('postoperativeDiagnosis')}
// 手术后情况
<strong>手术后情况</strong>${smartField('postOpCondition')}
// 切除标本描述
<strong>切除标本描述</strong>${smartField('specimenDescription')}
// 是否送病理检查
<strong>是否送病理检查</strong>${smartField('pathologyCheck')}
// 冰冻病理结果
<strong>冰冻病理结果</strong>${smartField('frozenPathology')}
// 手术者签名
手术者签名${smartField('surgeonSignature')}
// 医院 Logo 替换为图片字段占位符(使用 image-placeholder 结构但带 data-bind
// 保留原有居中样式
```
Logo 部分不再硬编码 `<img src="/logo_square.png">`,改为可管理的图片占位符:
```html
<div class="image-placeholder" data-placeholder="true" data-bind="hospitalLogo" contenteditable="false">
<span class="delete-btn" contenteditable="false">×</span>
<p class="placeholder-text" style="color: #94a3b8; font-size: 11px; margin: 0; pointer-events: none;">插入/点击放置图片</p>
</div>
```
---
## 三、TemplateManage.tsx 修改
### 3.1 新增字段表单修复(需求 1
在 category `onChange` 中:
- 选择"单选" → `type` 强制设为 `single_select`
- 选择"多选" → `type` 强制设为 `multi_select`
- 选择"图片" → `type` 强制设为 `image`
在 type select 的 options 渲染中,移除单选/多选/图片下的"文本" option
```tsx
<option value="text"></option>
{newFieldForm.category === '单选' && <option value="single_select"></option>}
{newFieldForm.category === '多选' && <option value="multi_select"></option>}
{newFieldForm.category === '时间' && <><option value="date"></option><option value="time"></option></>}
{newFieldForm.category === '图片' && <option value="image"></option>}
```
### 3.2 字段管理折叠分组(需求 5
新增状态:
```tsx
const [expandedCategories, setExpandedCategories] = useState<string[]>(['填空','单选','多选','时间','图片']);
```
将字段管理列表改为按 category 分组渲染。每组一个可点击标题栏,点击时 toggle 该 category 在 `expandedCategories` 中的存在性。展开的组内渲染对应字段列表。
### 3.3 字段管理点击编辑选项(需求 3
新增状态:
```tsx
const [editingFieldKey, setEditingFieldKey] = useState<string | null>(null);
const [editFieldOptions, setEditFieldOptions] = useState('');
const [editFieldLabel, setEditFieldLabel] = useState('');
```
在字段分组列表中,每个字段行增加 `onClick`
```tsx
onClick={() => { setEditingFieldKey(field.key); setEditFieldOptions((field.options || []).join(', ')); setEditFieldLabel(field.label); }}
```
`editingFieldKey === field.key` 时,将该行替换为编辑表单:
- 显示字段名 input仅非系统锁定字段可改 label系统字段只读展示
- 选项 input逗号分隔
- "保存"/"取消"按钮。
保存函数:
```tsx
const saveFieldEdit = (key: string) => {
const updated = formFields.map(f => {
if (f.key !== key) return f;
const next = { ...f, options: ['单选','多选','图片'].includes(f.category) ? editFieldOptions.split(/[,]/).map(s => s.trim()).filter(Boolean) : f.options };
if (!f.isSystemLocked) next.label = editFieldLabel.trim() || f.label;
return next;
});
setFormFields(updated);
storage.set('formFieldsConfig', updated);
setEditingFieldKey(null);
};
```
### 3.4 编辑器点击联动侧边栏(需求 6、7
在现有的 `handleEditorClick` 事件监听中(已存在于 `useEffect`),增加非 delete-btn 的 `smart-field-wrapper` 点击处理:
```typescript
const smartField = targetEl.closest('.smart-field-wrapper') as HTMLElement | null;
if (smartField) {
const valueSpan = smartField.querySelector('.field-value');
const fieldKey = valueSpan?.getAttribute('data-bind') || smartField.getAttribute('data-bind');
if (fieldKey) {
setActiveFieldKey(fieldKey);
const field = formFields.find(f => f.key === fieldKey);
if (field) {
// 展开对应分组
setExpandedCategories(prev => prev.includes(field.category) ? prev : [...prev, field.category]);
// 滚动到可视区域
setTimeout(() => {
const el = document.getElementById(`sidebar-field-${fieldKey}`);
el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 50);
}
}
}
```
新增状态 `const [activeFieldKey, setActiveFieldKey] = useState<string | null>(null);`
在"插入字段"Tab 的按钮上增加 `id={`sidebar-field-${field.key}`}` 和动态高亮类:
```tsx
className={... + (activeFieldKey === field.key ? ' ring-2 ring-accent bg-blue-50 border-accent' : '')}
```
在"字段管理"Tab 的字段卡片上同样增加 `id` 和高亮边框。
### 3.5 素材管理(需求 4 的一部分)
在"字段管理"Tab 底部新增"素材库"折叠面板(或放在图片分组下方)。
新增状态:
```tsx
const [imageAssets, setImageAssets] = useState<{id: string, name: string, dataUrl: string}[]>([]);
```
初始化时从 `storage.get('imageAssets', [])` 读取。若为空且存在 `/logo_square.png`,则通过 `fetch('/logo_square.png') -> blob -> FileReader` 将其转为 Base64 并作为默认素材 `hospital-logo` 存入。
提供本地上传按钮:选择图片后用 Canvas 压缩max 500px转 Base64追加到 `imageAssets` 并保存 `storage.set('imageAssets', ...)`
---
## 四、ReportEditor.tsx 修改
### 4.1 图片来源选择弹窗(需求 4
新增状态:
```tsx
const [imagePickerOpen, setImagePickerOpen] = useState(false);
const [imagePickerTarget, setImagePickerTarget] = useState<HTMLElement | null>(null);
const [imageAssets, setImageAssets] = useState<{id: string, name: string, dataUrl: string}[]>([]);
```
修改 `triggerPlaceholderUpload` 的调用逻辑:当点击无图片的 `image-placeholder` 时,不再直接 `input.click()`,而是:
```tsx
setImagePickerTarget(placeholder);
setImagePickerOpen(true);
```
弹窗 JSXModal包含三个 Tab 按钮:
- **本地上传**:内部隐藏 `<input type="file" accept="image/*">`,点击"选择文件"触发,读取后填充 placeholder。
- **我的签名**:若 `currentUser.signature` 存在,展示签名缩略图,点击后填充。
- **系统素材**:读取 `imageAssets` 列表,展示缩略图网格,点击后填充。
填充函数:
```tsx
const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => {
placeholder.innerHTML = `<span class="delete-btn" contenteditable="false">×</span><img src="${src}" style="max-width: 100%; height: auto; display: block; margin: 0 auto;" draggable="false">`;
placeholder.classList.add('has-image');
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
};
```
### 4.2 图片字段在 TemplateManage 中的插入
`TemplateManage.tsx``insertSmartField` 中,对 `type === 'image'` 的字段,不再插入 `span.smart-field-wrapper`,而是插入 `image-placeholder`
```tsx
if (field.type === 'image') {
const id = 'ph_' + Date.now();
const html = `<div id="${id}" class="image-placeholder" data-placeholder="true" data-bind="${field.key}" contenteditable="false"><span class="delete-btn" contenteditable="false">×</span><p class="placeholder-text" style="color: #94a3b8; font-size: 11px; margin: 0; pointer-events: none;">插入/点击放置图片</p></div>`;
// 同样的 Range.insertNode 逻辑插入 html
}
```
---
## 五、index.css 修改
新增/微调以下样式:
1. `.accordion-header`:字段管理分组标题样式(可复用现有按钮类)。
2. `.accordion-body`:分组内容过渡动画(可选)。
3. `.sidebar-field-active`:高亮边框/背景色。
4. 图片选择弹窗遮罩与内容卡片样式(可复用现有 Modal 样式)。
---
## 回滚策略
修改前 `git` 仓库已处于干净状态(最新提交 `b155dd4`)。若验证失败,可执行 `git reset --hard b155dd4` 回滚。
## 无新增 npm 依赖
所有改动均利用现有 React + Tailwind 能力完成。