- 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)
5.4 KiB
5.4 KiB
实现方案 — 模板字段唯一性、删除交互与报告批量导出(2026-04-17-10-21-18)
一、修改文件清单
src/pages/TemplateManage.tsx— 字段唯一性校验 + 删除按钮 + 键盘删除增强src/utils/defaultContent.ts—smartField()增加删除按钮 HTMLsrc/index.css— 智能字段删除按钮样式src/pages/ReportManage.tsx— 复选框、批量操作栏、导出功能
二、详细改动
2.1 src/pages/TemplateManage.tsx
A. insertSmartField 增加唯一性校验
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 改为:
<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 内的判断:
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 的样式:
.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. 状态扩展
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>最左侧增加 Checkbox,checked 状态为selectedIds.includes(report.id),onChange 时切换选中状态。 - 当
selectedIds.length > 0时,在搜索栏下方显示批量操作栏(flex row),包含:已选择 N 项文本- 批量删除 按钮(红色)
- 批量导出 PDF 按钮
- 批量导出 JSON 按钮
- 取消选择 按钮
C. 单报告导出
- PDF:调用
printDocument(report.content)。 - JSON:构建对象:
通过
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. 批量删除
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中的smartFieldHTML 结构后,旧模板中已存在的smart-field-wrapper没有删除按钮。这属于正常行为(旧数据不 retroactive),新建报告时的新模板会带删除按钮。 - 风险:
keydown删除逻辑改动较大,可能在不同浏览器下对边界 selection 的解析有差异,需要手工测试。 - 回滚:如出现问题,可回退
TemplateManage.tsx、defaultContent.ts、index.css、ReportManage.tsx的修改。