# 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 标题:**
> 【住院医生站-临床医嘱】住院医嘱录入界面:医嘱类型/耗材仓库显示不符合业务逻辑,且开嘱医生列无回写值
**重现步骤:**
1. 登录账号 `doctor1`,在"住院医生工作站"选中患者,点击【新增】医嘱
2. 点击【医嘱类型】下拉框,观察显示内容
3. 在添加含耗材的医嘱时,观察【耗材仓库】字段选择列表的显示内容
4. 保存并签发该医嘱,查看医嘱列表明细中【开嘱医生】列的显示
**结果:**
1. 医嘱类型显示为数字(如"2")
2. 耗材仓库选择框显示的是批号(如"4444444")
3. 已保存/签发的医嘱,【开嘱医生】列显示为"-"
**期望:**
1. 医嘱类型下拉应展示业务名称(如"耗材"),而非数字
2. 耗材仓库选择列表应展示"仓库名称",而非批号
3. 【开嘱医生】应显示当前操作医生的姓名
**附图关键信息:**
- 图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 会显示原始值。
**关键代码:**
```javascript
// 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,导致类型不一致。
**关键代码:**
```javascript
// index.vue:2206
mergedStockList = row.inventoryList.map((item, index) => {
return { ...item, ...priceList[index] }; // priceList 可能覆盖 inventoryId 类型
});
// OrderForm.vue:480
```
---
#### 问题3:开嘱医生显示"-"
**目标文件:**
- 后端:`AdviceManageAppServiceImpl.java` (行 817-910, `handDevice` 方法)
- 前端:`index.vue` (行 1030-1033, 数据转换)
**根因:** 后端 `AdviceSaveDto` 构造函数设置了 `this.practitionerId = SecurityUtils.getLoginUser().getPractitionerId()`。但 `handDevice` 方法中,`requesterId` 只在 `if (is_save)` 分支内设置:
```java
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`
**修改内容:**
1. 在 `getRowSelectValue` 函数中,确保返回值类型与选项 `value` 类型一致(统一为 number)
2. 在 `@change` 处理器中,使用 `prescriptionList.value[index]` 直接操作源数据,而非通过 computed 属性
```javascript
// 修复 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`
**修改内容:**
1. 在耗材(adviceType==2)的 el-select 中,使用 `:value="String(item.inventoryId)"` 和 `v-model` 统一为 string 类型
2. 或者在 `setValue` 函数中确保 `inventoryId` 类型一致
```javascript
// OrderForm.vue 中耗材部分,统一 inventoryId 为 string
```
同时在 `index.vue` 的 `setValue` 中:
```javascript
inventoryId: selectedStock?.inventoryId ? String(selectedStock.inventoryId) : undefined,
```
#### 修复3:开嘱医生显示"-"
**文件:** `healthlink-his-server/.../AdviceManageAppServiceImpl.java`
**修改内容:**
在 `handDevice` 方法中,将 `requesterId` 的设置移到 `if (is_save)` 块外面,确保签发操作也设置 `requesterId`:
```java
// handDevice 方法中,longInsertOrUpdateList 和 tempInsertOrUpdateList 的循环内
// 将 requesterId 设置移到 if (is_save) 块外
deviceRequest.setRequesterId(regAdviceSaveDto.getPractitionerId()); // ← 移到这里
if (is_save) {
deviceRequest.setBusNo(...);
deviceRequest.setGenerateSourceEnum(...);
// ... 其他仅保存时设置的字段
}
```
同样在 `handMedication` 和 `handService` 方法中检查是否存在相同问题。
**附加修复(保险措施):** 在前端数据转换中,确保 `requesterId_dictText` 的回退逻辑更健壮:
```javascript
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 代码。两个角色各负责独立的修复范围,无冲突,可并行工作。
> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。