2026-04-17-21-32-27 - 时间日期字段格式配置与撰写时间动态字段

This commit is contained in:
2026-04-17 21:42:51 +08:00
parent 8f746c25f3
commit 8beb534abb
8 changed files with 703 additions and 17 deletions

View File

@@ -0,0 +1,210 @@
# 实现方案 — 2026-04-17-21-32-27
## 根因分析
当前系统的时间/日期字段为「硬编码」形态:
- `date` 类型固定使用浏览器原生 `<input type="date">`smart field 中直接显示原始值。
- `time` 类型仅对 `startTime/endTime` 有表单渲染hour+minute select且固定为 24 小时制smart field 中直接拼接 `HH:MM`
- 没有「当前时间自动填充」机制,也没有「显示格式切换」能力。
- 模板底部「年 月 日」是写死文本,无法自动关联系统时间。
## 修改文件清单
| 文件 | 修改内容 |
|------|---------|
| `src/types.ts` | `FormField` 增加 `timeFormat`/`timeDefault`;更新 `DEFAULT_FORM_FIELDS`;新增 `reportDate` |
| `src/utils/defaultContent.ts` | 底部「年 月 日」→「撰写时间:${smartField('reportDate')}」 |
| `src/pages/TemplateManage.tsx` | 新增字段/编辑面板增加时间配置 UI保存逻辑扩展 |
| `src/pages/ReportEditor.tsx` | date/time 表单渲染增强smart field 同步增加格式转换;初始化自动填充 |
## 具体代码变更
### 1. types.ts
```ts
export interface FormField {
key: string;
label: string;
category: string;
type: FieldType;
visibleInForm: boolean;
isSystemLocked: boolean;
options?: string[];
timeFormat?: string; // NEW
timeDefault?: 'current' | 'specific'; // NEW
}
```
`DEFAULT_FORM_FIELDS` 更新:
- `surgeryDate` 增加 `timeFormat: 'YYYY-MM-DD', timeDefault: 'specific'`
- `startTime` 增加 `timeFormat: '24h', timeDefault: 'specific'`
- `endTime` 增加 `timeFormat: '24h', timeDefault: 'specific'`
- 新增:`reportDate`date, `YYYY年MM月DD日`, `current`, systemLocked
### 2. defaultContent.ts
尾部修改:
```html
<!-- 删除旧的 "年 月 日" 段落 -->
<p style="text-align: right; font-family: SimSun;">
撰写时间:${smartField('reportDate')}
</p>
```
### 3. TemplateManage.tsx
#### 新增状态
```ts
const [newFieldTimeFormat, setNewFieldTimeFormat] = useState('YYYY-MM-DD');
const [newFieldTimeDefault, setNewFieldTimeDefault] = useState<'current' | 'specific'>('specific');
const [editFieldTimeFormat, setEditFieldTimeFormat] = useState('');
const [editFieldTimeDefault, setEditFieldTimeDefault] = useState<'current' | 'specific'>('specific');
```
#### 点击字段进入编辑时
```ts
setEditFieldTimeFormat(field.timeFormat || '');
setEditFieldTimeDefault(field.timeDefault || 'specific');
```
#### 编辑面板editingFieldKey === field.key
在「选项输入框」之后,增加条件渲染:
```tsx
{field.category === '时间' && (
<div className="space-y-1">
<select value={editFieldTimeDefault} onChange={...}>
<option value="specific"></option>
<option value="current"></option>
</select>
<select value={editFieldTimeFormat} onChange={...}>
{field.type === 'date' && <><option value="YYYY-MM-DD">YYYY-MM-DD</option><option value="YYYY年MM月DD日">YYYY年MM月DD日</option></>}
{field.type === 'time' && <><option value="24h">24</option><option value="12h">12</option></>}
</select>
</div>
)}
```
#### saveFieldEdit
```ts
if (field.category === '时间') {
next.timeFormat = editFieldTimeFormat;
next.timeDefault = editFieldTimeDefault;
}
```
#### 新增字段表单
在 category === '时间' 条件下,增加「默认值」和「显示格式」两个 select。
#### addField
```ts
if (newFieldForm.category === '时间') {
newField.timeFormat = newFieldTimeFormat;
newField.timeDefault = newFieldTimeDefault;
}
```
### 4. ReportEditor.tsx
#### 新增辅助函数(组件内)
```ts
const formatDateDisplay = (isoDate: string, fmt?: string): string => {
if (!isoDate) return '';
if (fmt === 'YYYY年MM月DD日') {
const [y, m, d] = isoDate.split('-');
if (y && m && d) return `${y}${m}${d}`;
}
return isoDate;
};
const formatTimeDisplay = (timeStr: string, fmt?: string): string => {
if (!timeStr) return '';
if (fmt === '12h') {
const [hStr, mStr] = timeStr.split(':');
let h = parseInt(hStr);
const ampm = h >= 12 ? '下午' : '上午';
h = h % 12;
if (h === 0) h = 12;
return `${String(h).padStart(2, '0')}:${mStr} ${ampm}`;
}
return timeStr;
};
```
#### date 字段表单渲染
保持 `<input type="date">` 不变,值仍为 `YYYY-MM-DD`
#### time 字段表单渲染
重构为支持通用 time 字段:
**startTime/endTime向后兼容**
- `timeFormat === '24h'`:保持现有 hour(00-23) + minute select
- `timeFormat === '12h'`hour(01-12) + minute + AM/PM select
- 存储转换:`to24h(hour12, isPM)` → 写入 startHour/endHour
**通用 time 字段(非 startTime/endTime**
- 解析 reportData[field.key](格式 `HH:MM`)→ hour + minute
- `timeFormat === '24h'`hour(00-23) + minute
- `timeFormat === '12h'`hour(01-12) + minute + AM/PM
- onChange 时拼接为 `HH:MM` 存入 reportData[field.key]
#### smart field 同步useEffect
在拼接/取值后,增加格式转换:
```ts
if (fieldKey === 'startTime' || fieldKey === 'endTime') {
// ... 拼接 HH:MM
const fieldDef = formFields.find(f => f.key === fieldKey);
newValue = formatTimeDisplay(newValue, fieldDef?.timeFormat);
} else {
const rawValue = (reportData as any)[fieldKey];
// ... 处理 array/string
const fieldDef = formFields.find(f => f.key === fieldKey);
if (fieldDef?.type === 'date') {
newValue = formatDateDisplay(newValue, fieldDef.timeFormat);
} else if (fieldDef?.type === 'time') {
newValue = formatTimeDisplay(newValue, fieldDef.timeFormat);
}
}
```
#### 初始化自动填充
`useEffect` 初始化数据后,遍历 `formFields`
```ts
formFields.forEach(field => {
if (field.timeDefault !== 'current') return;
if (field.type === 'date') {
const current = new Date().toISOString().split('T')[0];
if (!(reportData as any)[field.key]) {
setReportData(prev => ({ ...prev, [field.key]: current }));
}
} else if (field.type === 'time') {
const now = new Date();
const hh = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const current = `${hh}:${mm}`;
if (field.key === 'startTime') {
if (!reportData.startHour) setReportData(prev => ({ ...prev, startHour: hh, startMinute: mm }));
} else if (field.key === 'endTime') {
if (!reportData.endHour) setReportData(prev => ({ ...prev, endHour: hh, endMinute: mm }));
} else {
if (!(reportData as any)[field.key]) {
setReportData(prev => ({ ...prev, [field.key]: current }));
}
}
}
});
```
## 风险点与应对措施
| 风险 | 应对措施 |
|------|---------|
| 现有用户已保存的 `formFieldsConfig` 缺少新字段,导致 `timeFormat` 为 undefined | 代码中统一使用 `field.timeFormat || 默认值` 做回退 |
| 12h 表单与 24h 存储转换出错 | 增加边界单元测试12AM→00, 12PM→12, 1PM→13 等) |
| startTime/endTime 的 hour/minute 存储结构改动影响历史报告 | 保持存储结构不变,仅改动渲染和显示 |
| 自动填充当前时间在编辑已有报告时覆盖用户值 | 仅当字段值为空时才填充 |
## 回滚策略
- `types.ts` 中新增的属性为 optional回滚时删除即可不影响已有数据结构。
- `defaultContent.ts` 的修改可通过 Git revert 恢复。
- TemplateManage/ReportEditor 的 UI 改动为增量添加,回滚时移除条件渲染块即可。

View File

@@ -0,0 +1,96 @@
# 测试方案 — 2026-04-17-21-32-27
## 测试目标
验证时间/日期字段的格式配置、默认值策略、以及模板底部「撰写时间」动态字段的正确性。
## 测试环境
- 本地开发服务器:`npm run dev`(端口 3000
- 浏览器Chrome/Edge
- 测试账号admin / 123456超级管理员
## 测试用例
### TC-1TemplateManage 新增时间字段配置
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 登录 admin进入「模板管理」 |
| 2 | 点击「新增字段」category 选「时间」type 选「日期」 | 下方出现「默认值」select手动选择/当前时间和「显示格式」selectYYYY-MM-DD / YYYY年MM月DD日 |
| 3 | 默认值选「当前时间」格式选「YYYY年MM月DD日」填写标签「出院日期」点击「添加字段」 | 字段列表中出现「出院日期」category 显示「时间 · date」 |
| 4 | 新增字段 category 选「时间」type 选「时分」 | 显示格式 select 出现「24小时制 / 12小时制」 |
### TC-2TemplateManage 编辑已有时间字段配置
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 在字段列表中点击「手术日期」 | 进入编辑模式 |
| 2 | 修改显示格式为「YYYY年MM月DD日」保存 | 字段信息更新 |
| 3 | 点击「手术开始时间」 | 编辑模式中出现 24h/12h 选项 |
### TC-3ReportEditor 日期格式同步到富文本
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 新建报告,加载默认模板 | 基本信息中出现「撰写时间」字段 |
| 2 | 在 TemplateManage 中将「手术日期」格式设为「YYYY年MM月DD日」 | — |
| 3 | 回到 ReportEditor手术日期选「2026-04-17」 | 编辑器中「手术日期」smart field 显示为「2026年04月17日」 |
| 4 | 将格式改回「YYYY-MM-DD」 | 编辑器中显示为「2026-04-17」 |
### TC-4ReportEditor 时间 12h/24h 格式
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 在 TemplateManage 中将「手术开始时间」格式设为「12小时制」 |
| 2 | 在 ReportEditor 中选择 09:30 AM | 编辑器中显示「09:30 上午」 |
| 3 | 切换为 02:30 PM | 编辑器中显示「02:30 下午」reportData.startHour = "14" |
| 4 | 将格式改回「24小时制」 | 表单变为 hour(00-23)+minute编辑器显示「14:30」 |
### TC-5自动填充当前时间
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 确保「撰写时间」的 timeDefault 为「当前时间」 | — |
| 2 | 新建报告 | 「撰写时间」字段自动填充为当天日期(如 2026-04-17 |
| 3 | 确保「手术开始时间」的 timeDefault 为「当前时间」 | — |
| 4 | 新建报告 | 「手术开始时间」自动填充为当前时分 |
| 5 | 编辑已有报告(已有值的报告) | 已有值不被覆盖 |
### TC-6模板底部「撰写时间」
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 新建报告,加载默认模板 | 模板底部出现「撰写时间2026年04月17日」或当天日期 |
| 2 | 在基本信息中修改「撰写时间」 | 编辑器底部同步更新 |
| 3 | 预览/打印报告 | 底部显示正确的撰写时间 |
### TC-7通用 time 字段(非 startTime/endTime
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 在 TemplateManage 新增一个 time 字段「麻醉开始时间」 |
| 2 | 在 ReportEditor 中新建报告 | 基本信息中出现「麻醉开始时间」,可正常选择时分 |
| 3 | 选择 08:15 | 编辑器中对应 smart field 显示「08:15」24h或「08:15 上午」12h |
### TC-8向后兼容
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 清除 localStorage 中 `formFieldsConfig`,重新登录 | 系统加载默认字段,所有时间字段正常工作 |
| 2 | 不配置 timeFormat/timeDefault 的自定义字段 | 按默认行为工作date 显示 YYYY-MM-DDtime 显示 24h |
## 验收标准
- [ ] `npm run lint` 无 TypeScript 编译错误
- [ ] TemplateManage 中时间字段可正常新增、编辑、保存配置
- [ ] ReportEditor 中 date 字段可根据格式正确显示在富文本中
- [ ] ReportEditor 中 time 字段 12h/24h 切换正常,存储值正确
- [ ] 自动填充当前时间仅在值为空时触发
- [ ] 模板底部「撰写时间」动态显示且可编辑
- [ ] 通用 time 字段有表单渲染并能正确同步到富文本
- [ ] 现有报告和历史数据不受本次改动影响
## 测试方式
全部使用手工功能验证(项目无单元测试框架)。

View File

@@ -0,0 +1,43 @@
# 需求分析 — 2026-04-17-21-32-27
## 原始需求摘要
1. TemplateManage 字段管理中,时间/日期字段增加配置选项:
- date/time 均可选择默认值策略:「当前时间」或「手动选择(特定时间)」
- date 可选择显示格式:`YYYY-MM-DD``YYYY年MM月DD日`
- time 可选择显示格式24小时制 或 12小时制AM/PM
2. 默认模板底部写死的「年 月 日」改为动态「撰写时间」智能字段,自动取当前日期。
## 需求拆解
### 功能点
| # | 功能点 | 说明 |
|---|--------|------|
| 1 | 扩展 `FormField` 数据结构 | 新增 `timeFormat?: string``timeDefault?: 'current' \| 'specific'` |
| 2 | TemplateManage 新增字段表单增强 | 当 category === '时间' 时,显示「默认值」和「显示格式」两个配置项 |
| 3 | TemplateManage 字段编辑面板增强 | 已有时间字段点击编辑时,可修改默认值策略和显示格式 |
| 4 | 默认字段配置更新 | `surgeryDate/startTime/endTime` 加上合理的默认配置;新增 `reportDate` 字段 |
| 5 | 默认模板底部「撰写时间」 | `defaultContent.ts` 底部静态文本替换为 `${smartField('reportDate')}` |
| 6 | ReportEditor date 字段格式同步 | smart field 同步时根据 `timeFormat` 转换日期显示格式 |
| 7 | ReportEditor time 字段格式同步 | smart field 同步时根据 `timeFormat` 转换时间显示格式12h/24h |
| 8 | ReportEditor time 字段 12h 表单渲染 | time 字段表单增加 AM/PM 选择,与 hour/minute 联动 |
| 9 | ReportEditor 自动填充当前时间 | 组件初始化时,对 `timeDefault === 'current'` 且值为空的字段自动填充 |
| 10 | 通用 time 字段表单渲染 | 非 `startTime/endTime` 的 time 字段新增 hour+minute select 渲染 |
### 非功能点
- 向后兼容:未配置 `timeFormat/timeDefault` 的现有字段按原有行为工作
- 最小侵入不改动现有数据存储结构date 仍存 `YYYY-MM-DD`time 仍存 `HH:MM``startHour:startMinute`
## 影响范围预估
| 模块 | 影响程度 | 说明 |
|------|---------|------|
| `src/types.ts` | 高 | `FormField` 接口扩展 + `DEFAULT_FORM_FIELDS` 新增/更新 |
| `src/utils/defaultContent.ts` | 中 | 底部静态文本替换为 smartField |
| `src/pages/TemplateManage.tsx` | 高 | 新增字段表单、字段编辑面板、保存逻辑均需改动 |
| `src/pages/ReportEditor.tsx` | 高 | date/time 表单渲染、smart field 同步、初始化自动填充 |
## 待确认问题
无(用户已明确需求)。