From 5fee3352c1bbd44fb30978b9bfe002d9e4b02112 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Fri, 17 Apr 2026 19:34:03 +0800 Subject: [PATCH] refactor: unify image-placeholder across editors and remove image field type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove surgeonSignature and hospitalLogo from DEFAULT_FORM_FIELDS. - Replace logo and signature in default template with inline image-placeholder spans. - Enhance insertImage() in both editors with prompt for max-width/height (px). - Abbreviate placeholder text to '插入图片' when width < 80px. - Force inline insertion using display:inline-flex + vertical-align:middle. - Port image-source picker modal from ReportEditor to TemplateManage. - Remove legacy triggerPlaceholderUpload direct upload logic. --- src/pages/ReportEditor.tsx | 24 ++-- src/pages/TemplateManage.tsx | 136 +++++++++++++------- src/types.ts | 2 - src/utils/defaultContent.ts | 8 +- 工程分析/实现方案-2026-04-17-19-26-17.md | 154 +++++++++++++++++++++++ 工程分析/测试方案-2026-04-17-19-26-17.md | 71 +++++++++++ 工程分析/经验记录.md | 32 +++++ 工程分析/需求分析-2026-04-17-19-26-17.md | 62 +++++++++ 8 files changed, 432 insertions(+), 57 deletions(-) create mode 100644 工程分析/实现方案-2026-04-17-19-26-17.md create mode 100644 工程分析/测试方案-2026-04-17-19-26-17.md create mode 100644 工程分析/需求分析-2026-04-17-19-26-17.md 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 = ` × - + `; placeholder.classList.add('has-image'); if (editorRef.current) contentRef.current = editorRef.current.innerHTML; @@ -422,13 +422,21 @@ export default function ReportEditor() { 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 = ` -
- × -

插入/点击放置图片

-
- `; + const html = `×${hintText}​`; execCmd('insertHTML', html); }; diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx index a86218f..789450d 100644 --- a/src/pages/TemplateManage.tsx +++ b/src/pages/TemplateManage.tsx @@ -24,7 +24,9 @@ export default function TemplateManage() { const [formFields, setFormFields] = useState([]); const [newFieldForm, setNewFieldForm] = useState({ label: '', category: '填空', type: 'text' as FieldType }); const [newFieldOptions, setNewFieldOptions] = useState(''); - const [expandedCategories, setExpandedCategories] = useState(['填空', '单选', '多选', '时间', '图片']); + const [expandedCategories, setExpandedCategories] = useState(['填空', '单选', '多选', '时间']); + const [imagePickerOpen, setImagePickerOpen] = useState(false); + const [imagePickerTarget, setImagePickerTarget] = useState(null); const [activeFieldKey, setActiveFieldKey] = useState(null); const [editingFieldKey, setEditingFieldKey] = useState(null); const [editFieldLabel, setEditFieldLabel] = useState(''); @@ -117,26 +119,13 @@ export default function TemplateManage() { return () => observer.disconnect(); }, [currentUser]); - const triggerPlaceholderUpload = (placeholder: HTMLElement) => { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (ev) => { - const file = (ev.target as HTMLInputElement).files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (event) => { - const src = event.target?.result as string; - placeholder.innerHTML = ` - × - - `; - placeholder.classList.add('has-image'); - }; - reader.readAsDataURL(file); - } - }; - input.click(); + const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => { + placeholder.innerHTML = ` + × + + `; + placeholder.classList.add('has-image'); + saveTemplateContent(); }; // Handle image placeholder and smart field delete interactions via click capture @@ -190,7 +179,7 @@ export default function TemplateManage() { placeholder.classList.remove('has-image'); 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 = `
×

插入/点击放置图片

​`; - } else { - html = ` ×​`; - } + const html = ` ×​`; const sel = window.getSelection(); if (sel && sel.rangeCount > 0) { @@ -502,13 +486,21 @@ export default function TemplateManage() { const insertImage = () => { editorRef.current?.focus(); restoreSelection(); + 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 = ` -
- × -

插入/点击放置图片

-
- `; + const html = `×${hintText}​`; pushHistory(); execCmd('insertHTML', html); }; @@ -798,7 +790,7 @@ export default function TemplateManage() {
{fieldLibTab === 'insert' && (
- {['填空', '单选', '多选', '时间', '图片'].map(cat => { + {['填空', '单选', '多选', '时间'].map(cat => { const catFields = formFields.filter(f => f.category === cat); if (catFields.length === 0) return null; return ( @@ -829,7 +821,7 @@ export default function TemplateManage() { {fieldLibTab === 'manage' && (
- {['填空', '单选', '多选', '时间', '图片'].map(cat => { + {['填空', '单选', '多选', '时间'].map(cat => { const catFields = formFields.filter(f => f.category === cat); if (catFields.length === 0) return null; const expanded = expandedCategories.includes(cat); @@ -868,7 +860,7 @@ export default function TemplateManage() { /> )}
- {['单选', '多选', '图片'].includes(field.category) && ( + {['单选', '多选'].includes(field.category) && ( 单选 -
{['单选', '多选'].includes(newFieldForm.category) && ( @@ -1054,6 +1043,67 @@ export default function TemplateManage() {
)} + + {imagePickerOpen && imagePickerTarget && ( +
+
+

选择图片来源

+
+ + +
+
系统素材
+
+ {imageAssets.map(asset => ( + + ))} + {imageAssets.length === 0 &&
暂无素材
} +
+
+
+
+ +
+
+
+ )} ); } diff --git a/src/types.ts b/src/types.ts index ffbc851..f38c014 100644 --- a/src/types.ts +++ b/src/types.ts @@ -137,6 +137,4 @@ export const DEFAULT_FORM_FIELDS: FormField[] = [ { key: 'pathologyCheck', label: '是否送病理检查', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: true, options: ['是', '否'] }, { key: 'frozenPathology', label: '冰冻病理结果', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: true, options: ['未见恶性', '待石蜡'] }, { key: 'isSigned', label: '手术者签名确认', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['已签字', '未签字'] }, - { key: 'surgeonSignature', label: '手术者签名', category: '图片', type: 'signature', visibleInForm: true, isSystemLocked: false }, - { key: 'hospitalLogo', label: '医院Logo', category: '图片', type: 'image', visibleInForm: true, isSystemLocked: true }, ]; diff --git a/src/utils/defaultContent.ts b/src/utils/defaultContent.ts index 41f7080..a9bf4c0 100644 --- a/src/utils/defaultContent.ts +++ b/src/utils/defaultContent.ts @@ -3,10 +3,10 @@ const smartField = (key: string) => `

-

+ × -

插入/点击放置图片

-
+ 插入图片 +

@@ -151,7 +151,7 @@ export const defaultReportContent = `

- 手术者签名:${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 = `×${hintText}​`; + + 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 = `×${hintText}​`; + 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. 插入图片占位符时无法自定义默认宽高,且使用 `
` 导致强制换行。 +3. 占位符框太小时"插入/点击放置图片"文字显示不全。 +4. 默认模板中签名和 Logo 的结构不统一(一个是 `smartField`,一个是 `div.image-placeholder`)。 +5. `template-manage` 点击图片占位符直接调起本地文件选择器,与 `report-editor` 的三选一弹窗行为不一致。 + +**B. 问题产生原因** +1. `DEFAULT_FORM_FIELDS` 仍包含 `surgeonSignature` 和 `hospitalLogo`。 +2. 两端编辑器的 `insertImage()` 使用块级 `
` 插入,未提供尺寸 prompt。 +3. 占位符提示文本固定为长文本,未根据容器宽度做缩写适配。 +4. `TemplateManage` 的 placeholder 点击事件直接调用 `triggerPlaceholderUpload()`,缺少与 `ReportEditor` 一致的弹窗组件。 + +**C. 解决问题方法** +1. **清理图片字段**:从 `DEFAULT_FORM_FIELDS` 和 `types.ts` 中移除 `surgeonSignature` 和 `hospitalLogo`;在 `TemplateManage.tsx` 的插入字段/字段管理/新增字段表单中彻底移除"图片"分类。 +2. **统一默认模板**:在 `defaultContent.ts` 中将 Logo 和签名均替换为 ``。 +3. **改造 insertImage()**:在 `TemplateManage.tsx` 和 `ReportEditor.tsx` 中,插入前通过 `prompt` 获取最大宽度/高度(px),生成带 `max-width/max-height` 的 `` 行内占位符;提示文字中附加"正文一行文字高度约为 20 像素左右"。 +4. **文本自适应**:根据 prompt 输入的宽度决定提示文字:宽度 < 80px 时显示"插入图片",否则显示"插入/点击放置图片"。 +5. **统一弹窗行为**:将 `ReportEditor` 的 `imagePickerOpen` / `imagePickerTarget` / `fillPlaceholderSrc` 逻辑完整移植到 `TemplateManage`;删除旧的 `triggerPlaceholderUpload` 直接上传逻辑;两端点击图片占位符均弹出"本地上传 / 我的签名 / 系统素材"三选一弹窗。 +6. **优化填充样式**:`fillPlaceholderSrc` 中给 `` 增加 `max-width:100%; max-height:100%; object-fit:contain;`,避免撑破设置了固定尺寸的占位符。 + +**D. 经验与教训总结** +- 当从字段体系中彻底移除某一分类时,需要同时清理:`DEFAULT_FORM_FIELDS`、UI 渲染数组、新增表单 options、以及可能残留的分类判断逻辑(如编辑字段时显示 options 输入框的条件)。 +- 在 `contentEditable` 中实现"同行插入"必须使用行内元素(``)并显式设置 `display:inline-flex` + `vertical-align:middle`;块级 `
` 即使通过 CSS 改 display 也可能因浏览器 execCommand 修正导致换行。 +- 跨页面/跨编辑器的一致交互(如图片选择弹窗)应抽取为可复用逻辑或至少保持代码结构一致,避免用户在不同页面产生认知割裂。 +- `prompt` 虽不是最优雅的用户交互,但在工具栏快捷操作中是一种零依赖、快速落地的方案;若后续需要更复杂交互,可再替换为 Modal 组件。 diff --git a/工程分析/需求分析-2026-04-17-19-26-17.md b/工程分析/需求分析-2026-04-17-19-26-17.md new file mode 100644 index 0000000..50e8645 --- /dev/null +++ b/工程分析/需求分析-2026-04-17-19-26-17.md @@ -0,0 +1,62 @@ +# 需求分析 — 2026-04-17-19-26-17 + +## 用户反馈的 6 项需求 + +### 1. 移除插入字段中的"图片"类型字段 +在 `template-manage` 的"插入字段"Tab 中,"图片"分类(包含手术者签名、医院Logo)不再需要。需要: +- 从插入字段的分类列表中移除"图片"; +- 从 `DEFAULT_FORM_FIELDS` 中移除 `surgeonSignature` 和 `hospitalLogo`; +- 清理 `TemplateManage.tsx` 中与图片类型字段相关的分类渲染逻辑(如折叠分组、新增字段表单中的"图片"选项等)。 + +### 2. 细化"插入图片占位符"功能(支持自定义默认宽高) +在 `template-manage` 和 `report-editor` 中,点击工具栏的"插入图片占位符"按钮时: +- 弹出提示框让用户输入默认最大宽度(px)和最大高度(px),留空则表示无限制; +- 提示文字中附加说明:"一个文字高度约为 20 像素左右"; +- 将用户输入的宽高写入占位符的 `style` 属性中(`max-width` / `max-height`)。 + +### 3. 占位符文字自适应缩写 +`class="image-placeholder"` 中的提示文字,如果占位符框太小(通过宽度 < 80px 判断),则将 "插入/点击放置图片" 缩写为 "插入图片"。 + +### 4. 手术者签名、医院Logo 改用图片占位符 +在 `defaultContent.ts` 中: +- 将 `smartField('surgeonSignature')` 替换为行内 ``; +- 将顶部的 `hospitalLogo` 占位符替换为标准的 ``(保持居中)。 + +### 5. 插入图片占位符时保持同行 +当前使用 `
` 插入图片占位符会导致强制换行。需要: +- 将占位符的容器标签从 `
` 改为 ``; +- 设置 `display: inline-flex` + `vertical-align: middle`; +- 确保插入后与前后文字保持在同一行。 + +### 6. 统一两个编辑器的图片占位符点击行为 +`template-manage` 中点击图片占位符时目前直接调起本地文件选择器。需要将其改为与 `report-editor` 一致的行为: +- 弹出"图片来源选择器"弹窗; +- 支持"本地上传"、"我的签名"、"系统素材"三种来源; +- 将 `ReportEditor` 中的弹窗逻辑复用到 `TemplateManage` 中。 + +--- + +## 影响范围 + +- `src/types.ts`:移除 `surgeonSignature` 和 `hospitalLogo` 字段(或将其从字段配置中移除)。 +- `src/utils/defaultContent.ts`:签名和 Logo 位置替换为 `image-placeholder`。 +- `src/pages/TemplateManage.tsx`: + - 移除"图片"分类相关渲染和新增字段表单选项; + - 改造 `insertImage()` 支持 prompt 输入宽高; + - 新增图片来源选择弹窗状态和填充逻辑; + - 移除 `triggerPlaceholderUpload` 的直接调用。 +- `src/pages/ReportEditor.tsx`: + - 改造 `insertImage()` 支持 prompt 输入宽高; + - 保持现有的图片来源选择弹窗逻辑不变。 + +--- + +## 验收标准 + +1. `template-manage` 的"插入字段"和"字段管理"中不再出现"图片"分类和相关字段。 +2. `defaultContent.ts` 中签名和 Logo 均为 `image-placeholder`。 +3. 点击"插入图片占位符"时弹出宽高 prompt,正确应用 `max-width` / `max-height`。 +4. 图片占位符使用 `` + `display: inline-flex`,插入后与文字同行。 +5. 宽度 < 80px 的占位符显示"插入图片",否则显示"插入/点击放置图片"。 +6. `template-manage` 和 `report-editor` 点击图片占位符均弹出三选一图片来源弹窗。 +7. `npm run lint` 通过。