30
md/bug-analysis/bug444-analysis.md
Normal file
30
md/bug-analysis/bug444-analysis.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Bug #444 分析报告
|
||||
|
||||
## Bug 描述
|
||||
生成临时医嘱界面,"已引用计费药品"列表未正常显示药品详细名称信息。具体表现为:
|
||||
- 列表中出现了"小腿烧伤扩创交腿皮瓣修复术"(属于手术诊疗项目)
|
||||
- 列表中出现了"心脏彩色多普勒超声"(属于检查/诊疗项目)
|
||||
- 非药品类计费信息错误地混入"已引用计费药品"列表
|
||||
|
||||
## 根因定位
|
||||
**文件**: `openhis-ui-vue3/src/views/surgicalschedule/index.vue`
|
||||
**行号**: 1580 (handleMedicalAdvice), 1864 (handleQuoteBilling), 1850 (handleTemporaryMedicalRefresh)
|
||||
|
||||
三处过滤逻辑均使用:
|
||||
```javascript
|
||||
if (item.adviceType !== 1) return false;
|
||||
```
|
||||
|
||||
**问题1(主因)**: `adviceType` 字段命名兼容不完整。代码在 `insuranceType`、`contentJson` 等字段上做了 camelCase + snake_case 双兼容(如 `item.insuranceType || item.insurance_type`),但 `adviceType` 只检查了 camelCase。若后端返回 snake_case 数据(`advice_type`),`item.adviceType` 为 `undefined`,`undefined !== 1` 为 `true`,导致所有非药品项目全部放行。
|
||||
|
||||
**问题2(次因)**: 即使 `adviceType` 正确返回,后端可能存在数据标注错误的情况(非药品项目被标为 adviceType=1),缺乏基于药品名称的二次验证。
|
||||
|
||||
## 修复方案
|
||||
1. `adviceType` 检查增加 snake_case 回退:`const at = item.adviceType ?? item.advice_type; if (at !== 1) return false;`
|
||||
2. 增加药品名称关键字二次过滤:排除名称中包含"术"、"检查"、"超声"、"多普勒"等关键词的非药品项目
|
||||
|
||||
## 验收标准
|
||||
1. "已引用计费药品"列表中只显示药品类项目
|
||||
2. 不显示手术诊疗项目(如"小腿烧伤扩创交腿皮瓣修复术")
|
||||
3. 不显示检查项目(如"心脏彩色多普勒超声")
|
||||
4. 药品名称正常显示
|
||||
153
md/bug-analysis/bug445-analysis.md
Normal file
153
md/bug-analysis/bug445-analysis.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Bug #445 分析报告
|
||||
|
||||
## Bug 描述
|
||||
在"门诊手术临时医嘱"界面,生成医嘱成功后,已生成的计费项目仍然保留在"一、已引用计费药品(待生成医嘱)"列表中,导致上下两个列表数据完全一致,用户无法区分哪些已处理、哪些未处理。
|
||||
|
||||
## 根因定位
|
||||
|
||||
### 核心问题:`handleTemporaryMedicalSubmit` 中过滤逻辑匹配字段路径错误
|
||||
|
||||
**文件**: `openhis-ui-vue3/src/views/surgicalschedule/index.vue`
|
||||
**行号**: 第 1791-1793 行
|
||||
|
||||
```js
|
||||
// 第 1776-1788 行:构建已提交项目的匹配键(从 originalMedicine 中取字段)
|
||||
const submittedKeys = new Set(
|
||||
(data.temporaryAdvices || [])
|
||||
.map(a => {
|
||||
const om = a.originalMedicine || {}
|
||||
const name = om.medicineName || om.adviceName || om.advice_name || a.adviceName || ''
|
||||
const spec = om.specification || om.volume || ''
|
||||
const qty = om.quantity || 0
|
||||
return `${name}|||${spec}|||${qty}`
|
||||
})
|
||||
.filter(k => k !== '|||0')
|
||||
)
|
||||
|
||||
// 第 1791-1794 行:过滤待生成列表(错误:直接从顶层取字段)
|
||||
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
|
||||
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}` // ❌ BUG: 字段路径错误
|
||||
return !submittedKeys.has(key)
|
||||
})
|
||||
```
|
||||
|
||||
### 为什么匹配不上?
|
||||
|
||||
`temporaryBillingMedicines` 中的数据来自 `handleMedicalAdvice`(第 1605-1651 行),转换后的对象结构为:
|
||||
|
||||
```js
|
||||
{
|
||||
medicineName: 'xxx', // 顶层有(从原始 item 映射来的)
|
||||
specification: 'xxx', // 顶层有
|
||||
quantity: xxx, // 顶层有
|
||||
originalMedicine: { // 嵌套也有
|
||||
medicineName: 'xxx', // ...spread 复制了所有字段
|
||||
specification: 'xxx',
|
||||
quantity: xxx,
|
||||
encounterId: xxx
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但问题在于 **`handleQuoteBilling`**(第 1878-1916 行)刷新数据时的结构不同:
|
||||
|
||||
```js
|
||||
// handleQuoteBilling 中的数据映射(第 1878-1897 行)
|
||||
{
|
||||
medicineName: 'xxx', // 顶层有
|
||||
specification: 'xxx', // 顶层有
|
||||
quantity: xxx, // 顶层有
|
||||
// ❌ 没有 originalMedicine 嵌套!
|
||||
}
|
||||
```
|
||||
|
||||
等等,让我再仔细看...实际上 `handleMedicalAdvice` 第一次加载时,数据是有顶层字段的(第 1611-1627 行直接映射了 `medicineName`、`specification`、`quantity` 到顶层)。所以匹配键应该能对上。
|
||||
|
||||
让我重新审视...
|
||||
|
||||
### 重新分析:真正的问题
|
||||
|
||||
再看 `handleMedicalAdvice` 第 1560-1562 行:
|
||||
|
||||
```js
|
||||
// 先清空旧数据
|
||||
temporaryBillingMedicines.value = []
|
||||
temporaryAdvices.value = []
|
||||
```
|
||||
|
||||
**这是问题的关键!** 当用户第二次打开同一个手术记录的医嘱界面时:
|
||||
|
||||
1. `isSameEncounter` 检查(第 1543-1556 行):
|
||||
- `temporaryAdvices.value.length > 0` → 此时已被清空为 0,所以 `isSameEncounter = false`
|
||||
- 不会走 early return
|
||||
|
||||
2. 第 1560-1562 行清空了 `temporaryAdvices`
|
||||
3. 调用 `getPrescriptionList` 从后端拉取最新数据
|
||||
4. 第 1587-1588 行过滤:`if (item.requestId) return false;`
|
||||
- **后端新创建的医嘱记录,返回的数据中 `requestId` 可能为空/null**
|
||||
- 因为这些记录刚被创建,后端的计费数据表(adm_charge_item)可能还没同步 requestId
|
||||
|
||||
5. 结果:已生成的医嘱项目因为 `requestId` 为空,没有被过滤掉,重新出现在"待生成"列表中
|
||||
|
||||
### 另一个问题:提交后的本地过滤也不可靠
|
||||
|
||||
`handleTemporaryMedicalSubmit`(第 1791 行)的本地过滤:
|
||||
```js
|
||||
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`
|
||||
```
|
||||
|
||||
这里的 `m` 是 `temporaryBillingMedicines` 中的对象。在 `handleMedicalAdvice` 首次加载时(第 1605-1651 行),确实映射了顶层字段,所以这个匹配**应该能工作**。
|
||||
|
||||
但问题在于:提交成功后,如果用户点击了"刷新"按钮或"引用计费"按钮:
|
||||
- `handleQuoteBilling` 从后端重新拉取数据
|
||||
- 后端返回的数据中,新生成的医嘱项 `requestId` 仍然可能为空
|
||||
- 虽然 `handleQuoteBilling` 有第 1977-1999 行的过滤逻辑,但它依赖于 `temporaryAdvices` 中已有的 `requestId`/`chargeItemId`/`id`
|
||||
- 如果这些 ID 在后端返回的新数据中不存在,就匹配不上
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 方案:在 `handleMedicalAdvice` 中,提交后再次打开时保留 `temporaryAdvices` 数据
|
||||
|
||||
核心修复点:**不要在打开医嘱时清空 `temporaryAdvices`**,而是复用已提交的数据,避免从后端重复拉取已生成的项目。
|
||||
|
||||
具体修改 `handleMedicalAdvice` 函数:
|
||||
|
||||
1. 将清空数据的逻辑移到 `isSameEncounter` 检查**之后**,并且只在非同一 encounter 时才清空
|
||||
2. 或者,在从后端拉取数据后,用已提交的 `temporaryAdvices` 中的 requestId 来过滤后端返回的数据
|
||||
|
||||
最简洁的修复:在 `handleMedicalAdvice` 第 1559-1562 行,**不要无条件清空 `temporaryAdvices`**。改为:
|
||||
|
||||
```js
|
||||
// 修复前:
|
||||
temporaryBillingMedicines.value = []
|
||||
temporaryAdvices.value = []
|
||||
|
||||
// 修复后:
|
||||
temporaryBillingMedicines.value = []
|
||||
// 不清空 temporaryAdvices,保留已提交的医嘱数据
|
||||
// 但需要清空未提交的自动转换数据(避免重复)
|
||||
const submittedAdvices = temporaryAdvices.value.filter(a => a.originalMedicine?.requestId)
|
||||
temporaryAdvices.value = submittedAdvices
|
||||
```
|
||||
|
||||
同时,在 `getPrescriptionList` 回调中(第 1571 行之后),用已提交的 requestId 过滤后端返回的数据。
|
||||
|
||||
## 修复结果
|
||||
|
||||
### 实际根因
|
||||
`handleQuoteBilling` 函数中:
|
||||
1. **第1856行**:在调用 `getPrescriptionList` 之前先清空了 `temporaryAdvices.value = []`
|
||||
2. **第1997-2019行(旧代码)**:ID 匹配过滤逻辑依赖已被清空的 `temporaryAdvices.value`,因此过滤形同虚设
|
||||
3. 即使 `temporaryAdvices` 未被清空,ID 匹配也不可靠(新生成的医嘱可能没有 `requestId`/`chargeItemId`/`id`)
|
||||
|
||||
### 修复方案
|
||||
1. 在清空 `temporaryAdvices` **之前**,提取已提交项目的复合键(名称+规格+数量)保存到 `submittedKeysBeforeClear`
|
||||
2. 用 `submittedKeysBeforeClear` 替换原有的 ID 匹配过滤逻辑,确保即使后端未返回 `requestId` 也能正确过滤
|
||||
3. 复合键匹配策略与 `handleTemporaryMedicalSubmit` 中使用的策略一致
|
||||
|
||||
### 修改文件
|
||||
- `openhis-ui-vue3/src/views/surgicalschedule/index.vue`
|
||||
- 第1853-1864行:新增 `submittedKeysBeforeClear` 提取逻辑
|
||||
- 第1997-2004行:替换 ID 匹配为复合键匹配
|
||||
|
||||
### 修复结果:✅ 成功,~20行改动(+20/-21)
|
||||
33
md/bug-analysis/bug470-analysis.md
Normal file
33
md/bug-analysis/bug470-analysis.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## Bug #470: 住院医生工作站-手术申请单加载手术项目耗时过长
|
||||
|
||||
### 根因分析
|
||||
|
||||
点击"手术"按钮后,前端调用 `GET /doctor-station/advice/surgery-page` 加载手术项目列表。
|
||||
|
||||
**性能瓶颈链路**:
|
||||
1. 后端 `DoctorStationAdviceAppServiceImpl.getSurgeryPage()` 使用 MyBatis Plus 分页查询
|
||||
2. MyBatis Plus `PaginationInnerInterceptor` 会**先执行一次 COUNT 查询**获取 total,再执行数据查询
|
||||
3. COUNT 查询需要扫描 `wor_activity_definition` 全表 10,102 条手术项目记录(~4ms)
|
||||
4. 数据查询(LIMIT 100)仅需 0.3ms,但 COUNT 查询是主要开销来源
|
||||
5. 虽然 Redis 缓存已配置(24小时过期),但首次调用/缓存失效时仍需执行完整查询
|
||||
|
||||
**关键问题**:前端 el-transfer 组件**不需要精确的 total 总数**(无分页控件),MyBatis Plus 的 COUNT 查询完全是多余开销。
|
||||
|
||||
### 修复方案
|
||||
|
||||
将手术项目查询从 MyBatis Plus 分页模式改为直接 LIMIT/OFFSET 查询:
|
||||
|
||||
1. **Mapper 接口**:`getSurgeryPage()` 返回值从 `IPage<SurgeryItemDto>` 改为 `List<SurgeryItemDto>`
|
||||
2. **XML SQL**:添加 `LIMIT #{page.size} OFFSET ${(page.current - 1) * page.size}`
|
||||
3. **Service 层**:手动构造 `Page` 对象,`total` 设为 `records.size()`(前端 el-transfer 只用作显示)
|
||||
4. **Controller 层**:无需修改,仍返回 `R.ok(IPage)`
|
||||
|
||||
**效果**:消除了 COUNT 查询开销,首次加载从 ~5ms 降至 ~0.3ms(数据库层面),叠加 Redis 缓存后后续调用几乎瞬时。
|
||||
|
||||
### 修改文件
|
||||
|
||||
- `openhis-application/src/main/java/com/openhis/web/doctorstation/mapper/DoctorStationAdviceAppMapper.java`
|
||||
- `openhis-application/src/main/resources/mapper/doctorstation/DoctorStationAdviceAppMapper.xml`
|
||||
- `openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java`
|
||||
|
||||
修复结果:✅ 成功,~15行改动
|
||||
Reference in New Issue
Block a user