From 9e75e86454e5ba2468c50d60f5ec3db6be279d9e Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Fri, 17 Apr 2026 10:11:48 +0800 Subject: [PATCH] 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 --- src/pages/ReportEditor.tsx | 8 ++- src/pages/TemplateManage.tsx | 8 ++- src/types.ts | 8 +++ src/utils/defaultContent.ts | 8 +-- 工程分析/实现方案-2026-04-17-09-57-08.md | 73 ++++++++++++++++++++++++ 工程分析/测试方案-2026-04-17-09-57-08.md | 48 ++++++++++++++++ 工程分析/经验记录.md | 29 ++++++++++ 工程分析/需求分析-2026-04-17-09-57-08.md | 45 +++++++++++++++ 8 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 工程分析/实现方案-2026-04-17-09-57-08.md create mode 100644 工程分析/测试方案-2026-04-17-09-57-08.md create mode 100644 工程分析/需求分析-2026-04-17-09-57-08.md diff --git a/src/pages/ReportEditor.tsx b/src/pages/ReportEditor.tsx index e3b7c1d..4d8d143 100644 --- a/src/pages/ReportEditor.tsx +++ b/src/pages/ReportEditor.tsx @@ -110,8 +110,14 @@ export default function ReportEditor() { if (savedAnesthesia) setAnesthesiaOptions(savedAnesthesia); const savedFields = storage.get('formFieldsConfig', []); + let mergedFields = savedFields; 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 { setFormFields(DEFAULT_FORM_FIELDS); storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS); diff --git a/src/pages/TemplateManage.tsx b/src/pages/TemplateManage.tsx index c7680c7..e94b85a 100644 --- a/src/pages/TemplateManage.tsx +++ b/src/pages/TemplateManage.tsx @@ -41,8 +41,14 @@ export default function TemplateManage() { setCurrentUser(user); const savedFields = storage.get('formFieldsConfig', []); + let mergedFields = savedFields; 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 { setFormFields(DEFAULT_FORM_FIELDS); storage.set('formFieldsConfig', DEFAULT_FORM_FIELDS); diff --git a/src/types.ts b/src/types.ts index 68af5d3..cf46e61 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,6 +30,10 @@ export interface Report { assistant?: string[]; anesthesiologist?: string[]; anesthesiaType?: string; + postOpCondition?: string; + specimenDescription?: string; + pathologySent?: string; + frozenPathology?: string; reportNote?: string; content: string; videos?: { @@ -129,4 +133,8 @@ export const DEFAULT_FORM_FIELDS: FormField[] = [ { 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: '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 }, ]; diff --git a/src/utils/defaultContent.ts b/src/utils/defaultContent.ts index a798d74..3f0198f 100644 --- a/src/utils/defaultContent.ts +++ b/src/utils/defaultContent.ts @@ -132,19 +132,19 @@ export const defaultReportContent = `

- 手术后情况:患者麻醉恢复后安返病房 + 手术后情况:${smartField('postOpCondition')}

- 切除标本描述切除标本描述 + 切除标本描述:${smartField('specimenDescription')}

- 是否送病理检查:是 + 是否送病理检查:${smartField('pathologySent')}

- 冰冻病理结果冰冻病理结果 + 冰冻病理结果:${smartField('frozenPathology')}

diff --git a/工程分析/实现方案-2026-04-17-09-57-08.md b/工程分析/实现方案-2026-04-17-09-57-08.md new file mode 100644 index 0000000..6b34945 --- /dev/null +++ b/工程分析/实现方案-2026-04-17-09-57-08.md @@ -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')}` +- `切除标本描述` → `${smartField('specimenDescription')}` +- `是` → `${smartField('pathologySent')}` +- `冰冻病理结果` → `${smartField('frozenPathology')}` + +### 2.3 `src/pages/ReportEditor.tsx` + +在初始化 `useEffect` 中,读取 `formFieldsConfig` 后增加**字段合并逻辑**: + +```ts +const savedFields = storage.get('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 条。 diff --git a/工程分析/测试方案-2026-04-17-09-57-08.md b/工程分析/测试方案-2026-04-17-09-57-08.md new file mode 100644 index 0000000..1cf26bc --- /dev/null +++ b/工程分析/测试方案-2026-04-17-09-57-08.md @@ -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 个新字段的值能被自动保存/恢复。 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 5ef3ae8..206f6a0 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.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 处静态文本/占位色 `` 替换为 `${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 防误删 **A. 具体问题** diff --git a/工程分析/需求分析-2026-04-17-09-57-08.md b/工程分析/需求分析-2026-04-17-09-57-08.md new file mode 100644 index 0000000..472e8eb --- /dev/null +++ b/工程分析/需求分析-2026-04-17-09-57-08.md @@ -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 中,当前"手术后情况"、"切除标本描述"、"是否送病理检查"、"冰冻病理结果"均为纯文本或占位色 ``。 + - 需要将这 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 个缺失字段。