# 实现方案 — 2026-04-17-23-12-52 ## 根因分析 ### 问题1:格式选项缺失与旧值残留 - `types.ts` 的 `DEFAULT_FORM_FIELDS` 中,`startTime` 和 `endTime` 的 `timeFormat` 仍被硬编码为 `'24h'`(历史遗留)。 - `TemplateManage.tsx` 中的 `defaultFormats` 虽然已包含 `HH:mm` 和 `hh:mm A`,但 `customTimeFormats` 的 datalist 未按字段类型过滤,导致 date/time 格式混在一起显示,用户体验混乱。 - 用户的 `localStorage` 中 `customTimeFormats` 可能还残留旧值 `'24h'`、`'12h'`。 ### 问题2:报告编辑器显示 "24h" - `formatTimeDisplay('14:30', '24h')` 中,格式字符串 `'24h'` 不包含 `HH`/`hh`/`mm`/`A` 等任何替换 token,函数直接原样返回 `'24h'`。 - 这是 F1 根因的直接后果:`formFieldsConfig` 中的 `timeFormat = '24h'` 被传入格式化函数。 ### 问题3:输入框点击失效 - `TemplateManage.tsx` 字段管理列表位于一个 `overflow-y-auto` 的滚动容器中。 - 点击字段卡片后,卡片内部展开编辑表单,高度瞬间增加。如果卡片原本位于可视区域底部,展开后的输入框可能刚好处于容器边缘之外。 - 浏览器的 hit-testing 在布局突变时可能无法正确将点击事件路由到新出现的输入框上,导致需要手动滚动后才能点击。 ## 修改文件清单 | 文件 | 修改内容 | |------|---------| | `src/types.ts` | `DEFAULT_FORM_FIELDS` 中 `startTime`/`endTime` 的 `timeFormat` 从 `'24h'` 改为 `'HH:mm'` | | `src/pages/ReportEditor.tsx` | `formatTimeDisplay` 开头增加 `if (fmt === '24h') fmt = 'HH:mm';` 兼容兜底 | | `src/pages/TemplateManage.tsx` | ① `defaultFormats` 初始化时过滤掉旧脏数据 `'24h'`/`'12h'`;② datalist 渲染时按字段类型(date/time)过滤选项;③ 字段编辑卡片 `onClick` 中增加 `scrollIntoView` | ## 具体代码变更 ### 1. types.ts ```ts // 修改前 { key: 'startTime', label: '手术开始时间', category: '时间', type: 'time', visibleInForm: true, isSystemLocked: true, timeFormat: '24h', timeDefault: 'specific' }, { key: 'endTime', label: '手术终止时间', category: '时间', type: 'time', visibleInForm: true, isSystemLocked: true, timeFormat: '24h', timeDefault: 'specific' }, // 修改后 { key: 'startTime', label: '手术开始时间', category: '时间', type: 'time', visibleInForm: true, isSystemLocked: true, timeFormat: 'HH:mm', timeDefault: 'specific' }, { key: 'endTime', label: '手术终止时间', category: '时间', type: 'time', visibleInForm: true, isSystemLocked: true, timeFormat: 'HH:mm', timeDefault: 'specific' }, ``` ### 2. ReportEditor.tsx ```ts // 在 formatTimeDisplay 函数开头增加一行兼容兜底 const formatTimeDisplay = (timeStr: string, fmt?: string): string => { if (!timeStr || !fmt) return timeStr || ''; if (fmt === '24h') fmt = 'HH:mm'; // 兼容旧脏数据 // ... 后续代码不变 }; ``` ### 3. TemplateManage.tsx **3.1 清理旧脏数据(初始化时)** ```ts // 修改前 const savedFormats = storage.get('customTimeFormats', []); const defaultFormats = ['YYYY-MM-DD', 'YYYY年MM月DD日', 'MM-DD', 'MM月DD日', 'HH:mm', 'hh:mm A']; setCustomTimeFormats(Array.from(new Set([...defaultFormats, ...savedFormats]))); // 修改后 const savedFormats = storage.get('customTimeFormats', []); const defaultFormats = ['YYYY-MM-DD', 'YYYY年MM月DD日', 'MM-DD', 'MM月DD日', 'HH:mm', 'hh:mm A']; // 过滤掉历史遗留的无效旧格式 const cleanedSaved = savedFormats.filter(f => f !== '24h' && f !== '12h'); setCustomTimeFormats(Array.from(new Set([...defaultFormats, ...cleanedSaved]))); ``` **3.2 按字段类型过滤 datalist** 在编辑字段和新增字段的 format `` 渲染处,增加按 `field.type` / `newFieldForm.type` 过滤: ```tsx {customTimeFormats .filter(fmt => { const isDateFormat = /YYYY|MM|DD/.test(fmt); const isTimeFormat = /HH|hh|mm|A/.test(fmt); if (field.type === 'date') return isDateFormat; if (field.type === 'time') return isTimeFormat; return true; }) .map(fmt => ``` 新增字段的 datalist 同理。 **3.3 编辑卡片点击后自动滚动对齐** ```tsx onClick={(e) => { setEditingFieldKey(field.key); // ... 其他 setState const target = e.currentTarget; setTimeout(() => { target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 50); }} ``` ## 风险点与应对措施 | 风险 | 应对措施 | |------|---------| | 清理 `customTimeFormats` 中的 `'24h'`/`'12h'` 可能误删用户真正想保留的自定义格式 | 仅过滤精确匹配 `'24h'` 和 `'12h'` 两个字符串,不影响其他自定义格式 | | 按类型过滤 datalist 可能误过滤 | 使用正则 `/YYYY|MM|DD/` 识别日期格式,`/HH|hh|mm|A/` 识别时间格式;若格式同时满足两者(理论上不应发生)则同时显示 | | `scrollIntoView` 在极端短容器中频繁触发 | 使用 `block: 'nearest'` 而非 `'start'`,减少不必要的滚动;50ms 延迟确保 DOM 已撑开 | ## 回滚策略 - `types.ts` 的修改可直接回退两个字段的 `timeFormat` 字符串。 - `ReportEditor.tsx` 的修改仅增加一行,删除即可。 - `TemplateManage.tsx` 的修改均为增量逻辑,回滚时移除条件块即可。