# Bug #732 诸葛亮分析报告 > **文档类型**: Bug分析 > **分析时间**: 2026-06-13 00:30:55 > **分析模型**: mimo-v2.5 (LLM深度分析) --- ## 基本信息 - **Bug #**: 732 - **标题**: 【医嘱闭环-闭环统计】科室的闭环和未闭环医嘱预警加载卡死 - **模块**: 系统管理 - **提出人**: 王栩坤 --- Based on my analysis, I now have a clear picture of the root cause. Let me output the structured analysis. --- ### 一、Bug 理解 **禅道原文引用:** - **标题**:【医嘱闭环-闭环统计】科室的闭环和未闭环医嘱预警加载卡死 - **重现步骤**:登录内科医生1的账号:doctor1 密码:123456进入医嘱闭环打开闭环统计查看界面 - **结果**:出现报错:`main.js:84 TypeError: Cannot convert object to primitive value` - **期望**:能够正常显示报卡的信息和操作界面 **附图分析关键信息:** - 页面标题"科室/医生闭环统计",右侧有"按科室"下拉和"刷新"按钮 - 两个数据区域(科室/医生闭环统计表格、未闭环医嘱预警表格)均为空白,仅有蓝色加载动画圆圈(loading spinner) - 右上角红色标签"X 条待处理"但数字被遮挡 - **核心异常**:控制台 `TypeError: Cannot convert object to primitive value`,导致两个表格数据加载卡死 **综合总结**:用户登录 doctor1 账号进入医嘱闭环统计页面,页面框架正常渲染但两个核心数据表格(科室/医生闭环统计、未闭环医嘱预警)均卡在 loading 状态无法加载数据。控制台报 `TypeError: Cannot convert object to primitive value`,表明前端在渲染过程中遇到了一个对象值被当作原始值使用的场景。 --- ### 二、根因分析 经过全链路追踪(前端 Vue 组件 → API → Controller → AppService → Mapper SQL),根因是**多层问题叠加**: #### 根因 1(直接触发 TypeError):PostgreSQL 列别名大小写折叠 + 前端 prop 不匹配 `OrderExecuteRecordMapper.java` 中的 4 个 SQL 查询使用了 camelCase 列别名: ```sql -- selectGroupByDepartment SELECT COALESCE(m.department_name, '未知') AS department, COUNT(*) AS totalOrders, ... -- selectGroupByDoctor SELECT COALESCE(m.doctor_name, '未知') AS doctorName, COUNT(*) AS totalOrders, ... -- selectUnclosedWarnings SELECT e.order_no AS orderNo, e.patient_name AS patientName, ... ``` **PostgreSQL 会将未加引号的标识符折叠为小写**。因此 JDBC ResultSet 返回的 Map key 全部是小写: - `totalOrders` → `totalorders` - `closedCount` → `closedcount` - `orderNo` → `orderno` - `patientName` → `patientname` - `orderType` → `ordertype` - `currentStep` → `currentstep` - `orderTime` → `ordertime` Java 的 `getLong()`/`getString()` helper 有 `key.toLowerCase()` fallback,所以 Java 层计算正确。但 **`getGroupStats()` 返回的 Map 中同时存在**: - 小写 key(来自 SQL):`totalorders`, `closedcount` - camelCase key(Java 代码 put):`unclosedCount`, `closedRate` 前端模板使用 `prop="totalOrders"` 无法匹配 `totalorders`,导致表格列值为 `undefined`。 #### 根因 2(直接触发 TypeError):`el-progress` 接收到非 number 类型的 `percentage` 在 `` 中: - `scope.row.closedRate` 是 Java 计算后 put 进 map 的 `double`,序列化为 JSON number → 正常 - 但 `scope.row.closedRate` 如果因上游数据异常为非数字对象,`||` 不会拦截(对象是 truthy),`el-progress` 内部尝试数值转换时抛出 `TypeError: Cannot convert object to primitive value` #### 根因 3(数据问题):`getUnclosedWarnings()` 未用 helper 处理 `orderTime` ```java // 第 143 行 Object orderTimeObj = row.get("orderTime"); // ← PostgreSQL 返回的是 "ordertime",永远 null ``` 导致所有未闭环医嘱的 `overdueDuration` 都显示"未知",`orderTime` 都显示空字符串。 #### 根因 4(缺失数据源):`order_execute_record` 表与 `order_main` 表无关联数据 `selectGroupByDepartment` 等 SQL 做了 `LEFT JOIN order_main m ON e.order_no = m.order_no`,但 `order_execute_record` 是闭环追踪表,其中的 `order_no` 可能与 `order_main`(门诊挂号单表)中的 `order_no` **语义不同**——`order_main` 是门诊挂号/预约表,不是医嘱表。即使 JOIN 成功,`department_name` 和 `doctor_name` 也可能全为 NULL。 **涉及的关键文件:** | 层 | 文件 | 行号 | |---|---|---| | Mapper SQL | `OrderExecuteRecordMapper.java` | 全部 4 个 `@Select` 方法 | | AppService | `OrderClosedLoopAppServiceImpl.java` | `getGroupStats()` L131-143, `getUnclosedWarnings()` L146-168 | | 前端组件 | `statistics/index.vue` | L74 `el-progress`, L84-88 `el-table-column`, L99-102 `el-progress` | | 前端 API | `orderclosedloop.js` | `getClosedLoopStatistics()` | --- ### 三、修复方案 #### 修复 1:SQL 列别名加双引号保留大小写(根本修复) 修改 `OrderExecuteRecordMapper.java`,所有 SQL 别名加 PostgreSQL 双引号: ```sql -- selectOverviewByType SELECT e.order_type AS "orderType", COUNT(*) AS "totalOrders", COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS "closedCount" -- selectGroupByDepartment SELECT COALESCE(m.department_name, '未知') AS "department", COUNT(*) AS "totalOrders", COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS "closedCount" -- selectGroupByDoctor SELECT COALESCE(m.doctor_name, '未知') AS "doctorName", COUNT(*) AS "totalOrders", COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS "closedCount" -- selectUnclosedWarnings SELECT e.order_no AS "orderNo", e.patient_name AS "patientName", e.order_type AS "orderType", COALESCE(m.department_name, '未知') AS "department", COALESCE(m.doctor_name, '未知') AS "doctorName", e.current_step AS "currentStep", e.create_time AS "orderTime" ``` 这样 JDBC 返回的 Map key 就是精确的 camelCase,前端 `prop` 能正确匹配。 #### 修复 2:`getUnclosedWarnings()` 使用 helper 处理 `orderTime` 将 `row.get("orderTime")` 改为使用 `getString()` helper 或直接用小写 key: ```java // 修改前 Object orderTimeObj = row.get("orderTime"); // 修改后(兼容大小写) Object orderTimeObj = row.get("orderTime"); if (orderTimeObj == null) { orderTimeObj = row.get("ordertime"); } ``` 或者更彻底地,在修复 1(SQL 加引号)之后,`row.get("orderTime")` 就能正确获取值。 #### 修复 3:前端 `el-progress` 添加防御性数值转换 在 `statistics/index.vue` 中,确保传给 `el-progress` 的 `percentage` 始终为 number: ```html ``` 同理修复 `closedRate` 的绑定: ```html ``` #### 修复 4:`getGroupStats()` 确保 key 一致性 在 `OrderClosedLoopAppServiceImpl.getGroupStats()` 中,显式 put camelCase key(即使修复 1 后 SQL 已正确,这是防御性编码): ```java // 在 item.put("unclosedCount", ...) 之前加: item.put("totalOrders", totalOrders); item.put("closedCount", closedCount); ``` --- ### 四、路由决策 **FIXER: zhaoyun(赵云 — 前端开发)** **REASON**: 此 Bug 的直接表现是前端 TypeError 导致页面卡死,核心修复在前端模板的防御性编码(修复 3)和后端 Mapper SQL 的列别名引号处理(修复 1/2/4)。赵云负责前端修复(`statistics/index.vue` 中 `el-progress` 的数值转换),同时需要协调荀彧协助后端 Mapper SQL 修改(`OrderExecuteRecordMapper.java`)。前端权重更高(页面卡死的直接原因是前端渲染),由赵云主导。 --- ## 路由决策 - **FIXER_ID**: zhaoyun - **修复 Agent**: zhaoyun(前端) - **原因**: LLM 分析决策 > ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。