` 的列顺序严格一致,避免列错位。
---
## 记录 16:模板字段唯一性、删除按钮与报告批量导出
**A. 具体问题**
1. `TemplateManage` 中智能字段可以重复插入多次,导致模板混乱。
2. 智能字段在某些边界位置(如段落开头/结尾)无法通过 Backspace/Delete 删除。
3. `ReportManage` 缺少报告导出功能和批量操作能力。
**B. 产生问题原因**
1. `insertSmartField` 没有检测 DOM 中是否已存在相同 `data-bind` 的字段节点。
2. 之前的 `keydown` 拦截逻辑只处理了光标在文本节点内的情况,没有处理光标直接在块级父节点边界(`startContainer` 为 `` 等块元素)的场景。
3. `ReportManage` 的设计只支持单条查看/编辑/删除,没有设计多选状态和导出逻辑。
**C. 解决问题方案**
1. **唯一性校验**:在 `insertSmartField` 中通过 `editorRef.current?.querySelector([data-bind="..."])` 预检查,若已存在则 `alert` 并终止插入。
2. **删除按钮**:给 `.smart-field-wrapper` 内部增加一个红色圆形的 `×`,点击即可删除整个字段节点。同时在 `index.css` 和 `print` 媒体查询中分别定义显示/隐藏样式。
3. **键盘删除增强**:重写 `keydown` 处理器,同时处理 `startContainer` 为 `TEXT_NODE` 和 `ELEMENT_NODE` 两种情况。当光标位于块级父节点的子节点边界时,通过 `el.childNodes[offset - 1]` 或 `el.childNodes[offset]` 定位字段节点并安全删除。
4. **报告批量操作**:
- 在 `ReportManage.tsx` 中引入 `selectedIds` 状态,表格每行增加 Checkbox,表头支持全选/反选。
- 增加浮动批量操作栏,支持"批量删除"、"批量导出 PDF"、"批量导出 JSON"、"取消选择"。
- 单报告操作列增加"导出"按钮,点击弹出模态框选择 PDF 或 JSON。
- PDF 导出复用现有的 `printDocument(content)`;JSON 导出通过 `Blob` + `URL.createObjectURL` 实现下载,数据结构包含 `meta`(报告元信息)和 `fields`(所有 `DEFAULT_FORM_FIELDS` 对应值)。
- 批量 PDF 将多份报告的 HTML 用 `
` 拼接后统一打印。
- 批量 JSON 将多份报告导出为数组形式的单个 `.json` 文件。
**D. 后续如何避免问题**
- 在 `contentEditable` 中插入的任何可复用控件,都应考虑增加唯一性校验和明确的删除入口(可视化按钮 + 键盘事件拦截)。
- 键盘事件处理不能假设 `startContainer` 一定是文本节点,必须覆盖块级元素边界的情况。
- 当列表页需要增加批量操作时,建议将"选择状态"和"批量动作"封装为独立逻辑,保持单条操作按钮的可维护性。
- 导出功能应尽量复用现有的 `printDocument` 等工具函数,减少新依赖引入。
---
## 记录 17:字段聚焦高亮、删除按钮显隐隔离与 multi_select 脏数据崩溃修复
**A. 具体问题**
1. `TemplateManage` 中编辑智能字段时缺少视觉焦点反馈,用户体验不够直观。
2. 红色 × 删除按钮始终显示在字段内部左侧,且在任何包含 `smart-field-wrapper` 的页面(包括 `ReportEditor`)都会显示。
3. `ReportEditor` 加载某些历史报告时崩溃,报错 `(y[x.key] || []).map is not a function`。
**B. 产生问题原因**
1. 之前没有为 `.field-value` 定义 `:focus` 状态的 CSS 样式。
2. `delete-btn` 使用 `display: inline-flex` 默认常驻显示,且没有针对页面做显隐隔离。
3. `multi_select` 字段(如 `surgeon`、`assistant`)的渲染直接对值调用 `.map()`,但旧数据或异常存储可能将其保存为字符串(如 `"张医生"` 而非 `["张医生"]`),导致 `.map` 在字符串上调用时抛出 `TypeError`。
**C. 解决问题方案**
1. **聚焦高亮**:在 `index.css` 中为 `.smart-field-wrapper .field-value:focus` 增加背景色加深(`#e2e8f0`)、边框变深(`#94a3b8`)和蓝色外发光(`box-shadow: 0 0 0 2px rgba(59,130,246,0.25)`)的样式,配合 `transition` 实现平滑反馈。
2. **删除按钮定位与显隐隔离**:
- 将 `delete-btn` 从字段内部移到 `.field-value` 之后,并给 `.smart-field-wrapper` 增加 `position:relative`,使 `delete-btn` 可绝对定位到右上角(`top: -8px; right: -8px`)。
- 默认 `display: none`;在 `TemplateManage` 的编辑器容器上增加 `template-editor-mode` class,通过 `.template-editor-mode .smart-field-wrapper:hover .delete-btn` 和 `:focus-within .delete-btn` 控制仅在 TemplateManage 中悬浮/聚焦时显示。
- `ReportEditor` 的编辑器容器没有 `template-editor-mode`,因此删除按钮不会显示。
3. **类型安全修复**:在 `ReportEditor.tsx` 的 `multi_select` 渲染分支中,增加 `Array.isArray` 检查:
```ts
const rawValue = (reportData as any)[field.key];
const tags = Array.isArray(rawValue) ? rawValue : (rawValue ? [String(rawValue)] : []);
```
确保无论旧数据是数组、字符串还是空值,都能安全渲染为标签列表。
**D. 后续如何避免问题**
- 任何需要在不同页面显隐不同的 UI 元素,应通过容器级 class 做样式隔离,而不是依赖全局显示/隐藏。
- `contentEditable` 控件的焦点状态必须有明确的视觉反馈(背景/边框/阴影变化),否则用户难以感知当前编辑位置。
- 对从持久化存储读取的数组类型数据,在 React 渲染前务必做 `Array.isArray` 校验,防止历史脏数据导致整页崩溃。
---
## 记录 18:字段悬浮高亮、电子签上传与手术者签名联动
**A. 具体问题**
1. `TemplateManage` 中右侧字段库按钮与编辑器中的字段缺乏视觉关联,用户难以快速定位字段位置。
2. `UserManage` 缺少电子签名上传功能,无法为医生绑定个人签名图。
3. 模板中缺少"手术者签名"字段,报告编辑时无法自动带入医生签名。
4. 签名图片若直接放入 `.field-value` 中,容易撑大行高,影响排版和打印效果。
**B. 产生问题原因**
1. 字段库按钮没有任何与编辑器 DOM 联动的交互反馈机制。
2. 早期设计未考虑医疗文书中的电子签需求,`User` 模型和 `DEFAULT_FORM_FIELDS` 均缺少签名相关定义。
3. 没有针对签名图片设计专门的 CSS 尺寸约束,导致浏览器按原图尺寸渲染,破坏行高。
**C. 解决问题方案**
1. **悬浮高亮**:在 `TemplateManage.tsx` 的字段库按钮上增加 `onMouseEnter` / `onMouseLeave`,直接操作编辑器中对应 `data-bind` 的 `.field-value` 的 `style.boxShadow` 和 `style.backgroundColor`,实现蓝色外发光/背景变浅蓝色的即时高亮反馈。
2. **电子签上传与压缩**:
- 在 `UserManage.tsx` 中增加 `compressImage(file, maxSize=500)` 工具函数,利用 Canvas 等比例缩放并填充白色背景,输出 JPEG base64(质量 0.8)。
- 在用户编辑/新增弹窗中增加"电子签名"区块:预览图、上传按钮、清除按钮。
- 编辑当前登录用户时同步更新 `storage.set('currentUser', ...)`,确保 ReportEditor 能读取最新签名。
3. **手术者签名字段**:
- `types.ts` 中 `User` 增加 `signature?: string`;`FieldType` 增加 `'signature'`;`DEFAULT_FORM_FIELDS` 追加 `surgeonSignature`(分类"图片",系统锁定)。
- `TemplateManage` 插入字段分类增加"图片",`surgeonSignature` 自动出现在该分类下。
- `ReportEditor` 的"表单 → 编辑器"同步 `useEffect` 中,对 `fieldKey === 'surgeonSignature'` 做特殊分支:有签名则填充 `
`,无签名则填充文本"【请上传电子签】"。
4. **签名排版优化**:
- 在 `index.css` 和 `print.ts` 中定义 `.report-signature-img`:
```css
.report-signature-img {
height: 2.4em;
width: auto;
vertical-align: middle;
display: inline-block;
margin: -0.3em 0;
}
```
- 打印媒体查询中同步使用 `!important` 确保打印输出也保持同样尺寸。
**D. 后续如何避免问题**
- 当需要在 React 之外直接操作 DOM 样式实现即时反馈时,优先使用原生事件 + inline style(避免触发组件重渲染导致光标丢失)。
- 任何新增的持久化字段,应在类型定义(TypeScript interface)、默认值(DEFAULT_xxx)、以及所有相关读写逻辑中同步补齐,防止类型不一致。
- 在 `contentEditable` 中插入图片时,务必通过 CSS 对 `height`/`width`/`vertical-align` 做严格约束,避免原图尺寸破坏文本流。
- 涉及打印的样式必须在 iframe 打印模板和 `@media print` 中双端同步,防止打印效果与屏幕预览不一致。
---
## 记录 19:撤销栈修复、字段删除交互优化与签名字段闭环
**A. 具体问题**
1. `TemplateManage` 中通过红色 × 或键盘删除智能字段后,浏览器撤销栈(Undo)失效,点击"撤销"按钮无法恢复。
2. 插入"手术日期"、"手术者签名"等字段后,字段框有时会跳到下一行。
3. Backspace 键无法删除字段;Delete 键会误删字段前面的大段文本(如"手术步骤、术中出现的情况及处理:")。
4. 签名图片没有最大尺寸限制;"手术者签名"字段不在 ReportEditor 表单中显示,无法受控管理签字状态。
5. 点击"完成报告"时缺少对签名状态的确认提示。
**B. 产生问题原因**
1. 删除字段时使用了 `target.remove()` 直接操作 DOM,绕过了浏览器的原生撤销栈(`undo stack`)。
2. 插入的 `smart-field-wrapper` 是 `inline-block` 元素,但其后缺少行内锚点文本节点,浏览器在特定光标位置插入时容易将其挤到新行。
3. `keydown` 拦截逻辑中 `target.remove()` 同样会误删父级块节点(WebKit 在边界处对 `contenteditable="false"` inline 元素的处理缺陷)。
4. `surgeonSignature` 字段原先 `visibleInForm: false`,且签名图片样式仅用 `height: 2.4em` 约束,没有 `max-width/max-height` 的硬限制。
5. 完成报告逻辑中缺少针对签名字段的业务校验。
**C. 解决问题方案**
1. **撤销栈修复**:将点击红 × 删除和键盘 Backspace/Delete 删除全部改为 `Range.selectNode(target)` + `document.execCommand('delete')`。这样浏览器会将删除操作记录到撤销栈中,`execCommand('undo')` 可以正确恢复。
2. **防换行**:在 `insertSmartField` 和 `defaultContent.ts` 的 `smartField()` 生成的 HTML 末尾增加 ``(零宽空格),作为稳定的行内锚点,防止字段被浏览器排到新行。
3. **精准键盘删除**:配合 `Range.selectNode` + `execCommand('delete')`,不再直接 `remove()` DOM 节点,彻底避免误删父级 `` 的问题。
4. **签名尺寸与字段管理**:
- `types.ts` 中将 `surgeonSignature` 改为 `visibleInForm: true, isSystemLocked: false`,使其出现在字段管理和右侧表单中。
- 新增 `isSigned` 字段(单选:已签字 / 未签字,默认"未签字")。
- 签名图片样式改为 `max-width: 120px; max-height: 40px; object-fit: contain;`,并在打印样式和 `print.ts` 中同步。
5. **签名同步逻辑重构**:`ReportEditor` 中 `surgeonSignature` 的渲染由 `isSigned` 控制:
- `已签字` 且 `currentUser.signature` 存在 → 显示签名图片。
- `已签字` 但无签名图 → 显示 "【请上传电子签】"。
- `未签字` → 显示 "【未签字】"。
6. **完成报告签名校验**:`saveReport('completed')` 中,若模板包含 `surgeonSignature`:
- 未选择"已签字" → `confirm` 弱阻断提示。
- 已选择"已签字"但无签名图 → `confirm` 弱阻断提示。
- 用户点击"取消"则中断保存,点击"确定"仍可继续保存。
**D. 后续如何避免问题**
- 在 `contentEditable` 中删除元素时,**优先使用 `Range.selectNode` + `execCommand('delete')`** 而非直接 `remove()`,以确保撤销/重做等原生编辑行为正常工作。
- 插入 `inline-block` 或 `inline-flex` 控件时,可在其后追加 `` 零宽空格,为浏览器提供稳定的行内文本锚点,减少排版异常。
- 任何需要从"不可见"改为"可见/可配置"的字段,应在 `DEFAULT_FORM_FIELDS`、`Report 类型`、`reportData 初始值` 三处同步更新,防止表单渲染遗漏。
- 对于图片类嵌入内容,应使用 `max-width`/`max-height` + `object-fit: contain` 做硬约束,避免不同来源图片破坏页面布局。
---
## 记录 20:TemplateManage 自定义 Undo/Redo 与插入字段光标定位修复
**A. 具体问题**
1. `TemplateManage` 中删除智能字段(通过红 × 或 Backspace/Delete)后,点击工具栏的"撤销"按钮无法恢复字段,"重做"也失效。
2. 点击右侧字段库按钮插入字段时,字段经常跳到下一行或文档末尾。
**B. 产生问题原因**
1. 即使将删除逻辑改为 `execCommand('delete')`,浏览器原生的 undo stack 在 `contentEditable` 中结合 React 状态更新时仍然非常脆弱,容易被清空。
2. 点击侧边栏按钮会导致编辑器 `blur`,浏览器内部的光标位置(Selection/Range)丢失;再次 `focus()` 后光标被重置,导致 `insertHTML` 插入位置错误。
**C. 解决问题方案**
1. **自定义 Undo/Redo 栈**:
- 在 `TemplateManage.tsx` 中引入 `undoStack` 和 `redoStack` 两个 `useRef([])`。
- 实现 `pushHistory()`,在执行任何结构性变更(删除字段、插入字段、插入表格/图片、格式化命令)前将当前 `editorRef.current.innerHTML` 推入 undo 栈并清空 redo 栈。
- 实现 `handleUndo()` / `handleRedo()`,直接替换工具栏按钮的 `execCmd('undo')` / `execCmd('redo')` 调用。从栈中取出历史 HTML 字符串并赋值给 `editorRef.current.innerHTML`,再调用 `saveTemplateContent()` 同步到 React state 和 `localStorage`。
2. **阻止焦点流失**:
- 在所有工具栏按钮和字段库插入按钮上增加 `onMouseDown={(e) => e.preventDefault()}`,阻止 mousedown 默认行为导致编辑器失去焦点。
3. **光标位置记忆与恢复**:
- 利用已有的 `savedRangeRef`,实现 `saveSelection()` 和 `restoreSelection()`。
- 在编辑器 `` 上绑定 `onBlur={saveSelection}`、`onMouseUp={saveSelection}`、`onKeyUp={saveSelection}`,持续记录光标位置。
- 在 `insertSmartField` 和 `insertImage` 中,执行 `insertHTML` 前先调用 `restoreSelection()` 恢复光标,确保字段插入到正确的位置。
**D. 后续如何避免问题**
- 对于 `contentEditable` 编辑器中的结构性变更(插入/删除特殊节点),如果原生 undo 不可靠,应尽早实现自定义历史栈(基于 HTML 字符串快照),完全接管撤销/重做逻辑。
- 侧边栏/工具栏按钮与编辑器共存时,**必须**通过 `onMouseDown={e => e.preventDefault()}` 或等价手段阻止焦点流失,这是保证光标位置不丢失的最简单有效方案。
- 插入操作前恢复 `savedRangeRef` 可以作为焦点流失后的兜底保险,两者结合使用效果最佳。
---
## 记录 21:TemplateManage 快捷键 Undo/Redo 与字段插入排版修复
**A. 具体问题**
1. TemplateManage 中删除 smart-field-wrapper 后按键盘 Ctrl+Z 无法撤销,但点击工具栏撤销按钮可以恢复。
2. 当目标段落以 `
` 结尾时,从字段库插入 smart-field-wrapper 会被拆到下一行(`
` 跑到了 `` 外部)。
**B. 问题产生原因**
1. keydown 事件监听器只拦截了 Backspace/Delete,未拦截 Ctrl+Z/Ctrl+Y,导致浏览器原生 undo 与自定义 undoStack/redoStack 完全脱节。
2. insertSmartField 使用 document.execCommand('insertHTML'),WebKit/Blink 在块级元素末尾存在 `
` 时,会自动将插入的 inline `` 修正到块级元素外部,造成排版错位。
**C. 解决问题方法**
1. **快捷键拦截**:在 keydown 监听的最开头增加 Ctrl+Z / Cmd+Z / Ctrl+Shift+Z / Ctrl+Y 的拦截,调用 e.preventDefault() 后路由到 handleUndo() 或 handleRedo()。
2. **精确 Range 插入**:将 insertSmartField 的插入方式从 execCommand('insertHTML') 替换为手动 Range.insertNode():
- restoreSelection() 恢复光标;
- Range.deleteContents() 清空当前选区;
- 将 HTML 字符串转为 DocumentFragment;
- Range.insertNode(fragment) 精确插入到 Range 位置;
- setStartAfter(lastNode) 把光标移动到插入内容末尾。
**D. 经验与教训总结**
- 在 contentEditable 中实现自定义撤销栈时,必须**同时拦截界面按钮和键盘快捷键**的 undo/redo,否则两套历史机制会互相冲突。
- document.execCommand('insertHTML') 对块级元素边界(尤其是 `
` 结尾)的自动修正行为不可控;需要精确插入时,应优先使用 Range.insertNode() 手动操作 DOM。
- 任何对 contentEditable 的 DOM 修改后,都应同步保存内容(saveTemplateContent),确保 localStorage 中的模板数据与编辑器状态一致。
---
## 记录 22:TemplateManage 字段体系升级与双向交互联动
**A. 具体问题**
1. 新增字段时单选/多选分类仍显示"文本"选项,联动逻辑错误。
2. 默认模板中存在大量静态灰色占位文本(术前诊断、术后诊断等),无法与右侧表单双向绑定。
3. 字段管理列表平铺展示,无分组折叠,系统字段选项不可修改。
4. 图片占位符只能通过本地上传填充,无法使用签名图或系统素材。
5. 编辑器中的智能字段与右侧侧边栏完全无联动。
**B. 问题产生原因**
1. `newFieldForm.category` onChange 时未正确过滤 type select 的 options。
2. `DEFAULT_FORM_FIELDS` 缺少术前/术后诊断等临床字段,导致 `defaultContent.ts` 只能写死占位文本。
3. 字段管理 UI 未按 category 分组,也未提供编辑系统字段选项的入口。
4. `ReportEditor.tsx` 中图片占位符点击后直接调用 `input.click()`,缺少多渠道选择机制。
5. `TemplateManage.tsx` 的 `handleEditorClick` 仅处理了删除逻辑,未处理点击高亮/导航。
**C. 解决问题方法**
1. **类型联动修复**:category onChange 时强制设置对应 type(单选→single_select、多选→multi_select、图片→image);type select 使用条件渲染,只显示当前 category 支持的选项。
2. **扩展默认字段**:在 `types.ts` 追加 `preoperativeDiagnosis`、`postoperativeDiagnosis`、`postOpCondition`、`specimenDescription`、`pathologyCheck`、`frozenPathology`、`hospitalLogo` 等系统字段,全部 `isSystemLocked: true`。
3. **替换模板占位文本**:在 `defaultContent.ts` 中将所有灰色占位文本替换为 `smartField(...)`,Logo 替换为带 `data-bind="hospitalLogo"` 的 `image-placeholder`。
4. **字段管理折叠与编辑**:新增 `expandedCategories` 状态实现折叠面板;新增 `editingFieldKey` 等状态实现点击编辑(系统字段 label 只读、选项可编辑)。
5. **素材库与图片字段**:`FieldType` 扩展 `'image'`;初始化时自动将 Logo 转 Base64 存入 `imageAssets`;`insertSmartField` 对图片类型插入 `image-placeholder`。
6. **图片来源选择弹窗**:`ReportEditor.tsx` 点击图片占位符弹出 Modal,支持本地上传、我的签名、系统素材三选一。
7. **编辑器-侧边栏双向联动**:点击 `smart-field-wrapper` 时读取 `data-bind`,高亮并滚动定位到右侧对应字段,自动展开分组。
**D. 经验与教训总结**
- category→type 的联动应在 state 变更层强制收敛,而不是仅依赖 JSX 条件渲染。
- 升级静态占位文本为字段时,必须同步修改 `DEFAULT_FORM_FIELDS`、`defaultContent.ts` 和 `formFieldsConfig`。
- 图片字段与普通文本字段的 DOM 结构差异大,插入逻辑需要按 type 分支。
- 编辑器与侧边栏联动建议使用 `scrollIntoView` + 临时 CSS 类,避免复杂的状态同步。
- 新增 localStorage key 时应提供合理的默认值或降级处理。
---
## 记录 23:图片占位符体系重构与双端统一
**A. 具体问题**
1. `template-manage` 的"插入字段"中仍存在"图片"分类(手术者签名、医院Logo),用户认为不再需要。
2. 插入图片占位符时无法自定义默认宽高,且使用 `` 导致强制换行。
3. 占位符框太小时"插入/点击放置图片"文字显示不全。
4. 默认模板中签名和 Logo 的结构不统一(一个是 `smartField`,一个是 `div.image-placeholder`)。
5. `template-manage` 点击图片占位符直接调起本地文件选择器,与 `report-editor` 的三选一弹窗行为不一致。
**B. 问题产生原因**
1. `DEFAULT_FORM_FIELDS` 仍包含 `surgeonSignature` 和 `hospitalLogo`。
2. 两端编辑器的 `insertImage()` 使用块级 `
` 插入,未提供尺寸 prompt。
3. 占位符提示文本固定为长文本,未根据容器宽度做缩写适配。
4. `TemplateManage` 的 placeholder 点击事件直接调用 `triggerPlaceholderUpload()`,缺少与 `ReportEditor` 一致的弹窗组件。
**C. 解决问题方法**
1. **清理图片字段**:从 `DEFAULT_FORM_FIELDS` 和 `types.ts` 中移除 `surgeonSignature` 和 `hospitalLogo`;在 `TemplateManage.tsx` 的插入字段/字段管理/新增字段表单中彻底移除"图片"分类。
2. **统一默认模板**:在 `defaultContent.ts` 中将 Logo 和签名均替换为 `
`。
3. **改造 insertImage()**:在 `TemplateManage.tsx` 和 `ReportEditor.tsx` 中,插入前通过 `prompt` 获取最大宽度/高度(px),生成带 `max-width/max-height` 的 `` 行内占位符;提示文字中附加"正文一行文字高度约为 20 像素左右"。
4. **文本自适应**:根据 prompt 输入的宽度决定提示文字:宽度 < 80px 时显示"插入图片",否则显示"插入/点击放置图片"。
5. **统一弹窗行为**:将 `ReportEditor` 的 `imagePickerOpen` / `imagePickerTarget` / `fillPlaceholderSrc` 逻辑完整移植到 `TemplateManage`;删除旧的 `triggerPlaceholderUpload` 直接上传逻辑;两端点击图片占位符均弹出"本地上传 / 我的签名 / 系统素材"三选一弹窗。
6. **优化填充样式**:`fillPlaceholderSrc` 中给 `
` 增加 `max-width:100%; max-height:100%; object-fit:contain;`,避免撑破设置了固定尺寸的占位符。
**D. 经验与教训总结**
- 当从字段体系中彻底移除某一分类时,需要同时清理:`DEFAULT_FORM_FIELDS`、UI 渲染数组、新增表单 options、以及可能残留的分类判断逻辑(如编辑字段时显示 options 输入框的条件)。
- 在 `contentEditable` 中实现"同行插入"必须使用行内元素(``)并显式设置 `display:inline-flex` + `vertical-align:middle`;块级 `` 即使通过 CSS 改 display 也可能因浏览器 execCommand 修正导致换行。
- 跨页面/跨编辑器的一致交互(如图片选择弹窗)应抽取为可复用逻辑或至少保持代码结构一致,避免用户在不同页面产生认知割裂。
- `prompt` 虽不是最优雅的用户交互,但在工具栏快捷操作中是一种零依赖、快速落地的方案;若后续需要更复杂交互,可再替换为 Modal 组件。
---
## 记录 24:时间/日期字段格式配置与撰写时间动态字段
**A. 具体问题**
用户提出 2 个需求:
1. TemplateManage 字段管理中,时间/日期字段增加配置:date 可选 `YYYY-MM-DD` / `YYYY年MM月DD日` 显示格式;time 可选 24h / 12h 显示格式;两者均可选「当前时间」或「手动选择」作为默认值策略。
2. 默认模板底部写死的「年 月 日」改为动态「撰写时间」智能字段,自动取当前日期。
**B. 产生问题原因**
1. `FormField` 数据结构缺少格式和默认值配置字段。
2. `ReportEditor` 中 time 字段的表单渲染仅支持 `startTime/endTime` 且固定为 24 小时制;smart field 同步时直接显示原始值,不做任何格式转换。
3. 模板底部「年 月 日」是纯静态 HTML 文本,没有数据绑定能力。
**C. 解决问题方案**
1. **扩展数据结构**:`FormField` 增加 `timeFormat?: string` 和 `timeDefault?: 'current' | 'specific'`。现有字段补充默认值(`surgeryDate` → `YYYY-MM-DD`+`specific`,`startTime/endTime` → `24h`+`specific`);新增系统字段 `reportDate`(`YYYY年MM月DD日`+`current`)。
2. **TemplateManage UI 增强**:
- 新增字段表单:category 为「时间」时显示「默认值」select(手动选择/当前时间)和「显示格式」select(date 提供两种日期格式,time 提供 24h/12h)。
- 字段编辑面板:点击已有时间字段进入编辑模式时,可修改上述两项配置。
3. **ReportEditor 自动填充**:新增 `useEffect` 监听 `formFields`,对 `timeDefault === 'current'` 且值为空的字段,自动填充系统当前日期/时间。
4. **ReportEditor 表单渲染重构**:
- `startTime/endTime`:根据 `timeFormat` 选择 hour select 的选项范围(24h: 00-23,12h: 01-12),12h 时额外增加 AM/PM select。存储仍保持 24h(`startHour/startMinute`),转换函数 `to24h`/`from24h` 处理 12h↔24h。
- 通用 time 字段(非 startTime/endTime):新增 hour+minute select 渲染,值统一存储为 `HH:MM` 字符串。
5. **smart field 同步格式化**:同步 useEffect 中,根据字段定义调用 `formatDateDisplay`/`formatTimeDisplay`,将原始值转换为配置格式后写入编辑器。
6. **编辑器反向编辑解析**:`handleEditorInput` 中,当用户直接在编辑器内修改 date/time smart field 时,通过正则解析格式化文本(如 `2026年04月17日` → `2026-04-17`、`02:30 下午` → `14:30`),转回原始值后存入 `reportData`。
7. **默认模板更新**:`defaultContent.ts` 底部静态「年 月 日」替换为 `${smartField('reportDate')}`。
**D. 后续如何避免问题**
- 当为字段增加新的配置属性时,务必在 `DEFAULT_FORM_FIELDS` 中为所有已有字段提供合理的默认值,保证向后兼容。
- 显示格式与存储格式分离时,必须同时实现「正向格式化」(存储→显示)和「反向解析」(显示→存储),否则用户在编辑器中直接编辑格式化后的值会导致数据格式混乱。
- 12h/24h 转换要覆盖所有边界情况:12AM→00、12PM→12、1PM→13,建议用独立纯函数(`to24h`/`from24h`)集中处理,避免在 JSX 中内联复杂计算。
- 自动填充当前时间必须增加「仅当值为空时触发」的保护,防止编辑已有报告时覆盖用户数据。
---
## 记录 25:时间字段增强——自定义格式、固定时间默认值、系统锁定标签
**A. 具体问题**
用户提出 4 个改进需求:
1. 默认模板底部「撰写时间」文字前缀与 smartField 占位符重复,需删除前缀仅保留占位符;
2. 多选类和时间类字段在 TemplateManage 字段管理中仍可修改名称,应锁定为系统字段;
3. 「手动选择」文案歧义,应改为「固定时间」;
4. 时间格式应从固定下拉选项改为支持自定义格式输入(类似单选新增选项策略),并支持为「固定时间」设置默认值。
**B. 产生问题原因**
1. `defaultContent.ts` 中底部 HTML 写死了 `撰写时间:${smartField('reportDate')}`,导致编辑器中显示重复文字。
2. `DEFAULT_FORM_FIELDS` 中 `surgeryDate`、`startTime`、`endTime`、`surgeon` 等字段的 `isSystemLocked` 为 `false`,字段库允许修改 label。
3. 早期实现时默认将时间默认值策略命名为「手动选择」,语义不够精确。
4. 日期/时间格式仅通过固定 `