diff --git a/BUG461_ANALYSIS.md b/BUG461_ANALYSIS.md new file mode 100644 index 000000000..b548866d4 --- /dev/null +++ b/BUG461_ANALYSIS.md @@ -0,0 +1,89 @@ +# Bug #461 分析报告 + +## Bug描述 +[系统管理-执行科室配置] 保存项目配置后,项目名称回显为ID码,未显示正确名称 + +## 根因分析 + +### 数据流 +1. 前端调用 `getDiagnosisTreatmentList()` → GET `/base-data-manage/org-loc/org-loc` +2. 后端 `OrganizationLocationController.getOrgLocPage()` 返回 `Page` +3. `OrgLocQueryDto.activityDefinitionId` 标注了 `@Dict(dictTable="wor_activity_definition", dictCode="id", dictText="name")` +4. `DictAspect` AOP 拦截 GET/POST 请求,对带 `@Dict` 注解的字段执行 SQL 翻译:`SELECT name FROM wor_activity_definition WHERE id::varchar = ? LIMIT 1` +5. 翻译结果写入 `activityDefinitionId_dictText` 字段 +6. 前端使用 `record.activityDefinitionId_dictText` 作为项目名称显示 + +### 根本原因 +**`DictAspect.queryDictLabel` 方法在 SQL 查询失败时返回空字符串 `""`,导致 `_dictText` 字段被设置为空值。** + +具体代码 (`DictAspect.java:123-149`): +```java +private String queryDictLabel(String dictTable, String dictCode, String dictText, String deleteFlag, String dictValue) { + if (!StringUtils.hasText(dictTable)) { + return DictUtils.getDictLabel(dictCode, dictValue); + } else { + if (!StringUtils.hasText(dictText)) { + return DictUtils.getDictLabel(dictCode, dictValue); + } + String sql = String.format("SELECT %s FROM %s WHERE %s::varchar = ?", dictText, dictTable, dictCode); + // ... + try { + return jdbcTemplate.queryForObject(sql, String.class, dictValue); + } catch (DataAccessException e) { + return ""; // ← 关键问题:查询失败返回空字符串 + } + } +} +``` + +当 `jdbcTemplate.queryForObject` 查询失败(无结果或异常)时,返回空字符串 `""`。而在 `processDict` 中: +```java +String dictLabel = queryDictLabel(...); +if (dictLabel != null) { // ← 空字符串 "" 不等于 null,条件为 true + textField.set(dto, dictLabel); // ← 设置为空字符串 +} +``` + +空字符串 `""` 不是 `null`,所以 `_dictText` 被设为空字符串,而不是保持 `null`(未翻译)。前端收到 `activityDefinitionId_dictText: ""`,当作有效值处理,显示为空或回退到 ID。 + +### 前端 fallback 的不足 +前端代码在 `getList()` 中尝试用 `activityDefinitionId_dictText` 补充选项,但: +```javascript +if (record.activityDefinitionId && !filteredOptions.some(o => o.value === record.activityDefinitionId)) { + filteredOptions.push({ + value: record.activityDefinitionId, + label: record.activityDefinitionId_dictText || record.activityDefinitionId // ← 空字符串时显示ID + }); +} +``` +空字符串是 falsy 值,所以 `"" || record.activityDefinitionId` 回退到 ID,仍然显示 ID。 + +## 影响范围 +- **前端**: `openhis-ui-vue3/src/views/basicmanage/implementDepartment/index.vue` +- **后端**: `openhis-server-new/.../basedatamanage/appservice/impl/OrganizationLocationAppServiceImpl.java` +- **AOP**: `openhis-server-new/.../common/aspectj/DictAspect.java` +- **DTO**: `openhis-server-new/.../basedatamanage/dto/OrgLocQueryDto.java` +- **数据库表**: `adm_organization_location`, `wor_activity_definition` + +## 修复方案 + +### 方案:在 service 层直接 JOIN 查询项目名称 + +修改 `OrganizationLocationAppServiceImpl.getOrgLocPage()` 方法,在返回结果前手动填充 `activityDefinitionId_dictText`,不依赖 DictAspect 的行为。这样更可靠,避免了 AOP 执行顺序、SQL异常处理等不确定因素。 + +具体改动:在 `OrganizationLocationAppServiceImpl` 中,利用已有的 `activityDefinitionMapper` 对每条记录补充 `activityDefinitionId_dictText`。 + +## 验证计划 +1. 修改代码后编译通过 +2. 验证 `activityDefinitionId_dictText` 在 API 响应中正确填充 +3. 前端 el-select 能正确显示项目名称而非 ID + +--- + +## 修复结果:✅ 成功,12行改动 + +**修改文件**: `openhis-server-new/openhis-application/src/main/java/com/openhis/web/basedatamanage/appservice/impl/OrganizationLocationAppServiceImpl.java` + +**改动内容**: 在 `getOrgLocPage()` 方法中,对分页返回的每条记录,使用已注入的 `activityDefinitionMapper` 查询对应的 `ActivityDefinition` 实体,手动填充 `activityDefinitionId_dictText` 字段。 + +**策略**: 不依赖 DictAspect 的 AOP 翻译机制,在 service 层直接填充字典翻译值,确保前端能接收到正确的项目名称。