# 实现方案 — 2026-04-18-00-02-08
## 根因分析
### 问题1:拖拽插入后边框不消失
- `fillPlaceholderSrc`(点击上传路径)设置了 `border='none'` 和 `background='transparent'`。
- `fillPlaceholder`(拖拽路径)遗漏了这两行样式清除,导致拖拽后虚线框和灰色背景仍然可见。
- 同时 `fillPlaceholder` 中图片 style 缺少 `max-height:100%;object-fit:contain;`,图片可能溢出占位符。
### 问题2:prompt 弹窗体验差 + 自动帧插入无区分
- `insertImage` 使用浏览器原生 `prompt` 询问宽高,交互体验不佳。
- 所有 `.image-placeholder` 一视同仁,`autoCaptureFrames` 会自动填入任意空占位符。Logo、签名等位置不应被手术关键帧污染。
- 没有机制区分"接受关键帧"和"不接受关键帧"的占位符。
### 问题3:insertTable 使用 prompt
- 与 insertImage 同理,原生 `prompt` 弹窗用户体验差,应替换为与项目风格一致的自定义 Modal。
## 修改文件清单
| 文件 | 修改内容 |
|------|---------|
| `src/pages/ReportEditor.tsx` | ① fillPlaceholder 补齐样式清除和图片约束;② insertImage 改为 placeholderModal;③ insertTable 改为 tableModal;④ autoCaptureFrames/insertFrameToPlaceholder 选择器增加 `:not([data-mode="manual"])`;⑤ handleDrop 拦截 manual 模式;⑥ JSX 底部新增 2 个 Modal |
| `src/pages/TemplateManage.tsx` | ① insertImage 改为 placeholderModal;② insertTable 改为 tableModal;③ JSX 底部新增 2 个 Modal |
## 具体代码变更
### 1. ReportEditor.tsx
#### 1.1 fillPlaceholder 修复
```ts
const fillPlaceholder = (placeholder: HTMLElement, frame: CapturedFrame) => {
placeholder.innerHTML = `
×
`;
placeholder.classList.add('has-image');
placeholder.style.border = 'none';
placeholder.style.background = 'transparent';
if (editorRef.current) contentRef.current = editorRef.current.innerHTML;
saveDraftToStorage();
};
```
#### 1.2 新增状态
```ts
const [placeholderModal, setPlaceholderModal] = useState({
isOpen: false, width: '200', height: '200', mode: 'frame' as 'frame' | 'manual'
});
const [tableModal, setTableModal] = useState({
isOpen: false, rows: '2', cols: '3'
});
```
#### 1.3 insertImage 改为打开 Modal
```ts
const insertImage = () => {
editorRef.current?.focus();
setPlaceholderModal({ isOpen: true, width: '200', height: '200', mode: 'frame' });
};
```
#### 1.4 insertTable 改为打开 Modal
```ts
const insertTable = () => {
editorRef.current?.focus();
setTableModal({ isOpen: true, rows: '2', cols: '3' });
};
```
#### 1.5 autoCaptureFrames 中选择器修改
将 `setTimeout` 回调内的:
```ts
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image)') as HTMLElement | null;
```
改为:
```ts
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image):not([data-mode="manual"])') as HTMLElement | null;
```
#### 1.6 insertFrameToPlaceholder 选择器修改
```ts
const emptyPlaceholder = editorRef.current.querySelector('.image-placeholder:not(.has-image):not([data-mode="manual"])') as HTMLElement | null;
```
#### 1.7 handleDrop 拦截 manual 模式
```ts
const handleDrop = (e: React.DragEvent, placeholder: HTMLElement) => {
e.preventDefault();
if (placeholder.getAttribute('data-mode') === 'manual') {
alert('此处为静态图片占位符,仅支持点击插入(如Logo/签名),不支持拖入关键帧');
return;
}
const frameId = e.dataTransfer.getData('frameId');
const frame = capturedFrames.find(f => f.id.toString() === frameId);
if (frame) {
fillPlaceholder(placeholder, frame);
}
};
```
#### 1.8 JSX 底部新增 Modal
**Placeholder Insert Modal**(在 `` 关闭之前,与现有 `imagePickerOpen` Modal 并列):
```tsx
{placeholderModal.isOpen && (
插入图片占位符
)}
```
**Table Insert Modal**:
```tsx
{tableModal.isOpen && (
插入表格
)}
```
### 2. TemplateManage.tsx
结构与 ReportEditor.tsx 类似,但 `insertImage` 的 Modal 中也需要表格检测逻辑(已在上一轮修改中实现)。
#### 2.1 新增状态
```ts
const [placeholderModal, setPlaceholderModal] = useState({
isOpen: false, width: '200', height: '200', mode: 'frame' as 'frame' | 'manual'
});
const [tableModal, setTableModal] = useState({
isOpen: false, rows: '2', cols: '3'
});
```
#### 2.2 insertImage 改为打开 Modal
```ts
const insertImage = () => {
editorRef.current?.focus();
restoreSelection();
setPlaceholderModal({ isOpen: true, width: '200', height: '200', mode: 'frame' });
};
```
#### 2.3 insertTable 改为打开 Modal
```ts
const insertTable = () => {
editorRef.current?.focus();
restoreSelection();
pushHistory();
setTableModal({ isOpen: true, rows: '2', cols: '3' });
};
```
#### 2.4 JSX 底部新增 Modal
与 ReportEditor.tsx 的 Modal 结构一致。TemplateManage.tsx 的 `insertImage` Modal 中,确认按钮需要执行表格检测(沿用上一轮修改的逻辑),然后调用 `execCmd('insertHTML', html)` 和 `pushHistory()`。
## 风险点与应对措施
| 风险 | 应对措施 |
|------|---------|
| `data-mode="manual"` 的选择器 `:not([data-mode="manual"])` 可能不兼容旧浏览器 | 项目使用 Chrome/Edge,完全支持属性选择器 |
| 新增 Modal 与现有 `imagePickerOpen` Modal 的 z-index 冲突 | 两者都使用 `z-50`,在同一时刻不会同时打开 |
| TemplateManage.tsx 的 insertImage 中 pushHistory() 调用位置 | 确认按钮中在 `execCmd` 之前调用 `pushHistory()` |
| 表格内的 insertImage(上一轮修改)与本次 Modal 的冲突 | 确认按钮中保留表格检测逻辑,在表格内时不使用 Modal 中的宽高值 |
## 回滚策略
- 删除新增的状态和 Modal JSX,恢复 `insertImage` 和 `insertTable` 中的 `prompt` 弹窗逻辑。
- 恢复 `fillPlaceholder` 到修改前状态。
- 恢复 `autoCaptureFrames`、`insertFrameToPlaceholder`、`handleDrop` 中的选择器和拦截逻辑。