Compare commits

..

4 Commits

2 changed files with 68 additions and 68 deletions

44
bug444_analysis.md Normal file
View File

@@ -0,0 +1,44 @@
# Bug #444 分析报告
## Bug 描述
【手术管理-门诊手术安排】生成临时医嘱界面,"已引用计费药品"列表未正常显示药品详细名称信息,且错误地带出了非药品类的计费信息(如手术诊疗项目"小腿烧伤扩创交腿皮瓣修复术"、检查项目"心脏彩色多普勒超声")。
## 根因分析
### 数据流
1. 用户点击"医嘱"按钮 → `handleMedicalAdvice()` → 调用 `getPrescriptionList()` 获取计费数据
2. 用户对数据进行过滤后展示在"已引用计费药品"列表
3. 用户点击"引用计费"按钮 → `handleQuoteBilling()` → 再次调用 `getPrescriptionList()` 获取最新计费数据
### 根因定位
**`handleQuoteBilling()` 方法index.vue:1866-1877缺少非药品关键词过滤逻辑。**
`handleMedicalAdvice()` 中有两层过滤:
1. `adviceType !== 1` 过滤(只保留药品类型)
2. **关键词排除过滤**(排除名称中包含"术"、"超声"、"检查"等非药品关键词的项目)
`handleQuoteBilling()` 中只有第一层过滤(`adviceType !== 1`**缺少关键词排除过滤**。
当后端返回的计费数据中某些非药品项目被错误标注为 `adviceType=1` 时:
- `handleMedicalAdvice()` 能通过关键词过滤排除这些项目
- `handleQuoteBilling()` 无法排除,导致非药品项目出现在"已引用计费药品"列表中
### 代码对比
| 过滤条件 | handleMedicalAdvice (L1576-1597) | handleQuoteBilling (L1866-1877) |
|---------|:---:|:---:|
| encounterId 匹配 | ✓ | ✓ |
| adviceType === 1 | ✓ | ✓ |
| 名称非空 | ✓ | ✓ |
| **关键词排除** | ✓ **有** | ✗ **缺失** |
| requestId 过滤 | ✓ | ✓ |
## 修复方案
`handleQuoteBilling()` 方法的过滤逻辑中,添加与 `handleMedicalAdvice()` 一致的关键词排除逻辑:
```javascript
// 🔧 修复 Bug #444: 排除名称中包含手术/检查/诊疗关键词的非药品项目
const excludedKeywords = ['术', '超声', '多普勒', '检查', '检验', '彩超', 'X线', 'CT', 'MRI', '扫描', '造影'];
if (excludedKeywords.some(kw => medicineName.includes(kw))) return false;
```

View File

@@ -1854,21 +1854,6 @@ function handleTemporaryMedicalRefresh() {
function handleQuoteBilling() { function handleQuoteBilling() {
// 重新拉取计费药品数据 // 重新拉取计费药品数据
if (temporaryPatientInfo.value.visitId) { if (temporaryPatientInfo.value.visitId) {
// 🔧 修复 Bug #445: 在清空之前提取已提交项目的复合匹配键
// 原因:后续的 ID 匹配过滤依赖 temporaryAdvices但 temporaryAdvices 会被先清空
// 新医嘱没有 requestId/chargeItemId需用名称+规格+数量的复合键匹配
const submittedKeys = new Set(
(temporaryAdvices.value || [])
.map(a => {
const om = a.originalMedicine || {}
const name = om.medicineName || om.adviceName || a.adviceName || ''
const spec = om.specification || om.volume || ''
const qty = om.quantity ?? 0
return `${name}|||${spec}|||${qty}`
})
.filter(k => k !== '|||0')
)
temporaryMedicalLoading.value = true // 🔧 新增:开始加载 temporaryMedicalLoading.value = true // 🔧 新增:开始加载
getPrescriptionList(temporaryPatientInfo.value.visitId, 6, temporaryPatientInfo.value.operCode).then((res) => { getPrescriptionList(temporaryPatientInfo.value.visitId, 6, temporaryPatientInfo.value.operCode).then((res) => {
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
@@ -1876,13 +1861,14 @@ function handleQuoteBilling() {
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
temporaryAdvices.value = [] temporaryAdvices.value = []
// 🔧 修复 Bug #445: 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3/6) // 🔧 修复 Bug #444: 统一过滤逻辑,与 handleMedicalAdvice 保持一致
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中) // 1. 使用 Number() + snake_case 回退,避免类型转换导致过滤失效
// 2. 增加关键词二次过滤,排除手术/检查/诊疗等非药品项目
const filteredItems = res.data.filter(item => { const filteredItems = res.data.filter(item => {
// 匹配 encounterId // 匹配 encounterId
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false; if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
// 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3/6) // 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3/6)
// 🔧 修复 Bug #444: 使用 Number() 显式转换 + snake_case 回退,避免字符串 "1" 匹配失败 // 🔧 修复 Bug #444: 使用 Number() 显式转换,增加 snake_case 回退
const at = Number(item.adviceType ?? item.advice_type); const at = Number(item.adviceType ?? item.advice_type);
if (at !== 1) return false; if (at !== 1) return false;
// 过滤掉名称为空的项目 // 过滤掉名称为空的项目
@@ -2007,57 +1993,27 @@ function handleQuoteBilling() {
}) })
// 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目,避免"引用计费"后已提交项目重新出现在"待生成"列表 // 🔧 修复 Bug #445: 过滤掉已生成医嘱的项目,避免"引用计费"后已提交项目重新出现在"待生成"列表
// 使用清空前提取的 submittedKeys名称|||规格|||数量复合键)进行匹配 // 原因:后端返回的计费数据中,已生成医嘱的项目可能没有 requestId 字段
if (submittedKeys.size > 0) { // 方案:用 chargeItemId/requestId/id 与已有的 temporaryAdvices 做匹配,排除已生成项目
temporaryBillingMedicines.value = temporaryBillingMedicines.value.filter(m => { if (temporaryAdvices.value.length > 0) {
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity ?? 0}` const existingAdviceIds = new Set()
return !submittedKeys.has(key) temporaryAdvices.value.forEach(a => {
}) const om = a.originalMedicine || {}
// 同步更新 temporaryAdvices保持两份数据一致 if (om.requestId) existingAdviceIds.add(String(om.requestId))
temporaryAdvices.value = temporaryBillingMedicines.value.map((medicine, index) => { if (om.chargeItemId) existingAdviceIds.add(String(om.chargeItemId))
const specMatch = medicine.specification ? medicine.specification.match(/(\d+)(\D+)/) : null if (om.id) existingAdviceIds.add(String(om.id))
const specValue = specMatch ? parseInt(specMatch[1]) : 1
const specUnit = specMatch ? specMatch[2] : 'ml'
const dosage = specValue * (medicine.quantity || 1)
let usageCode = 'iv'
let usageLabel = '静脉注射'
try {
const jsonContent = medicine.contentJson || medicine.content_json;
if (jsonContent) {
const contentData = JSON.parse(jsonContent);
if (contentData.methodCode) {
usageCode = contentData.methodCode;
usageLabel = getUsageLabel(contentData.methodCode);
}
}
} catch (e) {}
if (!usageCode || usageCode === 'iv') {
if (medicine.medicineName && medicine.medicineName.includes('注射液')) {
usageCode = 'iv'; usageLabel = '静脉注射';
} else if (medicine.medicineName && medicine.medicineName.includes('片')) {
usageCode = 'po'; usageLabel = '口服';
} else if (medicine.medicineName && medicine.medicineName.includes('胶囊')) {
usageCode = 'po'; usageLabel = '口服';
}
}
return {
id: index + 1,
adviceName: medicine.medicineName || '',
dosage,
unit: specUnit,
usage: usageCode,
usageLabel,
frequency: '临时',
executeTime: new Date().toLocaleString('zh-CN'),
originalMedicine: {
...medicine,
medicineName: medicine.medicineName,
specification: medicine.specification,
quantity: medicine.quantity,
encounterId: temporaryPatientInfo.value.visitId
}
}
}) })
if (existingAdviceIds.size > 0) {
temporaryBillingMedicines.value = temporaryBillingMedicines.value.filter(m => {
const mRequestId = m.requestId != null ? String(m.requestId) : null
const mChargeItemId = m.chargeItemId != null ? String(m.chargeItemId) : null
const mId = m.id != null ? String(m.id) : null
if (mRequestId && existingAdviceIds.has(mRequestId)) return false
if (mChargeItemId && existingAdviceIds.has(mChargeItemId)) return false
if (mId && existingAdviceIds.has(mId)) return false
return true
})
}
} }
temporaryMedicalLoading.value = false // 🔧 新增:加载完成 temporaryMedicalLoading.value = false // 🔧 新增:加载完成