9.5 KiB
Bug #716 诸葛亮分析报告
文档类型: Bug分析 分析时间: 2026-06-13 02:33:10 分析模型: mimo-v2.5 (LLM深度分析)
基本信息
- Bug #: 716
- 标题: 【住院医生站-临床医嘱】住院医嘱录入界面:医嘱类型/耗材仓库显示不符合业务逻辑,且开嘱医生列无回写值
- 模块: 住院医生工作站
- 提出人: 陈显精
I have enough evidence now. Let me provide the comprehensive analysis.
Bug #716 分析报告
一、Bug 理解
原文引用 — 禅道 Bug 标题:
【住院医生站-临床医嘱】住院医嘱录入界面:医嘱类型/耗材仓库显示不符合业务逻辑,且开嘱医生列无回写值
重现步骤:
- 登录账号
doctor1,在"住院医生工作站"选中患者,点击【新增】医嘱 - 点击【医嘱类型】下拉框,观察显示内容
- 在添加含耗材的医嘱时,观察【耗材仓库】字段选择列表的显示内容
- 保存并签发该医嘱,查看医嘱列表明细中【开嘱医生】列的显示
结果:
- 医嘱类型显示为数字(如"2")
- 耗材仓库选择框显示的是批号(如"4444444")
- 已保存/签发的医嘱,【开嘱医生】列显示为"-"
期望:
- 医嘱类型下拉应展示业务名称(如"耗材"),而非数字
- 耗材仓库选择列表应展示"仓库名称",而非批号
- 【开嘱医生】应显示当前操作医生的姓名
附图关键信息:
- 图2506:红色箭头标注"显示数字",指向医嘱类型区域显示"2"
- 图2507:红色箭头标注"应该显示耗材仓库 不是批号",耗材仓库下拉选中值为"4444444"
- 图2508:红色箭头标注"未回写",新保存的耗材医嘱【开嘱医生】列显示"-"
综合总结: 用户在住院医生工作站新增耗材类医嘱时,遇到三个显示/数据问题:(1) 医嘱类型下拉框的 el-select 显示原始数字编码而非业务名称;(2) 耗材仓库下拉框显示的是 inventoryId/批号而非仓库名称标签;(3) 新保存的耗材医嘱在列表中开嘱医生列显示"-",说明 requesterId 的字典翻译或数据回写链路断裂。
二、根因分析
问题1:医嘱类型显示数字
目标文件:
healthlink-his-ui/src/views/inpatientDoctor/home/components/order/index.vue(行 312-357)
根因: 内联编辑行的医嘱类型 el-select 使用 :model-value="getRowSelectValue(scope.row)" 单向绑定。@change 处理器直接修改 filterPrescriptionList[scope.rowIndex].adviceType,但 filterPrescriptionList 是 computed 属性,其返回的数组是 prescriptionList.value.filter(...) 的结果。虽然修改的是同一个对象引用,但 Vue 的 computed 缓存机制可能导致 el-select 未及时重新评估 model-value,从而显示原始值而非匹配的 label。
此外,getRowSelectValue 返回 row.adviceType(数字),而选项的 value 也是数字 2。如果 adviceType 在某处被转为字符串 "2"(例如从 contentJson 反序列化时),则 model-value="2" (string) 与 value: 2 (number) 不匹配,el-select 会显示原始值。
关键代码:
// index.vue:1202
function getRowSelectValue(row) {
if (row.adviceType == 1 && row.categoryCode) {
return '1-' + row.categoryCode;
}
if (row.adviceType == 7) {
return 7;
}
return row.adviceType; // ← 可能是 string "2" 而非 number 2
}
影响范围: index.vue:316 的 el-select :model-value 绑定
问题2:耗材仓库显示批号
目标文件:
healthlink-his-ui/src/views/inpatientDoctor/home/components/order/OrderForm.vue(行 479-498)
根因: 耗材(adviceType==2)的药房/仓库下拉框使用 v-model="row.inventoryId",el-option 的 :value="item.inventoryId"。当 row.inventoryId 与 stockList 中的 item.inventoryId 类型不一致时(一个是 string,一个是 number),el-select 无法匹配选项,回退显示原始 inventoryId 值。
后端 AdviceInventoryDto.inventoryId 使用了 @JsonSerialize(using = ToStringSerializer.class),Jackson 会将其序列化为字符串。但在 setValue 函数中,inventoryId 从 selectedStock?.inventoryId 赋值,而 selectedStock 来自 mergedStockList(合并了 inventoryList 和 priceList)。如果 priceList 中也有 inventoryId 字段且为 number 类型,{ ...item, ...priceList[index] } 的 spread 会覆盖为 number,导致类型不一致。
关键代码:
// index.vue:2206
mergedStockList = row.inventoryList.map((item, index) => {
return { ...item, ...priceList[index] }; // priceList 可能覆盖 inventoryId 类型
});
// OrderForm.vue:480
<el-select v-model="row.inventoryId" ...>
<el-option :value="item.inventoryId" :label="`${item.locationName} 批次号: ...`" />
</el-select>
问题3:开嘱医生显示"-"
目标文件:
- 后端:
AdviceManageAppServiceImpl.java(行 817-910,handDevice方法) - 前端:
index.vue(行 1030-1033, 数据转换)
根因: 后端 AdviceSaveDto 构造函数设置了 this.practitionerId = SecurityUtils.getLoginUser().getPractitionerId()。但 handDevice 方法中,requesterId 只在 if (is_save) 分支内设置:
if (is_save) {
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId());
}
当用户直接签发(adviceOpType=SIGN_ADVICE)时,is_save=false,requesterId 不会被设置到 DeviceRequest 对象上。虽然 MyBatis-Plus 的 saveOrUpdate 默认跳过 null 字段(不会覆盖已有的 requester_id),但如果这是首次签发(未先保存),则 requester_id 在数据库中为 NULL。
更重要的是,SQL 查询的列别名 requester_id_dict_text(下划线命名)与 DTO 字段 requesterId_dictText(混合命名)不匹配。MyBatis 的 mapUnderscoreToCamelCase 会将 requester_id_dict_text 映射为 requesterIdDictText,而非 requesterId_dictText。虽然 @Dict 注解理论上会补填,但如果 requesterId 本身为 NULL,则字典查询也返回 NULL,最终前端回退到 '-'。
三、修复方案
修复1:医嘱类型 el-select 显示数字
文件: healthlink-his-ui/src/views/inpatientDoctor/home/components/order/index.vue
修改内容:
- 在
getRowSelectValue函数中,确保返回值类型与选项value类型一致(统一为 number) - 在
@change处理器中,使用prescriptionList.value[index]直接操作源数据,而非通过 computed 属性
// 修复 getRowSelectValue,确保返回 number
function getRowSelectValue(row) {
const adviceType = Number(row.adviceType);
if (adviceType === 1 && row.categoryCode) {
return '1-' + row.categoryCode;
}
if (adviceType === 7) {
return 7;
}
return isNaN(adviceType) ? undefined : adviceType;
}
修复2:耗材仓库显示批号
文件: healthlink-his-ui/src/views/inpatientDoctor/home/components/order/OrderForm.vue
修改内容:
- 在耗材(adviceType==2)的 el-select 中,使用
:value="String(item.inventoryId)"和v-model统一为 string 类型 - 或者在
setValue函数中确保inventoryId类型一致
// OrderForm.vue 中耗材部分,统一 inventoryId 为 string
<el-option
v-for="item in row.stockList"
:key="item.inventoryId"
:value="String(item.inventoryId)"
:label="`${item.locationName} 批次号: ${item.lotNumber ?? '-'} 库存:${...}`"
/>
同时在 index.vue 的 setValue 中:
inventoryId: selectedStock?.inventoryId ? String(selectedStock.inventoryId) : undefined,
修复3:开嘱医生显示"-"
文件: healthlink-his-server/.../AdviceManageAppServiceImpl.java
修改内容:
在 handDevice 方法中,将 requesterId 的设置移到 if (is_save) 块外面,确保签发操作也设置 requesterId:
// handDevice 方法中,longInsertOrUpdateList 和 tempInsertOrUpdateList 的循环内
// 将 requesterId 设置移到 if (is_save) 块外
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // ← 移到这里
if (is_save) {
deviceRequest.setBusNo(...);
deviceRequest.setGenerateSourceEnum(...);
// ... 其他仅保存时设置的字段
}
同样在 handMedication 和 handService 方法中检查是否存在相同问题。
附加修复(保险措施): 在前端数据转换中,确保 requesterId_dictText 的回退逻辑更健壮:
requesterId_dictText: item.requesterId_dictText
|| (item.requesterId ? (String(item.requesterId) === String(userStore.practitionerId) ? userStore.name : '') : '')
|| '-',
四、路由决策
FIXER: zhaoyun(前端开发)+ guanyu(后端开发)
REASON: 问题1和问题2是纯前端 el-select 绑定/类型问题,由赵云修复;问题3涉及后端 handDevice 方法中 requesterId 设置位置不当,需要关羽修改后端 Java 代码。两个角色各负责独立的修复范围,无冲突,可并行工作。
路由决策
- FIXER_ID: guanyu
- 修复 Agent: guanyu(后端)
- 原因: ** 问题1和问题2是纯前端 el-select 绑定/类型问题,由赵云修复;问题3涉及后端
handDevice方法中requesterId设置位置不当,需要关羽修改后端 Java 代码。两个角色各负责独立的修复范围,无冲突,可并行工作。
⚠️ 修复人员请先验证以上分析是否正确,再执行修复。