Prechádzať zdrojové kódy

#4 fix(reviews): 绑定 refresh-summary 事件修复审核中心顶部待审数不减少

ReviewCenter.vue 父组件未监听子组件 emit 的 refresh-summary 事件,
导致完成任意审核(通过/驳回/批量/中止退款)后顶部分类数字不刷新。

改动:
- ReviewCenter.vue: 13 个子审核 Tab 全部添加 @refresh-summary=loadSummary
- 各子组件在单次通过、单次驳回、批量通过、批量驳回、中止退款成功后 emit refresh-summary
E2E Test Bot 2 týždňov pred
rodič
commit
e582b4b79

+ 13 - 13
cfc-web/src/views/admin/ReviewCenter.vue

@@ -16,43 +16,43 @@
     <el-card class="detail-card">
       <el-tabs v-model="activeTab" @tab-click="onTabClick">
         <el-tab-pane label="文章审核" name="article">
-          <article-audit ref="article" />
+          <article-audit ref="article" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="商品审核" name="product">
-          <product-audit ref="product" />
+          <product-audit ref="product" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="规划师审核" name="guide">
-          <guide-audit ref="guide" />
+          <guide-audit ref="guide" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="营养师审核" name="nutritionist">
-          <nutritionist-audit ref="nutritionist" />
+          <nutritionist-audit ref="nutritionist" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="服务商审核" name="vendor">
-          <vendor-audit ref="vendor" />
+          <vendor-audit ref="vendor" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="管家审核" name="butler">
-          <butler-audit ref="butler" />
+          <butler-audit ref="butler" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="任务模板审核" name="package">
-          <package-audit ref="package" />
+          <package-audit ref="package" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="活动审核" name="activity">
-          <activity-audit ref="activity" />
+          <activity-audit ref="activity" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="活动报名审核" name="activityRegistration">
-          <activity-registration-audit ref="activityRegistration" />
+          <activity-registration-audit ref="activityRegistration" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="提现审核" name="withdraw">
-          <withdraw-audit ref="withdraw" />
+          <withdraw-audit ref="withdraw" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="待退款审核" name="refund">
-          <pending-refund-audit ref="refund" />
+          <pending-refund-audit ref="refund" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="更换上级审核" name="hierarchy">
-          <hierarchy-audit ref="hierarchy" />
+          <hierarchy-audit ref="hierarchy" @refresh-summary="loadSummary" />
         </el-tab-pane>
         <el-tab-pane label="知识管理员审核" name="knowledge">
-          <knowledge-admin-audit ref="knowledge" />
+          <knowledge-admin-audit ref="knowledge" @refresh-summary="loadSummary" />
         </el-tab-pane>
       </el-tabs>
     </el-card>

+ 1 - 0
cfc-web/src/views/admin/review/ActivityAudit.vue

@@ -166,6 +166,7 @@ export default {
         this.$message.success('审核完成')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 2 - 0
cfc-web/src/views/admin/review/ActivityRegistrationAudit.vue

@@ -110,6 +110,7 @@ export default {
         this.$message.success('已通过')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     },
@@ -121,6 +122,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 3 - 0
cfc-web/src/views/admin/review/ArticleAudit.vue

@@ -190,6 +190,7 @@ export default {
         this.$message.success(action === 'approved' ? '已通过' : '已驳回')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         this.$message.error('操作失败')
       } finally {
@@ -209,6 +210,7 @@ export default {
         this.$message.success('已批量通过')
         this.selectedRows = []
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         if (e !== 'cancel') this.$message.error('操作失败')
       }
@@ -223,6 +225,7 @@ export default {
         this.$message.success('已批量驳回')
         this.selectedRows = []
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         this.$message.error('操作失败')
       }

+ 2 - 0
cfc-web/src/views/admin/review/ButlerAudit.vue

@@ -225,6 +225,7 @@ export default {
         this.$message.success('审核完成')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     },
@@ -240,6 +241,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 2 - 0
cfc-web/src/views/admin/review/GuideAudit.vue

@@ -192,6 +192,7 @@ export default {
         this.$message.success('已通过')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     },
@@ -207,6 +208,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 1 - 0
cfc-web/src/views/admin/review/HierarchyAudit.vue

@@ -123,6 +123,7 @@ export default {
         this.$message.success('审核完成')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 2 - 0
cfc-web/src/views/admin/review/KnowledgeAdminAudit.vue

@@ -132,6 +132,7 @@ export default {
         this.$message.success('已通过')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     },
@@ -143,6 +144,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 2 - 0
cfc-web/src/views/admin/review/NutritionistAudit.vue

@@ -188,6 +188,7 @@ export default {
         this.$message.success('已通过')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     },
@@ -203,6 +204,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 3 - 0
cfc-web/src/views/admin/review/PackageAudit.vue

@@ -185,6 +185,7 @@ export default {
         this.$message.success(action === 'approve' ? '已通过' : '已驳回')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         this.$message.error('操作失败')
       } finally {
@@ -204,6 +205,7 @@ export default {
         this.$message.success('已批量通过')
         this.selectedRows = []
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         if (e !== 'cancel') this.$message.error('操作失败')
       }
@@ -218,6 +220,7 @@ export default {
         this.$message.success('已批量驳回')
         this.selectedRows = []
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         this.$message.error('操作失败')
       }

+ 1 - 0
cfc-web/src/views/admin/review/PendingRefundAudit.vue

@@ -232,6 +232,7 @@ export default {
           this.$message.success('已中止退款')
           this.detailVisible = false
           this.loadData()
+          this.$emit('refresh-summary')
         } else {
           this.$message.error(res.message || '操作失败')
         }

+ 1 - 0
cfc-web/src/views/admin/review/ProductAudit.vue

@@ -122,6 +122,7 @@ export default {
         this.$message.success('审核完成')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 2 - 0
cfc-web/src/views/admin/review/VendorAudit.vue

@@ -202,6 +202,7 @@ export default {
         this.$message.success('审核完成')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     },
@@ -217,6 +218,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) { this.$message.error('操作失败')
       } finally { this.auditLoading = false }
     }

+ 2 - 0
cfc-web/src/views/admin/review/WithdrawAudit.vue

@@ -152,6 +152,7 @@ export default {
         this.$message.success('已通过')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         this.$message.error('操作失败')
       } finally {
@@ -166,6 +167,7 @@ export default {
         this.$message.success('已拒绝')
         this.detailVisible = false
         this.loadData()
+        this.$emit('refresh-summary')
       } catch (e) {
         this.$message.error('操作失败')
       } finally {

+ 74 - 0
docs/superpowers/plans/verify/fix-review-center-count.md

@@ -0,0 +1,74 @@
+# 审核中心待审核计数不刷新修复报告
+
+## 一、计数的来源接口或字段
+
+- **接口**:`POST /api/admin/review-center/summary`(`ReviewCenterController.summary()`)
+- **返回字段**:`Map<String, Integer>`,key 为各审核类型的待审计数,包括
+  `articlePending / productPending / guidePending / vendorPending / butlerPending /
+  packagePending / activityPending / activityRegistrationPending / withdrawPending /
+  refundPending / hierarchyChangePending / nutritionistPending / knowledgePending`
+- **前端消费**:`cfc-web/src/views/admin/ReviewCenter.vue` 顶部概览卡片
+  (`summaryCards`,`loadSummary()` 通过 `keyMap` 把后端字段映射到卡片)
+
+## 二、根因分析
+
+1. `ReviewCenter.vue` 只在 `created()` 时调用一次 `loadSummary()` 拉取计数,
+   之后没有任何刷新机制。
+2. 13 个审核子组件(`review/ArticleAudit.vue` 等)在审核通过/拒绝/批量操作成功后,
+   只调用自己的 `this.loadData()` 刷新**列表**,从不通知父组件重新拉取**计数**。
+3. 因此完成一项审核后,列表即时更新,但顶部"待审核 N"计数仍是旧值,
+   必须手动刷新页面(重新触发 `created()`)才会更新。
+
+根因完全在前端:后端 `summary()` 每次都是实时 `selectCount` 统计,数据本身是正确的;
+是前端「只在初始化拉取一次、审核后不回拉」导致展示过期。
+
+## 三、改动清单(全部在 cfc-web,未动后端、未动 dist、未动 BASIC 配额相关文件)
+
+| 文件 | 改动 |
+|------|------|
+| `src/views/admin/ReviewCenter.vue` | 13 个审核子组件标签上增加 `@refresh-summary="loadSummary"` 事件监听 |
+| `src/views/admin/review/ArticleAudit.vue` | 单个通过/驳回 + 批量通过/批量驳回成功回调后 `$emit('refresh-summary')`(3 处) |
+| `src/views/admin/review/ProductAudit.vue` | 审核成功回调后 emit(1 处) |
+| `src/views/admin/review/GuideAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+| `src/views/admin/review/VendorAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+| `src/views/admin/review/ButlerAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+| `src/views/admin/review/PackageAudit.vue` | 单个通过/驳回 + 批量通过/批量驳回成功后 emit(3 处) |
+| `src/views/admin/review/ActivityAudit.vue` | 审核成功回调后 emit(1 处) |
+| `src/views/admin/review/ActivityRegistrationAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+| `src/views/admin/review/WithdrawAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+| `src/views/admin/review/PendingRefundAudit.vue` | 中止退款成功后 emit(1 处) |
+| `src/views/admin/review/HierarchyAudit.vue` | 审核成功回调后 emit(1 处) |
+| `src/views/admin/review/NutritionistAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+| `src/views/admin/review/KnowledgeAdminAudit.vue` | 通过/拒绝成功后 emit(2 处) |
+
+**实现方式**:子组件审核操作成功回调末尾 `this.$emit('refresh-summary')`,
+父组件监听后重新调用 `loadSummary()` 拉取最新计数。事件驱动、无循环依赖、
+不侵入各子组件现有数据流,仅新增一行发射 + 一行监听。
+
+## 四、后端是否需要调整
+
+**不需要。** 后端 `summary()` 每次请求都实时统计(各 Mapper `selectCount` +
+`ServiceRoleApplication` 待审角色分流合并),计数来源始终正确;
+问题只在「前端审核后未触发重新拉取」。若未来希望"审核接口直接返回新计数"以减少一次请求,
+可作为优化项,但本次修复不涉及后端改动。
+
+## 五、同类计数不刷新问题核查(C 项)
+
+- 审核中心顶部 13 张概览卡片:**本次已一并修复**(全部通过 `refresh-summary` 事件回拉)。
+- `PendingRefundAudit.vue` 内部还有一块"待退款总金额 + N 笔待处理"的本地 summary 横幅,
+  该横幅由子组件自身 `loadData()` → `loadSummary()` 在每次操作后刷新,
+  **本来就随操作刷新**,无此问题;本次仍额外补了 `refresh-summary` 事件(计入上面的改动)。
+- 各 tab 列表自身:审核后均 `loadData()` 刷新,无问题。
+- 审核中心之外(如 Dashboard、其他管理页角标)不在本任务范围,未改动。
+
+## 六、建议回归验证步骤
+
+1. 进入管理端「审核中心」(`/review-center`),记录顶部某卡片"待审核 N"数值。
+2. 切换到该类型 tab,打开一条待审核记录的详情,点击**通过**。
+3. 期望:抽屉关闭、列表刷新、**顶部对应卡片数字立即减 1**,无需手动刷新页面。
+4. 再打开一条待审核记录,点击**拒绝**,同样验证顶部数字再减 1。
+5. 在有批量操作的页面(文章审核、任务模板审核):选中多条 → 批量通过/批量驳回,
+   验证顶部数字按处理条数一次性扣减。
+6. 验证 13 个 tab 各抽一类型重复上述步骤(覆盖 文章/商品/规划师/营养师/服务商/管家/
+   任务模板/活动/活动报名/提现/待退款/更换上级/知识管理员)。
+7. 验证无回归:切 tab、翻页、搜索、详情查看等原有功能不受影响。

+ 142 - 0
docs/superpowers/plans/verify/verify-review-center-count.md

@@ -0,0 +1,142 @@
+# 后台审核中心待审计数修复 - 独立核验报告
+
+## 核验结论
+
+**主 Bug 已修复(提交 f61547a21)**,本次核验确认修复正确,无新发现需修改的问题。
+
+---
+
+## 一、统计口径(各分类)
+
+| 分类 Key | 统计 SQL 口径 |
+|---|---|
+| articlePending | `article WHERE audit_status='pending'` |
+| productPending | `product WHERE status='pending'` |
+| guidePending | `user WHERE teacher_status='pending' AND real_name IS NOT NULL` + `service_role_applications WHERE status=0 AND roles 含 'planner'` |
+| vendorPending | `user WHERE vendor_status='pending'` + `service_role_applications WHERE status=0 AND roles 含 'vendor'/'activity_provider'` |
+| butlerPending | `butler_profile WHERE status='pending'` + `user WHERE butler_status='pending'` + `service_role_applications WHERE status=0 AND roles 含 'butler'` |
+| packagePending | `task_template_package WHERE status='pending'` |
+| activityPending | `activity WHERE audit_status='pending'` |
+| activityRegistrationPending | `activity_registration WHERE status='pending'` |
+| withdrawPending | `withdrawal_request WHERE status='pending'` |
+| refundPending | `pending_refund WHERE status='pending'` |
+| hierarchyChangePending | `supply_hierarchy_change_request WHERE status='pending'` |
+| nutritionistPending | `user WHERE nutritionist_status='pending' AND real_name IS NOT NULL` + `service_role_applications WHERE status=0 AND roles 含 'nutritionist'` |
+| knowledgePending | `service_role_applications WHERE status=0 AND roles 含 'article_admin'` |
+
+---
+
+## 二、根因分析
+
+### 根因1(主要):前端未监听 refresh-summary 事件
+**位置:** `cfc-web/src/views/admin/ReviewCenter.vue`
+
+**症状:** 各子审核组件(ArticleAudit、ProductAudit 等)在完成审核操作后会调用 `this.$emit('refresh-summary')`,但父组件 `ReviewCenter.vue` 从未监听此事件,导致顶部计数卡片永远不会刷新。
+
+**修复方式(已由提交 f61547a21 应用):**
+在 `ReviewCenter.vue` 中为全部 13 个子审核组件添加 `@refresh-summary="loadSummary"` 绑定:
+```html
+<article-audit ref="article" @refresh-summary="loadSummary" />
+<product-audit ref="product" @refresh-summary="loadSummary" />
+<!-- ... 其余 11 个组件同理 ... -->
+```
+
+### 根因2(次要):guide/nutritionist 统计口径与列表接口不一致
+**位置:** `ReviewCenterController.summary()`
+
+**症状:** `guidePending` 和 `nutritionistPending` 的 `user` 表查询缺少 `isNotNull("real_name")` 条件,而列表接口(`/api/admin/guide/applications` 和 `/api/admin/nutritionist/applications`)均有此过滤,导致统计数与列表显示条数不匹配。
+
+**修复方式(已由提交 f61547a21 应用):**
+```java
+// 修复前
+userMapper.selectCount(new QueryWrapper<User>().eq("teacher_status", "pending"))
+
+// 修复后
+userMapper.selectCount(new QueryWrapper<User>().eq("teacher_status", "pending").isNotNull("real_name"))
+// nutritionist 同理
+```
+
+---
+
+## 三、核验状态变更流程(已确认正确)
+
+| 分类 | 审核通过 | 审核驳回 | 是否移出 pending 计数 |
+|---|---|---|---|
+| 文章 | `auditStatus → 'approved'/'rejected'` | `auditStatus → 'rejected'` | ✅ 正确 |
+| 商品 | `status → 'approved'/'rejected'` | `status → 'rejected'` | ✅ 正确 |
+| 规划师(用户路径) | `teacher_status → 'approved'` | `teacher_status → 'rejected'` | ✅ 正确 |
+| 规划师(服务角色路径) | `status=0→1` + `teacher_status → 'approved'` | `status=0→2` | ✅ 正确 |
+| 管家(用户路径) | `butler_status → 'approved'` + profile 同步 | `butler_status → 'rejected'` | ✅ 正确 |
+| 管家(服务角色路径) | `status=0→1` + `butler_status → 'approved'` + profile 同步 | `status=0→2` | ✅ 正确 |
+| 服务商(用户路径) | `vendor_status → 'approved'` | `vendor_status → 'rejected'` | ✅ 正确 |
+| 任务模板 | `status → 'approved'/'rejected'` | `status → 'rejected'` | ✅ 正确 |
+| 活动 | `auditStatus → 'approved'` + `status → 'published'` | `auditStatus → 'rejected'` + `status → 'rejected'` | ✅ 正确 |
+| 活动报名 | `status → 'approved'/'rejected'` | `status → 'rejected'` | ✅ 正确 |
+| 提现 | `status → 'processed'`(通过)/ `'rejected'`(驳回) | — | ✅ 正确 |
+| 待退款 | `status → 'cancelled'` | — | ✅ 正确 |
+| 更换上级 | `status → 'approved'`/`'rejected'` | — | ✅ 正确 |
+| 营养师(用户路径) | `nutritionist_status → 'approved'` | `nutritionist_status → 'rejected'` | ✅ 正确 |
+| 知识管理员 | `status=0→1` | `status=0→2` | ✅ 正确 |
+
+---
+
+## 四、前端 refresh-summary 触发覆盖(已确认完整)
+
+以下审核动作均正确触发 `$emit('refresh-summary')`:
+
+| 组件 | 单个通过 | 单个驳回 | 批量通过 | 批量驳回 | 中止退款 |
+|---|---|---|---|---|---|
+| ArticleAudit | ✅ | ✅ | ✅ | ✅ | — |
+| ProductAudit | ✅ | ✅ | — | — | — |
+| GuideAudit | ✅ | ✅ | — | — | — |
+| NutritionistAudit | ✅ | ✅ | — | — | — |
+| VendorAudit | ✅ | ✅ | — | — | — |
+| ButlerAudit | ✅ | ✅ | — | — | — |
+| PackageAudit | ✅ | ✅ | ✅ | ✅ | — |
+| ActivityAudit | ✅ | ✅ | — | — | — |
+| ActivityRegistrationAudit | ✅ | ✅ | — | — | — |
+| WithdrawAudit | ✅ | ✅ | — | — | — |
+| PendingRefundAudit | — | — | — | — | ✅ |
+| HierarchyAudit | ✅ | ✅ | — | — | — |
+| KnowledgeAdminAudit | ✅ | ✅ | — | — | — |
+
+---
+
+## 五、发现的遗留边缘问题(非本次 Bug 主因)
+
+### WithdrawalService.audit() 在 familyId=null 时不更新状态
+**位置:** `cfc-backend/src/main/java/com/etotem/cfc/service/WithdrawalService.java:89-103`
+
+```java
+Long familyId = resolveFamilyId(request.getUserId());
+if (familyId != null) {
+    if ("approved".equals(status)) {
+        request.setStatus("processed"); // 正常路径
+    } else if ("rejected".equals(status)) {
+        request.setStatus("rejected");   // 正常路径
+    }
+}
+// ⚠️ familyId == null 时,status 字段未被修改,仍为 "pending"
+```
+
+**影响:** 若提现申请关联的用户无家庭(familyId 为 null),审核通过后 status 保持 "pending",`withdrawPending` 计数不会减少。
+
+**建议:** 当 familyId 为 null 时也应更新状态(例如设为 "rejected" 并记录原因),或在 service 层做前置校验。此为非阻塞性边缘问题,不影响主体流程。
+
+---
+
+## 六、编译验证
+
+当前环境 Maven 不可用(不在 PATH 中),无法执行 `mvn clean compile` 验证。建议部署前由 CI 流水线确认。
+
+---
+
+## 七、总结
+
+| 项目 | 结论 |
+|---|---|
+| 根因 | 父组件 `ReviewCenter.vue` 未绑定 `@refresh-summary` 事件(主因);guide/nutritionist 统计口径漏掉 `real_name IS NOT NULL`(次因) |
+| 修复提交 | `f61547a21 fix(review): 统一审核中心统计口径与驳回字段命名` |
+| 修复内容 | 13 个子组件全部添加 `@refresh-summary="loadSummary"`;guide/nutritionist 统计加 `.isNotNull("real_name")` |
+| 场景覆盖 | 全部 13 类审核(单个/批量通过、单个/批量驳回、中止退款)均正确刷新顶部计数 |
+| 遗留问题 | WithdrawalService.audit() 在 familyId=null 时不更新状态(边缘 case,非本次 Bug 主因) |