Files
Mdeical_Sur_Report/工程分析/实现方案-2026-04-17-10-21-18.md
admin db5df13a05 feat: smart field uniqueness, delete button, bulk export in report manage
- TemplateManage: add uniqueness check for smart fields to prevent duplicate inserts
- Add red circular delete button to smart-field-wrapper (visible on hover via CSS)
- Enhance keydown handler to delete smart fields at block-level boundaries
- Update defaultContent.ts smartField() to include delete-btn
- ReportManage: add per-row checkboxes, select-all, bulk delete
- Add single-report export modal (PDF via printDocument, JSON via Blob)
- Add bulk export actions for PDF and JSON
- Update experience record (#16)
2026-04-17 10:32:07 +08:00

139 lines
5.4 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-10-21-18
## 一、修改文件清单
1. `src/pages/TemplateManage.tsx` — 字段唯一性校验 + 删除按钮 + 键盘删除增强
2. `src/utils/defaultContent.ts``smartField()` 增加删除按钮 HTML
3. `src/index.css` — 智能字段删除按钮样式
4. `src/pages/ReportManage.tsx` — 复选框、批量操作栏、导出功能
## 二、详细改动
### 2.1 `src/pages/TemplateManage.tsx`
#### A. `insertSmartField` 增加唯一性校验
```ts
const insertSmartField = (field: FormField) => {
editorRef.current?.focus();
if (editorRef.current?.querySelector(`[data-bind="${field.key}"]`)) {
alert(`字段 "${field.label}" 已存在,请勿重复插入。`);
return;
}
// ... 原有 insertHTML 逻辑,同时给 wrapper 增加 delete-btn
};
```
#### B. 给 `smart-field-wrapper` 增加删除按钮
插入的 HTML 改为:
```html
<span class="smart-field-wrapper" contenteditable="false" style="white-space:nowrap;">
<span class="delete-btn" contenteditable="false">×</span>
<span class="field-value" data-bind="..." ...> </span>
</span>
```
#### C. 点击删除按钮事件(复用或扩展已有的 `handleEditorClick` capture 事件)
在已有的 `handleEditorClick` 中,除了处理 `.image-placeholder`,再增加对 `.delete-btn``.smart-field-wrapper` 内的判断:
```ts
const wrapper = targetEl.closest('.smart-field-wrapper') as HTMLElement | null;
if (targetEl.closest('.delete-btn') && wrapper) {
e.preventDefault();
e.stopPropagation();
wrapper.remove();
// 同步保存模板内容到 localStorage
return;
}
```
#### D. 增强 `keydown` 删除逻辑
当前逻辑只处理 "光标在文本节点内且 offset 为 0/末尾" 的情况。需要额外处理:
- 光标直接在字段后面(`range.startContainer``<p>``range.startOffset` 指向字段节点位置)时按 Backspace。
- 光标直接在字段前面时按 Delete。
- 选区 collapsed 且紧邻字段的各种边界情况。
实现策略:在 `keydown` 中统一写一个 `findAdjacentSmartField(range, direction)` 辅助函数,先尝试从文本节点 sibling 找,若找不到则从父级块节点的 childNodes 中按 offset 找。
### 2.2 `src/utils/defaultContent.ts`
修改 `smartField(key)` 辅助函数,使其输出包含 `<span class="delete-btn" contenteditable="false">×</span>` 的单行 HTML。
### 2.3 `src/index.css`
新增 `.smart-field-wrapper .delete-btn` 的样式:
```css
.smart-field-wrapper .delete-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
margin-right: 2px;
background: #ef4444;
color: white;
border-radius: 50%;
font-size: 10px;
line-height: 1;
cursor: pointer;
user-select: none;
}
.smart-field-wrapper .delete-btn:hover {
background: #dc2626;
}
@media print {
.smart-field-wrapper .delete-btn {
display: none !important;
}
}
```
### 2.4 `src/pages/ReportManage.tsx`
#### A. 状态扩展
```ts
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [exportModalOpen, setExportModalOpen] = useState(false);
const [exportTarget, setExportTarget] = useState<Report | null>(null);
```
#### B. 复选框与全选逻辑
- 表头 `<th>` 增加 Checkbox状态为 `selectedIds.length === filteredReports.length && filteredReports.length > 0`
- 每行 `<td>` 最左侧增加 Checkboxchecked 状态为 `selectedIds.includes(report.id)`onChange 时切换选中状态。
-`selectedIds.length > 0`在搜索栏下方显示批量操作栏flex row包含
- `已选择 N 项` 文本
- **批量删除** 按钮(红色)
- **批量导出 PDF** 按钮
- **批量导出 JSON** 按钮
- **取消选择** 按钮
#### C. 单报告导出
- **PDF**:调用 `printDocument(report.content)`
- **JSON**:构建对象:
```ts
const exportData = {
meta: { id: report.id, title: report.title, createdAt: report.createdAt, updatedAt: report.updatedAt, author: report.author, authorName: report.authorName, status: report.status },
fields: { /* 所有 DEFAULT_FORM_FIELDS 的 key 对应的值 */ }
};
```
通过 `Blob` + `URL.createObjectURL` + `<a download>` 触发下载。
#### D. 批量导出
- **批量 PDF**`const mergedHTML = selectedReports.map(r => r.content).join('<div style="page-break-after: always;"></div>'); printDocument(mergedHTML);`
- **批量 JSON**`const exportData = selectedReports.map(r => ({ meta: ..., fields: ... }));` 同样通过 Blob 下载为 `reports_export_时间戳.json`。
#### E. 批量删除
```ts
const handleBulkDelete = () => {
if (!window.confirm(`确定要删除选中的 ${selectedIds.length} 份报告吗?`)) return;
const updated = reports.filter(r => !selectedIds.includes(r.id));
setReports(updated);
storage.set('reports', updated);
setSelectedIds([]);
};
```
## 三、风险与回滚
- **风险**:修改 `defaultContent.ts` 中的 `smartField` HTML 结构后,旧模板中已存在的 `smart-field-wrapper` 没有删除按钮。这属于正常行为(旧数据不 retroactive新建报告时的新模板会带删除按钮。
- **风险**`keydown` 删除逻辑改动较大,可能在不同浏览器下对边界 selection 的解析有差异,需要手工测试。
- **回滚**:如出现问题,可回退 `TemplateManage.tsx`、`defaultContent.ts`、`index.css`、`ReportManage.tsx` 的修改。