diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 6527c0e..505e4d5 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -306,7 +306,7 @@ export default function ReportEditor() { placeholder.classList.remove('has-image'); placeholder.innerHTML = ` × -
插入/点击放置图片
+ `; if (editorRef.current) contentRef.current = editorRef.current.innerHTML; saveDraftToStorage(); @@ -384,7 +384,7 @@ export default function ReportEditor() { const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => { placeholder.innerHTML = ` × -插入/点击放置图片
-插入/点击放置图片
+ `; } else { const range = document.createRange(); @@ -206,7 +195,8 @@ export default function TemplateManage() { if (!placeholder.classList.contains('has-image')) { e.preventDefault(); e.stopPropagation(); - triggerPlaceholderUpload(placeholder); + setImagePickerTarget(placeholder); + setImagePickerOpen(true); } }; @@ -358,19 +348,13 @@ export default function TemplateManage() { const insertSmartField = (field: FormField) => { editorRef.current?.focus(); restoreSelection(); - if (field.type !== 'image' && editorRef.current?.querySelector(`[data-bind="${field.key}"]`)) { + if (editorRef.current?.querySelector(`[data-bind="${field.key}"]`)) { alert(`字段 "${field.label}" 已存在,请勿重复插入。`); return; } pushHistory(); - let html = ''; - if (field.type === 'image') { - const id = 'ph_' + Date.now(); - html = `插入/点击放置图片
插入/点击放置图片
--
插入/点击放置图片
-- 手术者签名:${smartField('surgeonSignature')} + 手术者签名:×
diff --git a/工程分析/实现方案-2026-04-17-19-26-17.md b/工程分析/实现方案-2026-04-17-19-26-17.md new file mode 100644 index 0000000..8d5d84e --- /dev/null +++ b/工程分析/实现方案-2026-04-17-19-26-17.md @@ -0,0 +1,154 @@ +# 实现方案 — 2026-04-17-19-26-17 + +## 变更文件 + +1. `src/types.ts` +2. `src/utils/defaultContent.ts` +3. `src/pages/TemplateManage.tsx` +4. `src/pages/ReportEditor.tsx` + +--- + +## 一、types.ts 修改 + +从 `DEFAULT_FORM_FIELDS` 中移除以下两个字段: +- `surgeonSignature` +- `hospitalLogo` + +同时从 `FieldType` 中移除 `'image'`(因为图片不再作为可插入的字段类型,仅作为占位符存在)。若移除 `'image'` 会导致大量类型错误,也可保留类型但不在 UI 中暴露。为最小侵入,保留 `'image'` 类型但不再在 `DEFAULT_FORM_FIELDS` 中使用。 + +实际执行:删除 `DEFAULT_FORM_FIELDS` 中的最后两项(`surgeonSignature` 和 `hospitalLogo`)。 + +--- + +## 二、defaultContent.ts 修改 + +### 2.1 医院Logo +将原有的 `div.image-placeholder` 替换为 `span.image-placeholder`(保持居中): +```html + +
+ + × + + +
+``` + +### 2.2 手术者签名 +将 `手术者签名:${smartField('surgeonSignature')}` 替换为: +```html ++ 手术者签名:× +
+``` + +--- + +## 三、TemplateManage.tsx 修改 + +### 3.1 移除"图片"分类暴露 +- `expandedCategories` 初始值从 `['填空','单选','多选','时间','图片']` 改为 `['填空','单选','多选','时间']`。 +- "插入字段"Tab 的遍历数组从 `['填空','单选','多选','时间','图片']` 改为 `['填空','单选','多选','时间']`。 +- "字段管理"Tab 的遍历数组同样移除 `'图片'`。 +- 新增字段表单的 category select 中移除 ``。 +- type select 的条件渲染中移除图片相关的 option。 + +### 3.2 改造 insertImage() +将现有的 `insertImage()` 替换为: +```typescript +const insertImage = () => { + const widthStr = prompt('请输入占位符最大宽度 (px),留空无限制:\n(提示:正文一行文字高度约为 20 像素左右)', ''); + const heightStr = prompt('请输入占位符最大高度 (px),留空无限制:', ''); + const width = parseInt(widthStr || '0'); + const height = parseInt(heightStr || '0'); + + let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;'; + if (width > 0) styleStr += ` max-width:${width}px;`; + if (height > 0) styleStr += ` max-height:${height}px;`; + if (!width && !height) styleStr += ' padding:8px 16px;'; + + const showShortText = width > 0 && width < 80; + const hintText = showShortText ? '插入图片' : '插入/点击放置图片'; + + const id = 'ph_' + Date.now(); + const html = `×`; + + editorRef.current?.focus(); + restoreSelection(); + pushHistory(); + execCmd('insertHTML', html); +}; +``` + +### 3.3 统一图片源选择弹窗 +从 `ReportEditor.tsx` 复用以下逻辑到 `TemplateManage.tsx`: +- 新增状态:`imagePickerOpen`、`imagePickerTarget`、`imageAssets`(已存在)。 +- 新增 `fillPlaceholderSrc` 函数。 +- 修改 `handleEditorClick` 中的 placeholder 点击逻辑: + ```typescript + if (!placeholder.classList.contains('has-image')) { + e.preventDefault(); + e.stopPropagation(); + setImagePickerTarget(placeholder); + setImagePickerOpen(true); + } + ``` +- 删除原有的 `triggerPlaceholderUpload` 函数及其直接调用。 +- 在 JSX 底部(`isModalOpen` 弹窗之后)新增 `imagePickerOpen` 弹窗组件(与 ReportEditor 完全一致)。 + +### 3.4 清理删除后的重置逻辑 +当 placeholder 被删除(点击 × 后)时,重置为: +```html +× + +``` +同时保留原有内联样式(避免把 `inline-flex` 等样式清掉)。 + +--- + +## 四、ReportEditor.tsx 修改 + +### 4.1 改造 insertImage() +与 TemplateManage 保持一致: +```typescript +const insertImage = () => { + editorRef.current?.focus(); + const widthStr = prompt('请输入占位符最大宽度 (px),留空无限制:\n(提示:正文一行文字高度约为 20 像素左右)', ''); + const heightStr = prompt('请输入占位符最大高度 (px),留空无限制:', ''); + const width = parseInt(widthStr || '0'); + const height = parseInt(heightStr || '0'); + + let styleStr = 'display:inline-flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;'; + if (width > 0) styleStr += ` max-width:${width}px;`; + if (height > 0) styleStr += ` max-height:${height}px;`; + if (!width && !height) styleStr += ' padding:8px 16px;'; + + const showShortText = width > 0 && width < 80; + const hintText = showShortText ? '插入图片' : '插入/点击放置图片'; + + const id = 'ph_' + Date.now(); + const html = `×`; + execCmd('insertHTML', html); +}; +``` + +### 4.2 删除后重置逻辑 +在 `handleEditorClick` 中,placeholder 删除后重置的 HTML 改为使用 `` 结构并保留内联样式: +```typescript +placeholder.innerHTML = ` + × + +`; +``` + +### 4.3 fillPlaceholderSrc 保持兼容 +已有 `fillPlaceholderSrc` 可继续使用,但建议填充的图片增加 `max-width: 100%; max-height: 100%; object-fit: contain;`。 + +--- + +## 回滚策略 + +修改前最新提交为 `0c57409`。若失败可 `git reset --hard 0c57409`。 + +## 无新增 npm 依赖 diff --git a/工程分析/测试方案-2026-04-17-19-26-17.md b/工程分析/测试方案-2026-04-17-19-26-17.md new file mode 100644 index 0000000..8b5d801 --- /dev/null +++ b/工程分析/测试方案-2026-04-17-19-26-17.md @@ -0,0 +1,71 @@ +# 测试方案 — 2026-04-17-19-26-17 + +## 测试目标 + +验证 6 项需求全部正确实现,且不破坏现有编辑、保存、打印功能。 + +--- + +## 测试步骤 + +### 1. 编译检查 +```bash +npm run lint +``` +- **预期**:`tsc --noEmit` 通过,0 errors。 + +--- + +### 2. 字段体系清理(需求 1) +1. 进入 `/template-manage`。 +2. 在"插入字段"Tab 中,确认分类列表只有"填空、单选、多选、时间",**没有"图片"**。 +3. 在"字段管理"Tab 中,确认同样没有"图片"分组。 +4. 在"字段管理 → 新增字段"中,确认 category select 没有"图片"选项,type select 也不会出现"图片"。 + +--- + +### 3. 默认模板占位符替换(需求 4) +1. 重新加载默认模板(或清空 localStorage 后重新登录)。 +2. 确认模板顶部 Logo 处显示为一个虚线框占位符(而非直接显示医院 Logo 图片)。 +3. 确认"手术者签名:"后方显示为一个虚线框占位符,而非 `smart-field-wrapper` 文本框。 + +--- + +### 4. 插入图片占位符同行与尺寸设置(需求 2、5) +1. 在 `/template-manage` 编辑器中,将光标放在一段文字中间,点击工具栏"插入图片占位符"。 +2. 在 prompt 中输入宽度 `120`,高度 `60`。 +3. 确认占位符插入后与前后文字**保持在同一行**,没有换行。 +4. 使用浏览器 DevTools 检查该占位符,确认 `style` 中包含 `display:inline-flex` 和 `max-width:120px; max-height:60px;`。 +5. 在 `/report-editor` 中重复上述操作,确认行为一致。 +6. 测试留空宽高:确认插入的占位符没有 `max-width/max-height`,但有默认的 `padding: 8px 16px;`。 + +--- + +### 5. 占位符文字自适应(需求 3) +1. 插入一个宽度为 `60px` 的图片占位符。 +2. 确认占位符内显示的文字是**"插入图片"**(而非"插入/点击放置图片")。 +3. 插入一个宽度为 `120px` 的占位符,确认显示"插入/点击放置图片"。 + +--- + +### 6. 图片来源选择弹窗统一(需求 6) +1. 在 `/template-manage` 中,点击任意无图片的 `image-placeholder`。 +2. 确认弹出"选择图片来源"弹窗,包含"本地上传"、"我的签名"、"系统素材"三个选项。 +3. 选择"系统素材"中的医院 Logo,确认占位符被替换为 Logo 图片。 +4. 在 `/report-editor` 中点击占位符,确认弹窗行为与 `/template-manage` 完全一致。 +5. 测试弹窗中的"取消"按钮,确认点击后弹窗关闭且占位符未被修改。 + +--- + +### 7. 回归测试 +1. **保存模板**:修改模板后点击保存,刷新页面确认内容不丢失。 +2. **保存报告**:在 `/report-editor` 中填写表单并保存草稿,确认内容持久化。 +3. **打印预览**:确认图片占位符(已填充和未填充)在打印预览中显示正常。 +4. **撤销重做**:插入占位符后按 `Ctrl+Z`,确认占位符被正确撤销。 +5. **拖拽/自动插入关键帧**:确认 `/report-editor` 中的视频关键帧仍能正常插入到图片占位符中。 + +--- + +## 判定标准 + +全部测试通过后方可认为任务完成。若任何测试失败,需回滚并重新分析根因。 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 2814136..fb1b001 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -610,3 +610,35 @@ ange.insertNode(fragment) 精确插入到 Range 位置; - 图片字段与普通文本字段的 DOM 结构差异大,插入逻辑需要按 type 分支。 - 编辑器与侧边栏联动建议使用 `scrollIntoView` + 临时 CSS 类,避免复杂的状态同步。 - 新增 localStorage key 时应提供合理的默认值或降级处理。 + + +--- + +## 记录 23:图片占位符体系重构与双端统一 + +**A. 具体问题** +1. `template-manage` 的"插入字段"中仍存在"图片"分类(手术者签名、医院Logo),用户认为不再需要。 +2. 插入图片占位符时无法自定义默认宽高,且使用 `