diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 21b683f..90a452b 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -489,16 +489,24 @@ export default function ReportEditor() { }, []); const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => { + const mw = placeholder.style.maxWidth || placeholder.style.width || '200px'; + const mh = placeholder.style.maxHeight || placeholder.style.height || '200px'; placeholder.innerHTML = ` × - + `; placeholder.classList.add('has-image'); placeholder.style.border = 'none'; placeholder.style.background = 'transparent'; - placeholder.style.height = 'auto'; placeholder.style.width = 'auto'; + placeholder.style.height = 'auto'; placeholder.style.lineHeight = 'normal'; + placeholder.style.maxWidth = mw; + placeholder.style.maxHeight = mh; + placeholder.style.textAlign = 'left'; + placeholder.style.verticalAlign = 'top'; + placeholder.style.justifyContent = 'flex-start'; + placeholder.style.alignItems = 'flex-start'; if (editorRef.current) contentRef.current = editorRef.current.innerHTML; saveDraftToStorage(); }; @@ -664,14 +672,20 @@ export default function ReportEditor() { if (emptyPlaceholder) { emptyPlaceholder.innerHTML = ` × - + `; emptyPlaceholder.classList.add('has-image'); emptyPlaceholder.style.border = 'none'; emptyPlaceholder.style.background = 'transparent'; - emptyPlaceholder.style.height = 'auto'; emptyPlaceholder.style.width = 'auto'; + emptyPlaceholder.style.height = 'auto'; emptyPlaceholder.style.lineHeight = 'normal'; + emptyPlaceholder.style.maxWidth = emptyPlaceholder.style.maxWidth || emptyPlaceholder.style.width || '200px'; + emptyPlaceholder.style.maxHeight = emptyPlaceholder.style.maxHeight || emptyPlaceholder.style.height || '200px'; + emptyPlaceholder.style.textAlign = 'left'; + emptyPlaceholder.style.verticalAlign = 'top'; + emptyPlaceholder.style.justifyContent = 'flex-start'; + emptyPlaceholder.style.alignItems = 'flex-start'; contentRef.current = editorRef.current.innerHTML; saveDraftToStorage(); } @@ -696,16 +710,24 @@ export default function ReportEditor() { }; const fillPlaceholder = (placeholder: HTMLElement, frame: CapturedFrame) => { + const mw = placeholder.style.maxWidth || placeholder.style.width || '200px'; + const mh = placeholder.style.maxHeight || placeholder.style.height || '200px'; placeholder.innerHTML = ` × - + `; placeholder.classList.add('has-image'); placeholder.style.border = 'none'; placeholder.style.background = 'transparent'; - placeholder.style.height = 'auto'; placeholder.style.width = 'auto'; + placeholder.style.height = 'auto'; placeholder.style.lineHeight = 'normal'; + placeholder.style.maxWidth = mw; + placeholder.style.maxHeight = mh; + placeholder.style.textAlign = 'left'; + placeholder.style.verticalAlign = 'top'; + placeholder.style.justifyContent = 'flex-start'; + placeholder.style.alignItems = 'flex-start'; if (editorRef.current) contentRef.current = editorRef.current.innerHTML; saveDraftToStorage(); }; @@ -1798,45 +1820,43 @@ export default function ReportEditor() { onChange={handleVideoUpload} /> - {videos.length > 0 && ( -
-
- + {videos.map((v, i) => ( +
+
selectVideo(i)} + className="aspect-video bg-slate-900 rounded-lg flex items-center justify-center text-white" > -
+
selectVideo(i)} + className="text-[9px] font-bold text-text-main truncate mt-1.5 px-1" + >{v.name}
+ - {videos.map((v, i) => ( -
-
selectVideo(i)} - className="aspect-video bg-slate-900 rounded-lg flex items-center justify-center text-white" - > - -
-
selectVideo(i)} - className="text-[9px] font-bold text-text-main truncate mt-1.5 px-1" - >{v.name}
- -
- ))}
+ ))} +
- {currentVideoIndex !== -1 && ( -
+ {currentVideoIndex !== -1 && videos.length > 0 && ( +
)} -
- )}
)}
@@ -1982,7 +2000,7 @@ export default function ReportEditor() { html = `
×${hintText}
`; } else { let styleStr = 'display:inline-block;text-align:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;position:relative;'; - styleStr += `width:${w}px;height:${h}px;line-height:${h}px;`; + styleStr += `width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;line-height:${h}px;`; const showShortText = w > 0 && w < 80; const text = showShortText ? '插图' : hintText; html = `×${text}​`; diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx index bb61d03..37f7ba4 100644 --- a/src/pages/TemplateManage.tsx +++ b/src/pages/TemplateManage.tsx @@ -143,16 +143,24 @@ export default function TemplateManage() { }, [currentUser]); const fillPlaceholderSrc = (placeholder: HTMLElement, src: string) => { + const mw = placeholder.style.maxWidth || placeholder.style.width || '200px'; + const mh = placeholder.style.maxHeight || placeholder.style.height || '200px'; placeholder.innerHTML = ` × - + `; placeholder.classList.add('has-image'); placeholder.style.border = 'none'; placeholder.style.background = 'transparent'; - placeholder.style.height = 'auto'; placeholder.style.width = 'auto'; + placeholder.style.height = 'auto'; placeholder.style.lineHeight = 'normal'; + placeholder.style.maxWidth = mw; + placeholder.style.maxHeight = mh; + placeholder.style.textAlign = 'left'; + placeholder.style.verticalAlign = 'top'; + placeholder.style.justifyContent = 'flex-start'; + placeholder.style.alignItems = 'flex-start'; saveTemplateContent(); }; @@ -1359,7 +1367,7 @@ export default function TemplateManage() { html = `
×${hintText}
`; } else { let styleStr = 'display:inline-block;text-align:center;border:1px dashed #cbd5e1;background:#f8fafc;vertical-align:middle;margin:0 4px;cursor:pointer;position:relative;'; - styleStr += `width:${w}px;height:${h}px;line-height:${h}px;`; + styleStr += `width:${w}px;height:${h}px;max-width:${w}px;max-height:${h}px;line-height:${h}px;`; const showShortText = w > 0 && w < 80; const text = showShortText ? '插图' : hintText; html = `×${text}​`; diff --git a/工程分析/实现方案-2026-04-18-19-23-31.md b/工程分析/实现方案-2026-04-18-19-23-31.md new file mode 100644 index 0000000..18b3d42 --- /dev/null +++ b/工程分析/实现方案-2026-04-18-19-23-31.md @@ -0,0 +1,82 @@ +# 实现方案 —— 2026-04-18-19-23-31 + +## 方案目标 +修复视频分析模块空白问题,重构图片占位符的填充后尺寸逻辑。 + +## 需求 1:修复视频分析模块空白 + +### 修改文件 +`src/pages/ReportEditor.tsx` + +### 修改内容 +将「上传视频」按钮和视频缩略图列表从 `videos.length > 0` 条件内部移出,使其始终渲染。仅保留视频播放器和关键帧网格在 `currentVideoIndex !== -1 && videos.length > 0` 条件下渲染。 + +修改后结构: +```tsx +{activeTab === 'video' && ( +
+ + + {/* 始终可见:上传按钮 + 视频缩略图列表 */} +
+ + {videos.map(...)} +
+ + {/* 条件渲染:视频播放器和关键帧 */} + {currentVideoIndex !== -1 && videos.length > 0 && ( +
...
+ )} +
+)} +``` + +## 需求 2:图片占位符尺寸自适应 + +### 核心逻辑 +1. **插入占位符时**:在 `style` 中注入 `max-width` 和 `max-height`,与 `width`/`height` 相同,便于后续读取限制值。 +2. **填充图片时**: + - 读取占位符当前的 `max-width` / `max-height`(或回退到 `width` / `height`) + - 将这两个值赋给内部 `` 的 `max-width` / `max-height` + - 设置 `object-fit: contain; object-position: left top` + - 将占位符外壳的 `width`、`height`、`line-height` 设为 `auto` / `normal` + - 保留 `max-width`、`max-height` 作为硬限制 + - 设置 `text-align: left; vertical-align: top` + +### 修改文件及位置 +| 文件 | 函数/位置 | 修改内容 | +|------|-----------|----------| +| `src/pages/ReportEditor.tsx` | `fillPlaceholderSrc` | 填充后读取限制值,设置 img 和外壳样式 | +| `src/pages/ReportEditor.tsx` | `fillPlaceholder` | 同上 | +| `src/pages/ReportEditor.tsx` | `autoCaptureFrames` | 同上 | +| `src/pages/ReportEditor.tsx` | placeholderModal 确认插入 | style 中增加 `max-width` / `max-height` | +| `src/pages/TemplateManage.tsx` | `fillPlaceholderSrc` | 同上 | +| `src/pages/TemplateManage.tsx` | placeholderModal 确认插入 | style 中增加 `max-width` / `max-height` | + +### 样式值示例 +```ts +const mw = placeholder.style.maxWidth || placeholder.style.width || '200px'; +const mh = placeholder.style.maxHeight || placeholder.style.height || '200px'; + +placeholder.innerHTML = ` + × + +`; + +placeholder.style.width = 'auto'; +placeholder.style.height = 'auto'; +placeholder.style.maxWidth = mw; +placeholder.style.maxHeight = mh; +placeholder.style.lineHeight = 'normal'; +placeholder.style.textAlign = 'left'; +placeholder.style.verticalAlign = 'top'; +``` + +## 需求 3:Logo 框大小保持 65px × 65px +默认模板中 Logo 占位符的 `width:65px;height:65px` 保持不变。此需求通过不修改 Logo 占位符相关代码即可满足。 + +## 风险与注意事项 +1. 视频按钮移出条件渲染后,需确保 `videoInputRef` 的引用始终有效。 +2. 占位符 `width:auto` 后,在表格单元格(`td`)内的表现需要验证,确保不会超出单元格。 +3. `object-position: left top` 仅在 `object-fit: contain` 时生效。 +4. 需确保 `max-width` / `max-height` 在打印样式中不会被 `@media print` 规则覆盖。 diff --git a/工程分析/测试方案-2026-04-18-19-23-31.md b/工程分析/测试方案-2026-04-18-19-23-31.md new file mode 100644 index 0000000..a471edc --- /dev/null +++ b/工程分析/测试方案-2026-04-18-19-23-31.md @@ -0,0 +1,54 @@ +# 测试方案 —— 2026-04-18-19-23-31 + +## 测试目标 +验证视频分析模块空白修复和图片占位符自适应逻辑。 + +## 测试用例 + +### TC-1:视频分析模块无视频时显示上传按钮 +**前置条件**:新建报告,切换到「视频分析」Tab,尚未上传任何视频。 +**步骤**: +1. 点击「视频分析」Tab。 +**预期结果**: +- 面板显示「上传视频」按钮(缩小版,在水平滚动区域首位)。 +- 面板不显示视频播放器和关键帧区域。 +- 点击上传按钮可正常打开文件选择器。 + +### TC-2:视频分析模块有视频时正常显示 +**前置条件**:已上传至少一个视频。 +**步骤**: +1. 切换到「视频分析」Tab。 +**预期结果**: +- 上传按钮和视频缩略图列表均可见。 +- 选中视频后,播放器和关键帧区域正常显示。 + +### TC-3:图片占位符填充后尺寸自适应(小图片) +**前置条件**:模板中有 200×200 的图片占位符,准备一张 100×80 的小图片。 +**步骤**: +1. 将小图片插入占位符。 +**预期结果**: +- 占位符宽度收缩为约 100px,高度收缩为约 80px。 +- 图片靠左上方放置,无多余空白。 + +### TC-4:图片占位符填充后尺寸自适应(大图片) +**前置条件**:模板中有 200×200 的图片占位符,准备一张 800×600 的大图片。 +**步骤**: +1. 将大图片插入占位符。 +**预期结果**: +- 图片等比例缩小,最大不超过 200×200。 +- 占位符宽度收缩为缩小后的图片宽度(≤200px),高度同理。 +- 图片靠左上方放置。 + +### TC-5:Logo 占位符大小保持 65px × 65px +**前置条件**:默认模板已加载。 +**步骤**: +1. 检查顶部 Logo 占位符。 +**预期结果**:占位符尺寸为 65px × 65px,不受本次修改影响。 + +## 回归测试 +- 确保视频播放、关键帧摘取、拖拽插入功能正常。 +- 确保 `template-manage` 中的图片占位符同样支持尺寸自适应。 +- 确保打印样式正常,图片不会被截断。 + +## 测试通过标准 +所有用例均通过,无控制台报错,视频模块和图片占位符行为符合预期。 diff --git a/工程分析/需求分析-2026-04-18-19-23-31.md b/工程分析/需求分析-2026-04-18-19-23-31.md new file mode 100644 index 0000000..62eab4d --- /dev/null +++ b/工程分析/需求分析-2026-04-18-19-23-31.md @@ -0,0 +1,30 @@ +# 需求分析 —— 2026-04-18-19-23-31 + +## 需求来源 +用户在实际使用中发现两个问题,要求进行修复和优化。 + +## 需求概述 + +### 需求 1:修复视频分析模块空白问题 +在 `ReportEditor` 中,上一轮修改将「上传视频」按钮移入了 `videos.length > 0` 的条件渲染内部,导致当没有视频时,整个「视频分析」面板变为空白,用户无法上传第一个视频。 + +**预期行为**:无论是否有已上传视频,「上传视频」按钮和缩略图滚动列表都应始终可见。 + +### 需求 2:图片占位符尺寸自适应与等比例缩放限制 +当前图片占位符填充图片后,虽然高度变为 `auto`,但宽度仍保持预设值(如 200px),导致图片在占位符内居中显示,周围仍有大量空白。用户希望: +- 预设的宽高仅作为**最大限制**(`max-width` / `max-height`) +- 如果图片超出限制,则等比例缩小 +- 图片靠左上方放置(`object-position: left top`) +- 占位符自身的虚线框大小要**紧缩包围(shrink-wrap)**成图片实际缩放后的尺寸 + +### 需求 3:Logo 框大小保持 65px × 65px +默认模板中顶部医院 Logo 占位符的尺寸应保持 65px × 65px 不变。 + +## 涉及文件 +- `src/pages/ReportEditor.tsx`(需求 1、2) +- `src/pages/TemplateManage.tsx`(需求 2) + +## 需求影响范围 +- 视频分析面板的可见性逻辑 +- 图片占位符的填充后样式行为 +- 打印/预览时的图片尺寸表现