From 79b04bdb4edfe33bcc68062e6702291a56385180 Mon Sep 17 00:00:00 2001 From: zhaoyun Date: Sun, 17 May 2026 18:22:24 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20Bug=20#444:=20=E6=A0=B9=E5=9B=A0+?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=B9=E6=A1=88=E6=91=98=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- md/bug-analysis/bug445-analysis.md | 138 +++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 md/bug-analysis/bug445-analysis.md diff --git a/md/bug-analysis/bug445-analysis.md b/md/bug-analysis/bug445-analysis.md new file mode 100644 index 000000000..476a1792e --- /dev/null +++ b/md/bug-analysis/bug445-analysis.md @@ -0,0 +1,138 @@ +# 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 过滤后端返回的数据。 + +## 总结 + +- **根因**:`handleMedicalAdvice` 每次打开都清空 `temporaryAdvices`,然后从后端重新拉取数据。但后端返回的新创建医嘱项可能没有 `requestId`,导致无法过滤。 +- **修复**:保留已提交(有 requestId)的医嘱数据,不清空;同时用这些 requestId 过滤后端返回的新数据。