diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index 9a2b88e..8973d99 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -5,7 +5,7 @@ import Sidebar from '../components/Sidebar'; import { Check, Printer, Undo, Redo, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon, - Video, Play, Pause, Plus, X, ChevronLeft + Video, Play, Pause, Plus, X, ChevronLeft, Download } from 'lucide-react'; import { User, Report, Template, CapturedFrame, SystemSettings, FormField, DEFAULT_FORM_FIELDS } from '../types'; import { defaultReportContent } from '../utils/defaultContent'; @@ -48,6 +48,7 @@ export default function ReportEditor() { const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [isSaved, setIsSaved] = useState(false); + const [exportModalOpen, setExportModalOpen] = useState(false); const [loadedTemplateId, setLoadedTemplateId] = useState(''); const [pendingTemplateId, setPendingTemplateId] = useState(null); const prevVideoCountRef = useRef(0); @@ -1306,6 +1307,13 @@ export default function ReportEditor() { 完成报告 + + + + + + + )} + {imagePickerOpen && imagePickerTarget && (
diff --git a/src/pages/ReportManage.tsx b/src/pages/ReportManage.tsx index ae9de4c..932af3a 100644 --- a/src/pages/ReportManage.tsx +++ b/src/pages/ReportManage.tsx @@ -283,7 +283,7 @@ export default function ReportManage() { 报告信息 患者 - 患者号 + 住院号 创建者 时间 状态 diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx index 864a353..f41efd0 100644 --- a/src/pages/TemplateManage.tsx +++ b/src/pages/TemplateManage.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import Sidebar from '../components/Sidebar'; -import { Plus, Edit, Trash2, Save, Printer, Undo, Redo, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon, Check } from 'lucide-react'; +import { Plus, Edit, Trash2, Save, Printer, Undo, Redo, Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, Table, Image as ImageIcon, Check, Download } from 'lucide-react'; import { User, Template, FormField, FieldType, DEFAULT_FORM_FIELDS } from '../types'; import { defaultReportContent } from '../utils/defaultContent'; import { printDocument } from '../utils/print'; @@ -13,6 +13,7 @@ export default function TemplateManage() { const [templates, setTemplates] = useState([]); const [currentTemplateId, setCurrentTemplateId] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + const [exportModalOpen, setExportModalOpen] = useState(false); const [isEditing, setIsEditing] = useState(false); const [formData, setFormData] = useState({ name: '', desc: '' }); const [isSaved, setIsSaved] = useState(false); @@ -34,9 +35,11 @@ export default function TemplateManage() { const [editFieldTimeFormat, setEditFieldTimeFormat] = useState(''); const [editFieldTimeDefault, setEditFieldTimeDefault] = useState<'current' | 'specific'>('specific'); const [editFieldFixedTimeValue, setEditFieldFixedTimeValue] = useState(''); + const [editFieldHasUnderline, setEditFieldHasUnderline] = useState(true); const [newFieldTimeFormat, setNewFieldTimeFormat] = useState('YYYY年MM月DD日'); const [newFieldTimeDefault, setNewFieldTimeDefault] = useState<'current' | 'specific'>('specific'); const [newFieldFixedTimeValue, setNewFieldFixedTimeValue] = useState(''); + const [newFieldHasUnderline, setNewFieldHasUnderline] = useState(true); const [customTimeFormats, setCustomTimeFormats] = useState([]); const [formatDropdownOpen, setFormatDropdownOpen] = useState(false); const [newFormatDropdownOpen, setNewFormatDropdownOpen] = useState(false); @@ -390,7 +393,8 @@ export default function TemplateManage() { } pushHistory(); - const html = ` ×​`; + const underlineClass = field.hasUnderline === false ? ' no-underline' : ''; + const html = ` ×​`; const sel = window.getSelection(); if (sel && sel.rangeCount > 0) { @@ -459,6 +463,7 @@ export default function TemplateManage() { next.timeDefault = editFieldTimeDefault; next.fixedTimeValue = editFieldFixedTimeValue; } + next.hasUnderline = editFieldHasUnderline; return next; }); setFormFields(updated); @@ -476,6 +481,7 @@ export default function TemplateManage() { type: newFieldForm.type, visibleInForm: true, isSystemLocked: false, + hasUnderline: newFieldHasUnderline, options: ['单选', '多选'].includes(newFieldForm.category) && newFieldOptions.trim() ? newFieldOptions.split(/[,,]/).map(s => s.trim()).filter(Boolean) : undefined @@ -493,6 +499,7 @@ export default function TemplateManage() { setNewFieldTimeFormat('YYYY年MM月DD日'); setNewFieldTimeDefault('specific'); setNewFieldFixedTimeValue(''); + setNewFieldHasUnderline(true); }; const handleAssetUpload = (e: React.ChangeEvent) => { @@ -720,6 +727,13 @@ export default function TemplateManage() { 保存模板 +
)} +
+ {exportModalOpen && ( +
+
+

导出模板

+
+ + + +
+
+
+ )} + {isModalOpen && (
@@ -1293,11 +1355,11 @@ export default function TemplateManage() { const styleStr = 'display:flex;align-items:center;justify-content:center;border:1px dashed #cbd5e1;background:#f8fafc;cursor:pointer;width:100%;height:100%;max-width:200px;max-height:200px;min-height:60px;margin:0 auto;'; html = `
×${hintText}
`; } else { - 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;'; - styleStr += `width:${w}px;height:${h}px;`; + 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;`; const showShortText = w > 0 && w < 80; const text = showShortText ? '插图' : hintText; - html = `×${text}​`; + html = `×${text}​`; } const wrapper = document.createElement('div'); wrapper.innerHTML = html; diff --git a/src/types.ts b/src/types.ts index 74c1a3f..2012976 100644 --- a/src/types.ts +++ b/src/types.ts @@ -116,11 +116,12 @@ export interface FormField { timeFormat?: string; timeDefault?: 'current' | 'specific'; fixedTimeValue?: string; + hasUnderline?: boolean; } export const DEFAULT_FORM_FIELDS: FormField[] = [ - { key: 'patientName', label: '患者姓名', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: true }, - { key: 'hospitalId', label: '住院号', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: true }, + { key: 'patientName', label: '患者姓名', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: true, hasUnderline: true }, + { key: 'hospitalId', label: '住院号', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: true, hasUnderline: true }, { key: 'title', label: '手术名称', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: false }, { key: 'patientGender', label: '患者性别', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['男', '女'] }, { key: 'patientAge', label: '患者年龄', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: false }, diff --git a/src/utils/defaultContent.ts b/src/utils/defaultContent.ts index 8e322f2..5db4c08 100644 --- a/src/utils/defaultContent.ts +++ b/src/utils/defaultContent.ts @@ -140,8 +140,8 @@ export const defaultReportContent = ` 冰冻病理结果:${smartField('frozenPathology')}

-

- 手术者签名:×插入/点击放置图片 +

+ 手术者签名:×插入/点击放置图片

diff --git a/src/utils/print.ts b/src/utils/print.ts index 4455375..8a7fb93 100644 --- a/src/utils/print.ts +++ b/src/utils/print.ts @@ -1,4 +1,4 @@ -export const printDocument = (htmlContent: string) => { +export const printDocument = (htmlContent: string, docTitle: string = '图文报告') => { const iframe = document.createElement('iframe'); iframe.style.position = 'fixed'; iframe.style.right = '0'; @@ -40,6 +40,7 @@ export const printDocument = (htmlContent: string) => { .report-signature-img { max-width: 120px; max-height: 40px; width: auto; height: auto; object-fit: contain; vertical-align: middle; display: inline-block; } @media print { .smart-field-wrapper .field-value { border: none !important; border-bottom: 1px solid #000 !important; border-radius: 0 !important; background: transparent !important; padding: 0 2px !important; } + .smart-field-wrapper .field-value.no-underline { border-bottom: none !important; } } diff --git a/工程分析/实现方案-2026-04-18-18-36-43.md b/工程分析/实现方案-2026-04-18-18-36-43.md new file mode 100644 index 0000000..20a8a51 --- /dev/null +++ b/工程分析/实现方案-2026-04-18-18-36-43.md @@ -0,0 +1,91 @@ +# 实现方案 —— 2026-04-18-18-36-43 + +## 方案目标 +实现五项系统改进:列名修正、字段下划线控制、下载导出、右对齐排版修复、默认模板签名右对齐。 + +## 需求 1:ReportManage 列名修正 + +### 修改文件 +`src/pages/ReportManage.tsx` + +### 修改内容 +找到 `` 中「患者号」``,将文本改为「住院号」。同步检查表格数据渲染中是否有对应的 patientId/hospitalId 显示逻辑需调整。 + +## 需求 2:字段管理增加下划线控制 + +### 修改文件 +- `src/types.ts` +- `src/pages/TemplateManage.tsx` +- `src/utils/print.ts` + +### 实现步骤 +1. **扩展 FormField 接口**:增加 `hasUnderline?: boolean`(默认 `true`)。 +2. **修改 DEFAULT_FORM_FIELDS**:为所有默认字段设置 `hasUnderline: true`。 +3. **TemplateManage 字段管理 UI**: + - 新增字段表单中增加「打印时显示下划线」checkbox,默认勾选。 + - 编辑字段面板中同样增加该 checkbox。 + - 保存字段配置时将 `hasUnderline` 写入 `formFieldsConfig`。 +4. **insertSmartField 注入类名**: + - 在生成 `smart-field-wrapper` HTML 时,若 `field.hasUnderline === false`,给 `.field-value` 增加 `no-underline` 类。 +5. **print.ts 打印样式**: + - 在 `@media print` 中增加 `.smart-field-wrapper .field-value.no-underline { border-bottom: none !important; }` + +## 需求 3:ReportEditor / TemplateManage 新增下载按钮 + +### 修改文件 +- `src/pages/ReportEditor.tsx` +- `src/pages/TemplateManage.tsx` +- `src/utils/print.ts` + +### 实现步骤 +1. **print.ts 支持自定义标题**: + - `printDocument(htmlContent: string, docTitle?: string)` 增加可选 `docTitle` 参数。 + - 在 iframe HTML 的 `` 中注入 `${docTitle || '图文报告'}`,使浏览器保存 PDF 时使用该文件名。 +2. **ReportEditor 下载功能**: + - 引入 `Download` 图标。 + - 在顶部操作栏打印按钮旁增加下载按钮。 + - 新增 `exportModalOpen` 状态控制导出弹窗。 + - 实现 `getExportFilename()`:基于 `reportData.title`、`patientName`、`hospitalId` 和当前时间生成文件名。 + - 实现 `downloadJSON()`:将 `reportData` 序列化为 JSON Blob 并触发下载。 + - 导出 PDF 时调用 `printDocument(editorRef.current.innerHTML, getExportFilename())`。 +3. **TemplateManage 下载功能**: + - 类似实现。模板管理页面没有 reportData,文件名中患者信息使用"模板"或空值替代。 + - PDF 导出调用 `printDocument(editorRef.current.innerHTML, filename)`。 + - JSON 导出下载模板内容。 + +## 需求 4:修复右对齐时签名与图片框分离 + +### 修改文件 +- `src/pages/TemplateManage.tsx`(占位符插入逻辑) +- `src/pages/ReportEditor.tsx`(占位符插入逻辑,如有) +- `src/utils/defaultContent.ts`(默认模板签名占位符) + +### 实现步骤 +将 `display: inline-flex` 改为 `display: inline-block`,并通过 `line-height` 实现垂直居中: +- **运行时插入**:`styleStr` 从 `display:inline-flex;align-items:center;justify-content:center;` 改为 `display:inline-block;text-align:center;position:relative;line-height:${h}px;` +- **占位文本**:`.placeholder-text` 增加 `display:inline-block;vertical-align:middle;line-height:normal;` +- **默认模板**:手术者签名占位符同步应用上述样式。 + +## 需求 5:默认模板手术者签名右对齐 + +### 修改文件 +`src/utils/defaultContent.ts` + +### 修改内容 +将「手术者签名」`

` 增加 `text-align: right;`,并应用需求 4 的 `inline-block` 样式修复。 + +## 涉及文件及修改点 +| 文件 | 修改点 | +|------|--------| +| `src/pages/ReportManage.tsx` | 「患者号」→「住院号」 | +| `src/types.ts` | `FormField` 增加 `hasUnderline?: boolean` | +| `src/pages/TemplateManage.tsx` | 字段管理 UI 增加下划线 checkbox;insertSmartField 注入 no-underline 类;工具栏增加下载按钮和弹窗 | +| `src/pages/ReportEditor.tsx` | 工具栏增加下载按钮和弹窗;占位符插入样式改为 inline-block | +| `src/utils/print.ts` | 增加 `docTitle` 参数;打印样式支持 `.no-underline` | +| `src/utils/defaultContent.ts` | 签名占位符改为 inline-block;签名行设为 `text-align: right` | + +## 风险与注意事项 +1. `FormField` 接口扩展后,需确保 `DEFAULT_FORM_FIELDS` 和所有已有字段配置(localStorage 中的 `formFieldsConfig`)兼容。对于旧数据缺少 `hasUnderline` 的情况,按 `true` 处理。 +2. `printDocument` 增加 `docTitle` 参数后,需检查所有调用方是否已更新。现有调用方(如 ReportView)可保持默认行为。 +3. `inline-block` 替换 `inline-flex` 后,需验证占位符在非右对齐场景(如正常左对齐)下的垂直居中效果是否正常。 +4. 下载 JSON 时,TemplateManage 的 JSON 内容与 ReportEditor 不同(模板 vs 报告),需分别处理。 diff --git a/工程分析/测试方案-2026-04-18-18-36-43.md b/工程分析/测试方案-2026-04-18-18-36-43.md new file mode 100644 index 0000000..d60a297 --- /dev/null +++ b/工程分析/测试方案-2026-04-18-18-36-43.md @@ -0,0 +1,117 @@ +# 测试方案 —— 2026-04-18-18-36-43 + +## 测试目标 +验证五项系统改进:列名修正、字段下划线控制、下载导出、右对齐排版修复、默认模板签名右对齐。 + +## 测试用例 + +### TC-01:ReportManage 列名显示 +**前置条件**:进入 /report-manage +**操作步骤**: +1. 查看表格表头 + +**预期结果**: +- 表头显示为「住院号」而非「患者号」 +- 数据列正确显示 hospitalId 值 + +--- + +### TC-02:字段管理下划线开关 +**前置条件**:进入 /template-manage,点击字段管理 +**操作步骤**: +1. 新建一个字段 +2. 观察「打印时显示下划线」checkbox,默认应为勾选 +3. 取消勾选并保存 +4. 将该字段插入模板 + +**预期结果**: +- 新建字段表单中有「打印时显示下划线」选项 +- 编辑字段时也可修改该选项 +- 取消下划线的字段插入后,`.field-value` 带有 `no-underline` 类 + +--- + +### TC-03:打印时下划线控制 +**前置条件**:模板中有带/不带下划线的字段 +**操作步骤**: +1. 进入 report-editor,新建报告 +2. 填写字段内容 +3. 点击打印 + +**预期结果**: +- 默认勾选下划线的字段,打印时 `.field-value` 底部有黑色下划线 +- 取消下划线的字段,打印时 `.field-value` 底部无下划线 + +--- + +### TC-04:ReportEditor 下载按钮 +**前置条件**:进入 /report-editor,有内容的报告 +**操作步骤**: +1. 点击顶部下载按钮 +2. 在弹窗中选择「导出 PDF」 +3. 在弹窗中选择「导出 JSON」 + +**预期结果**: +- 弹窗正常显示两个导出选项 +- PDF 导出时浏览器保存对话框的文件名包含「图文报告-{手术名称}-{患者}-{住院号}-{时间}」 +- JSON 导出时下载的文件名格式同上,内容包含 reportData + +--- + +### TC-05:TemplateManage 下载按钮 +**前置条件**:进入 /template-manage +**操作步骤**: +1. 点击顶部下载按钮 +2. 选择导出 PDF/JSON + +**预期结果**: +- 导出功能正常 +- 文件名格式合理(模板名称 + 时间) + +--- + +### TC-06:右对齐时签名不换行 +**前置条件**:新建报告,加载默认模板 +**操作步骤**: +1. 找到「手术者签名」行 +2. 选中该行,点击工具栏「右对齐」 + +**预期结果**: +- 「手术者签名:」文字与图片占位符在同一行 +- 两者一起靠右对齐 +- 图片框不会单独换到下一行 + +--- + +### TC-07:默认模板签名右对齐 +**前置条件**:新建报告,加载默认模板 +**操作步骤**: +1. 查看报告底部「手术者签名」行 + +**预期结果**: +- 默认即为右对齐 +- 文字与图片框在同一行 + +--- + +### TC-08:占位符 inline-block 样式 +**前置条件**:在 template-manage 中插入静态图片占位符 +**操作步骤**: +1. 点击工具栏「插入图片占位符」 +2. 选择「静态图片占位」 +3. 确认插入 + +**预期结果**: +- 占位符的 style 中 `display` 为 `inline-block` 而非 `inline-flex` +- 占位符在编辑器中正常显示,垂直居中 + +--- + +## 回归测试范围 +- 验证所有现有字段(默认模板中的)打印时仍显示下划线 +- 验证 smart-field-wrapper 双向绑定正常工作 +- 验证 image-placeholder 点击上传、拖拽填充、删除功能正常 +- 验证 report-manage 的搜索、筛选、批量操作不受影响 + +## 测试结论 +TC-01~TC-08 全部通过,即可确认所有需求均正确实现。 diff --git a/工程分析/需求分析-2026-04-18-18-36-43.md b/工程分析/需求分析-2026-04-18-18-36-43.md new file mode 100644 index 0000000..4dfa56a --- /dev/null +++ b/工程分析/需求分析-2026-04-18-18-36-43.md @@ -0,0 +1,39 @@ +# 需求分析 —— 2026-04-18-18-36-43 + +## 需求来源 +用户提出报告管理列名修正、字段下划线控制、下载功能、右对齐排版修复及默认模板调整等五项改进需求。 + +## 需求概述 + +### 需求 1:ReportManage 列名"患者号"改为"住院号" +报告管理列表中表头显示为"患者号",但实际数据对应的是住院号字段,需修正列名。 + +### 需求 2:TemplateManage 字段管理增加"下划线"控制 +在模板管理的字段管理面板中,每个字段增加"打印时显示下划线"单选框,默认勾选。若取消勾选,则该字段在打印输出时不显示底部下划线。需在 `FormField` 数据结构中增加 `hasUnderline` 属性,并在打印样式中支持 `.no-underline` 类。 + +### 需求 3:ReportEditor / TemplateManage 新增下载按钮 +在报告编辑器和模板管理页面的打印按钮旁新增"下载"按钮,点击弹出模态框,支持导出 PDF 和 JSON: +- PDF:复用现有 `printDocument()`,传入自定义文件名 +- JSON:通过 `Blob` + `URL.createObjectURL` 实现下载 +- 默认文件名格式:`图文报告-{手术名称}-{患者}-{住院号}-{下载时间}.pdf/.json` + +### 需求 4:修复右对齐时签名与图片框分离 +当编辑器中设置右对齐时,"手术者签名:"文字与 `class="image-placeholder"` 图片框被拆分为两行。根本原因是 `display: inline-flex` 在右对齐布局下容易触发换行。需将运行时插入的占位符以及默认模板中的占位符 `display` 属性从 `inline-flex` 改为 `inline-block`,并配合 `line-height` 垂直居中。 + +### 需求 5:默认模板手术者签名右对齐 +将 `defaultContent.ts` 中「手术者签名」行默认设为 `text-align: right`,并应用需求 4 中的 `inline-block` 修复。 + +## 涉及文件 +- `src/pages/ReportManage.tsx`(需求 1) +- `src/types.ts`(需求 2:FormField 扩展) +- `src/utils/print.ts`(需求 2、3:打印样式 + 文件名支持) +- `src/pages/TemplateManage.tsx`(需求 2、3、4) +- `src/pages/ReportEditor.tsx`(需求 3、4) +- `src/utils/defaultContent.ts`(需求 4、5) + +## 需求影响范围 +- 报告管理列表展示 +- 模板字段配置体系 +- 编辑器/模板管理器工具栏交互 +- 打印输出样式 +- 文件导出功能