211 lines
7.4 KiB
Markdown
211 lines
7.4 KiB
Markdown
# 实现方案 — 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 改动为增量添加,回滚时移除条件渲染块即可。
|