feat: add 4 post-op fields to default template with auto-merge for existing configs
- Add postOpCondition, specimenDescription, pathologySent, frozenPathology to Report type and DEFAULT_FORM_FIELDS - Replace static placeholders in defaultContent.ts with smartField() calls - Auto-merge missing fields from DEFAULT_FORM_FIELDS into existing formFieldsConfig in ReportEditor and TemplateManage
This commit is contained in:
@@ -110,8 +110,14 @@ export default function ReportEditor() {
|
|||||||
if (savedAnesthesia) setAnesthesiaOptions(savedAnesthesia);
|
if (savedAnesthesia) setAnesthesiaOptions(savedAnesthesia);
|
||||||
|
|
||||||
const savedFields = storage.get<FormField[]>('formFieldsConfig', []);
|
const savedFields = storage.get<FormField[]>('formFieldsConfig', []);
|
||||||
|
let mergedFields = savedFields;
|
||||||
if (savedFields.length > 0) {
|
if (savedFields.length > 0) {
|
||||||
setFormFields(savedFields);
|
const missing = DEFAULT_FORM_FIELDS.filter(def => !savedFields.some(f => f.key === def.key));
|
||||||
|
if (missing.length > 0) {
|
||||||
|
mergedFields = [...savedFields, ...missing];
|
||||||
|
storage.set('formFieldsConfig', mergedFields);
|
||||||
|
}
|
||||||
|
setFormFields(mergedFields);
|
||||||
} else {
|
} else {
|
||||||
setFormFields(DEFAULT_FORM_FIELDS);
|
setFormFields(DEFAULT_FORM_FIELDS);
|
||||||
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
|
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
|
||||||
|
|||||||
@@ -41,8 +41,14 @@ export default function TemplateManage() {
|
|||||||
setCurrentUser(user);
|
setCurrentUser(user);
|
||||||
|
|
||||||
const savedFields = storage.get<FormField[]>('formFieldsConfig', []);
|
const savedFields = storage.get<FormField[]>('formFieldsConfig', []);
|
||||||
|
let mergedFields = savedFields;
|
||||||
if (savedFields.length > 0) {
|
if (savedFields.length > 0) {
|
||||||
setFormFields(savedFields);
|
const missing = DEFAULT_FORM_FIELDS.filter(def => !savedFields.some(f => f.key === def.key));
|
||||||
|
if (missing.length > 0) {
|
||||||
|
mergedFields = [...savedFields, ...missing];
|
||||||
|
storage.set('formFieldsConfig', mergedFields);
|
||||||
|
}
|
||||||
|
setFormFields(mergedFields);
|
||||||
} else {
|
} else {
|
||||||
setFormFields(DEFAULT_FORM_FIELDS);
|
setFormFields(DEFAULT_FORM_FIELDS);
|
||||||
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
|
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export interface Report {
|
|||||||
assistant?: string[];
|
assistant?: string[];
|
||||||
anesthesiologist?: string[];
|
anesthesiologist?: string[];
|
||||||
anesthesiaType?: string;
|
anesthesiaType?: string;
|
||||||
|
postOpCondition?: string;
|
||||||
|
specimenDescription?: string;
|
||||||
|
pathologySent?: string;
|
||||||
|
frozenPathology?: string;
|
||||||
reportNote?: string;
|
reportNote?: string;
|
||||||
content: string;
|
content: string;
|
||||||
videos?: {
|
videos?: {
|
||||||
@@ -129,4 +133,8 @@ export const DEFAULT_FORM_FIELDS: FormField[] = [
|
|||||||
{ key: 'assistant', label: '助手', category: '多选', type: 'multi_select', visibleInForm: true, isSystemLocked: false, options: ['赵医生', '钱医生', '孙医生'] },
|
{ key: 'assistant', label: '助手', category: '多选', type: 'multi_select', visibleInForm: true, isSystemLocked: false, options: ['赵医生', '钱医生', '孙医生'] },
|
||||||
{ key: 'anesthesiologist', label: '麻醉师', category: '多选', type: 'multi_select', visibleInForm: true, isSystemLocked: false, options: ['周医生', '吴医生', '郑医生'] },
|
{ key: 'anesthesiologist', label: '麻醉师', category: '多选', type: 'multi_select', visibleInForm: true, isSystemLocked: false, options: ['周医生', '吴医生', '郑医生'] },
|
||||||
{ key: 'anesthesiaType', label: '麻醉方式', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['全麻', '局麻', '腰麻', '硬膜外麻醉', '静脉麻醉', '吸入麻醉'] },
|
{ key: 'anesthesiaType', label: '麻醉方式', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['全麻', '局麻', '腰麻', '硬膜外麻醉', '静脉麻醉', '吸入麻醉'] },
|
||||||
|
{ key: 'postOpCondition', label: '手术后情况', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['患者麻醉恢复后安返病房'] },
|
||||||
|
{ key: 'specimenDescription', label: '切除标本描述', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: [] },
|
||||||
|
{ key: 'pathologySent', label: '是否送检病理', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['是', '否'] },
|
||||||
|
{ key: 'frozenPathology', label: '冰冻病理结果', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: false },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -132,19 +132,19 @@ export const defaultReportContent = `
|
|||||||
|
|
||||||
<div class="template-info-section">
|
<div class="template-info-section">
|
||||||
<p style="font-family: SimSun;">
|
<p style="font-family: SimSun;">
|
||||||
<strong>手术后情况</strong>:患者麻醉恢复后安返病房
|
<strong>手术后情况</strong>:${smartField('postOpCondition')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style="font-family: SimSun;">
|
<p style="font-family: SimSun;">
|
||||||
<strong>切除标本描述</strong>:<span style="color: #bdbdbd;">切除标本描述</span>
|
<strong>切除标本描述</strong>:${smartField('specimenDescription')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style="font-family: SimSun;">
|
<p style="font-family: SimSun;">
|
||||||
<strong>是否送病理检查</strong>:是
|
<strong>是否送病理检查</strong>:${smartField('pathologySent')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style="font-family: SimSun;">
|
<p style="font-family: SimSun;">
|
||||||
<strong>冰冻病理结果</strong>:<span style="color: #bdbdbd;">冰冻病理结果</span>
|
<strong>冰冻病理结果</strong>:${smartField('frozenPathology')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style="font-family: SimSun;">
|
<p style="font-family: SimSun;">
|
||||||
|
|||||||
73
工程分析/实现方案-2026-04-17-09-57-08.md
Normal file
73
工程分析/实现方案-2026-04-17-09-57-08.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# 实现方案 — 模板新增术后字段(2026-04-17-09-57-08)
|
||||||
|
|
||||||
|
## 一、修改文件清单
|
||||||
|
|
||||||
|
1. `src/types.ts` — 扩展 `Report` 类型 + 追加 `DEFAULT_FORM_FIELDS`
|
||||||
|
2. `src/utils/defaultContent.ts` — 替换默认模板中的 4 处占位文本为 `smartField()`
|
||||||
|
3. `src/pages/ReportEditor.tsx` — 增加 `formFieldsConfig` 自动补全/升级逻辑
|
||||||
|
4. `src/pages/TemplateManage.tsx` — 增加同样的自动补全/升级逻辑
|
||||||
|
|
||||||
|
## 二、详细改动
|
||||||
|
|
||||||
|
### 2.1 `src/types.ts`
|
||||||
|
|
||||||
|
#### A. `Report` 接口追加字段
|
||||||
|
```ts
|
||||||
|
postOpCondition?: string;
|
||||||
|
specimenDescription?: string;
|
||||||
|
pathologySent?: string;
|
||||||
|
frozenPathology?: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. `DEFAULT_FORM_FIELDS` 追加 4 条
|
||||||
|
```ts
|
||||||
|
{ key: 'postOpCondition', label: '手术后情况', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['患者麻醉恢复后安返病房'] },
|
||||||
|
{ key: 'specimenDescription', label: '切除标本描述', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: [] },
|
||||||
|
{ key: 'pathologySent', label: '是否送检病理', category: '单选', type: 'single_select', visibleInForm: true, isSystemLocked: false, options: ['是', '否'] },
|
||||||
|
{ key: 'frozenPathology', label: '冰冻病理结果', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: false },
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `src/utils/defaultContent.ts`
|
||||||
|
|
||||||
|
将模板底部 `template-info-section` 中的 4 处文本替换:
|
||||||
|
|
||||||
|
- `患者麻醉恢复后安返病房` → `${smartField('postOpCondition')}`
|
||||||
|
- `<span style="color: #bdbdbd;">切除标本描述</span>` → `${smartField('specimenDescription')}`
|
||||||
|
- `是` → `${smartField('pathologySent')}`
|
||||||
|
- `<span style="color: #bdbdbd;">冰冻病理结果</span>` → `${smartField('frozenPathology')}`
|
||||||
|
|
||||||
|
### 2.3 `src/pages/ReportEditor.tsx`
|
||||||
|
|
||||||
|
在初始化 `useEffect` 中,读取 `formFieldsConfig` 后增加**字段合并逻辑**:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const savedFields = storage.get<FormField[]>('formFieldsConfig', []);
|
||||||
|
let mergedFields = savedFields;
|
||||||
|
if (savedFields.length > 0) {
|
||||||
|
const missing = DEFAULT_FORM_FIELDS.filter(def => !savedFields.some(f => f.key === def.key));
|
||||||
|
if (missing.length > 0) {
|
||||||
|
mergedFields = [...savedFields, ...missing];
|
||||||
|
storage.set('formFieldsConfig', mergedFields);
|
||||||
|
}
|
||||||
|
setFormFields(mergedFields);
|
||||||
|
} else {
|
||||||
|
setFormFields(DEFAULT_FORM_FIELDS);
|
||||||
|
storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 `src/pages/TemplateManage.tsx`
|
||||||
|
|
||||||
|
在同样的初始化位置执行完全相同的合并逻辑,保证两个页面中的字段配置一致性。
|
||||||
|
|
||||||
|
## 三、风险与回滚
|
||||||
|
|
||||||
|
- **风险**:修改 `DEFAULT_FORM_FIELDS` 和默认模板后,新用户直接生效;老用户通过合并逻辑无痛升级。
|
||||||
|
- **回滚**:如出现问题,可将 `DEFAULT_FORM_FIELDS` 恢复为 14 条,并回滚 `defaultContent.ts` 中的 4 处替换。
|
||||||
|
|
||||||
|
## 四、验证计划
|
||||||
|
|
||||||
|
1. 清空浏览器 localStorage 后刷新,确认新建报告时模板底部 4 处为可编辑方框。
|
||||||
|
2. 在右侧表单中操作 4 个新控件,观察模板方框实时同步。
|
||||||
|
3. 在模板方框中直接输入,观察右侧表单实时同步。
|
||||||
|
4. 预先写入旧版 `formFieldsConfig`(14 条),刷新页面,确认自动补全为 18 条。
|
||||||
48
工程分析/测试方案-2026-04-17-09-57-08.md
Normal file
48
工程分析/测试方案-2026-04-17-09-57-08.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 测试方案 — 模板新增术后字段(2026-04-17-09-57-08)
|
||||||
|
|
||||||
|
## 一、编译检查
|
||||||
|
|
||||||
|
- 执行 `npm run lint`(`tsc --noEmit`),确保全量 TypeScript 无编译错误。
|
||||||
|
|
||||||
|
## 二、功能验证步骤
|
||||||
|
|
||||||
|
### 测试 1:新用户首次使用(默认模板加载)
|
||||||
|
1. 浏览器 DevTools → Application → Local Storage → 清空本站所有 key。
|
||||||
|
2. 刷新页面,用 `admin / 123456` 登录。
|
||||||
|
3. 进入【模板管理】→ 确认默认模板底部 4 处文本已变为可编辑的智能方框:
|
||||||
|
- 手术后情况
|
||||||
|
- 切除标本描述
|
||||||
|
- 是否送检病理
|
||||||
|
- 冰冻病理结果
|
||||||
|
4. 点击【新建报告】→ 确认右侧【基本信息】表单出现:
|
||||||
|
- 手术后情况(下拉单选,默认选项 1 条)
|
||||||
|
- 切除标本描述(下拉单选,暂无选项,显示"暂无选项")
|
||||||
|
- 是否送检病理(下拉单选,选项:是 / 否)
|
||||||
|
- 冰冻病理结果(文本输入框)
|
||||||
|
5. 在表单中修改"是否送检病理"为"否",观察编辑器中对应方框实时变为"否"。
|
||||||
|
6. 在编辑器中直接点击"冰冻病理结果"方框并输入"良性",观察右侧表单实时变为"良性"。
|
||||||
|
|
||||||
|
### 测试 2:老用户字段升级(自动补全)
|
||||||
|
1. DevTools Console 执行:
|
||||||
|
```js
|
||||||
|
localStorage.setItem('formFieldsConfig', JSON.stringify([
|
||||||
|
{ key: 'patientName', label: '患者姓名', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: true },
|
||||||
|
{ key: 'hospitalId', label: '住院号', category: '填空', type: 'text', visibleInForm: true, isSystemLocked: true }
|
||||||
|
]));
|
||||||
|
```
|
||||||
|
2. 刷新页面,进入【新建报告】。
|
||||||
|
3. 确认右侧表单中除了"患者姓名"、"住院号"外,还自动出现了本次新增的 4 个字段以及其他默认字段(共 18 条)。
|
||||||
|
4. DevTools 中查看 `formFieldsConfig`,确认 key 列表包含 `postOpCondition`、`specimenDescription`、`pathologySent`、`frozenPathology`。
|
||||||
|
|
||||||
|
### 测试 3:持久化验证
|
||||||
|
1. 在【新建报告】中填写 4 个新字段的值。
|
||||||
|
2. 切换到【报告管理】再返回【新建报告】。
|
||||||
|
3. 确认 4 个新字段的值在表单和编辑器中均被正确恢复。
|
||||||
|
|
||||||
|
## 三、预期结果
|
||||||
|
|
||||||
|
- `npm run lint` 0 错误。
|
||||||
|
- 新建报告时模板底部 4 处为智能方框,右侧表单出现对应控件。
|
||||||
|
- 表单 ↔ 编辑器双向同步正常。
|
||||||
|
- 老用户自动补全字段成功,无数据丢失。
|
||||||
|
- 路由切换后,4 个新字段的值能被自动保存/恢复。
|
||||||
29
工程分析/经验记录.md
29
工程分析/经验记录.md
@@ -345,6 +345,35 @@ if ((settings.autoInsertDelay || 0) > 0) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 记录 15:默认模板新增术后 4 个字段并自动补全老用户配置
|
||||||
|
|
||||||
|
**A. 具体问题**
|
||||||
|
需要在默认手术报告模板中新增 4 个字段(手术后情况、切除标本描述、是否送检病理、冰冻病理结果),使其与右侧【基本信息】动态表单双向联动。同时,老用户浏览器 localStorage 中已存在的 `formFieldsConfig` 不包含这 4 个字段,直接发版会导致老用户看不到新控件。
|
||||||
|
|
||||||
|
**B. 产生问题原因**
|
||||||
|
1. `DEFAULT_FORM_FIELDS` 和 `defaultContent.ts` 是代码层面的默认值,但 `ReportEditor.tsx` 和 `TemplateManage.tsx` 的初始化逻辑是"若 localStorage 中已存在 `formFieldsConfig`,则直接使用,不再回退到代码默认值"。
|
||||||
|
2. 因此,新增字段只对新用户(localStorage 为空)生效,老用户的字段列表会永远停留在旧版本。
|
||||||
|
|
||||||
|
**C. 解决问题方案**
|
||||||
|
1. 在 `src/types.ts` 的 `Report` 接口中追加 4 个新字段;在 `DEFAULT_FORM_FIELDS` 末尾追加 4 条配置(含 `options`)。
|
||||||
|
2. 在 `src/utils/defaultContent.ts` 中,将模板底部对应 4 处静态文本/占位色 `<span>` 替换为 `${smartField('key')}`。
|
||||||
|
3. 在 `ReportEditor.tsx` 和 `TemplateManage.tsx` 的初始化 `useEffect` 中,读取 `formFieldsConfig` 后增加**缺失字段自动补全逻辑**:
|
||||||
|
```ts
|
||||||
|
const missing = DEFAULT_FORM_FIELDS.filter(def => !savedFields.some(f => f.key === def.key));
|
||||||
|
if (missing.length > 0) {
|
||||||
|
mergedFields = [...savedFields, ...missing];
|
||||||
|
storage.set('formFieldsConfig', mergedFields);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
这样老用户首次进入页面时,缺失字段会自动追加到列表末尾,同时保留用户已有的自定义字段和显隐设置。
|
||||||
|
|
||||||
|
**D. 后续如何避免问题**
|
||||||
|
- 任何依赖 `localStorage` 持久化配置的"字段/菜单/权限"列表,在后续版本新增默认值时,都应提供**自动合并/升级逻辑**,而不是简单依赖"有值则跳过"。
|
||||||
|
- 合并策略推荐:以代码层 `DEFAULT_xxx` 为基准,检测缺失 key 并追加,保留用户已有的自定义项和状态,实现无痛升级。
|
||||||
|
- 对于模板 HTML 的变更,若旧模板已存入 `templates` localStorage,则默认模板只影响新建报告;若需要更新已有模板,需额外提供模板升级脚本或管理员手动维护机制。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 记录 14:智能字段插入间距修复与 Backspace 防误删
|
## 记录 14:智能字段插入间距修复与 Backspace 防误删
|
||||||
|
|
||||||
**A. 具体问题**
|
**A. 具体问题**
|
||||||
|
|||||||
45
工程分析/需求分析-2026-04-17-09-57-08.md
Normal file
45
工程分析/需求分析-2026-04-17-09-57-08.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# 需求分析 — 模板新增术后字段(2026-04-17-09-57-08)
|
||||||
|
|
||||||
|
## 一、需求来源
|
||||||
|
|
||||||
|
用户要求在默认手术报告模板中进一步插入 4 个新字段,并使其与右侧【基本信息】动态表单联动。
|
||||||
|
|
||||||
|
## 二、具体字段清单
|
||||||
|
|
||||||
|
| 序号 | 字段名称 | 字段 key | 类型 | 分类 | 选项 | 表单显隐 |
|
||||||
|
|------|----------|----------|------|------|------|----------|
|
||||||
|
| 1 | 手术后情况 | `postOpCondition` | 单选 | 单选 | `患者麻醉恢复后安返病房` | 显示 |
|
||||||
|
| 2 | 切除标本描述 | `specimenDescription` | 单选 | 单选 | 暂无(空数组) | 显示 |
|
||||||
|
| 3 | 是否送检病理 | `pathologySent` | 单选 | 单选 | `是`, `否` | 显示 |
|
||||||
|
| 4 | 冰冻病理结果 | `frozenPathology` | 填空 | text | — | 显示 |
|
||||||
|
|
||||||
|
## 三、影响范围分析
|
||||||
|
|
||||||
|
1. **`src/types.ts`**
|
||||||
|
- 需要在 `Report` 接口中新增 4 个字段定义,保持类型安全。
|
||||||
|
- 需要在 `DEFAULT_FORM_FIELDS` 数组末尾追加 4 条 `FormField` 配置。
|
||||||
|
|
||||||
|
2. **`src/utils/defaultContent.ts`**
|
||||||
|
- 默认模板 HTML 中,当前"手术后情况"、"切除标本描述"、"是否送病理检查"、"冰冻病理结果"均为纯文本或占位色 `<span>`。
|
||||||
|
- 需要将这 4 处替换为 `${smartField('key')}` 调用,使其具备双向绑定能力。
|
||||||
|
|
||||||
|
3. **`src/pages/ReportEditor.tsx`**
|
||||||
|
- 右侧表单已经是"配置驱动渲染"(遍历 `formFieldsConfig` 按 `type` switch-case),新增字段后无需修改 JSX。
|
||||||
|
- 表单 → 编辑器的同步 `useEffect` 使用 `(reportData as any)[fieldKey]` 读取,对新字段自动生效。
|
||||||
|
- 编辑器 → 表单的 `handleEditorInput` 通过 `data-bind` 属性反射,同样自动生效。
|
||||||
|
|
||||||
|
4. **`src/pages/TemplateManage.tsx`**
|
||||||
|
- 字段库侧边栏读取 `formFieldsConfig`,新增配置后自动显示在【插入字段】列表中。
|
||||||
|
- 无需额外修改。
|
||||||
|
|
||||||
|
## 四、兼容性说明
|
||||||
|
|
||||||
|
- 旧用户 localStorage 中已存在 `formFieldsConfig`,如果其中不包含新字段,则首次进入 `ReportEditor`/`TemplateManage` 时只会显示旧字段。
|
||||||
|
- **策略**:由于系统当前逻辑是"若 `formFieldsConfig` 有值则直接使用,不再回退到 `DEFAULT_FORM_FIELDS`",因此需要额外提供一个**字段合并/升级逻辑**,在初始化时检测缺失字段并自动补入,确保老用户也能看到这 4 个新字段。
|
||||||
|
|
||||||
|
## 五、验收标准
|
||||||
|
|
||||||
|
- [ ] 新建报告时,默认模板中"手术后情况"等 4 处显示为可编辑的智能方框。
|
||||||
|
- [ ] 右侧【基本信息】表单中出现对应的 4 个输入控件(3 个单选下拉 + 1 个文本框)。
|
||||||
|
- [ ] 表单修改值后,模板中的方框实时同步;在方框中直接输入后,表单也实时同步。
|
||||||
|
- [ ] 老用户(已有 `formFieldsConfig`)首次进入页面时,自动补全这 4 个缺失字段。
|
||||||
Reference in New Issue
Block a user